#!/usr/bin/env php
<?php
/**
 * Generated by Box.
 *
 * @link https://github.com/herrera-io/php-box/
 */
if (class_exists('Phar')) {
Phar::mapPhar('default.phar');
require 'phar://' . __FILE__ . '/bin/php7cc.php';
}
__HALT_COMPILER(); ?>
5               L   vendor/bcncommerce/json-stream/Bcn/Component/Json/Exception/ParsingError.php  D wY  ¶      L   vendor/bcncommerce/json-stream/Bcn/Component/Json/Exception/ReadingError.php#  D wY#  AQ>      L   vendor/bcncommerce/json-stream/Bcn/Component/Json/Exception/WritingError.php#  D wY#  !      <   vendor/bcncommerce/json-stream/Bcn/Component/Json/Parser.phpJ  D wYJ  r      N   vendor/bcncommerce/json-stream/Bcn/Component/Json/Parser/ListenerInterface.php!  D wY!  _N      ;   vendor/bcncommerce/json-stream/Bcn/Component/Json/README.mdN  D wYN        <   vendor/bcncommerce/json-stream/Bcn/Component/Json/Reader.php  D wY  T      F   vendor/bcncommerce/json-stream/Bcn/Component/Json/Reader/Tokenizer.php[  D wY[  h.-      G   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/ExampleTest.php
  D wY
  K}      Q   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest.php  D wY  a?F      ^   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest/TestListener.php  D wY  I-L      g   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest/fixtures/data-ranges.json$  D wY$  ;.      i   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest/fixtures/escaped-chars.jsonm   D wYm   l      c   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest/fixtures/example.jsonX  D wYX  <`g      a   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Parser/FunctionalTest/fixtures/plain.jsonM   D wYM   z      P   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Reader/TokenizerTest.php  D wY  h      U   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/Reader/fixtures/tokenizer.yml
  D wY
  =yO      F   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/ReaderTest.php_  D wY_  ,h      F   vendor/bcncommerce/json-stream/Bcn/Component/Json/Tests/WriterTest.phpK  D wYK  욶      <   vendor/bcncommerce/json-stream/Bcn/Component/Json/Writer.php  D wY        ?   vendor/bcncommerce/json-stream/Bcn/Component/Json/composer.json  D wY  {      B   vendor/bcncommerce/json-stream/Bcn/Component/Json/phpunit.xml.dist#  D wY#  C:m         vendor/composer/installed.jsonL  D wYL  {      '   vendor/composer/autoload_namespaces.php  D wY  ֤      !   vendor/composer/autoload_psr4.php   D wY   =q      %   vendor/composer/autoload_classmap.php   D wY   b      !   vendor/composer/include_paths.php   D wY   8Y&      "   vendor/composer/autoload_files.php  D wY  N      #   vendor/composer/autoload_static.phpO  D wYO  DՊ      !   vendor/composer/autoload_real.php(
  D wY(
           vendor/composer/ClassLoader.php4  D wY4  Q         vendor/composer/LICENSE.  D wY.            vendor/pimple/pimple/CHANGELOG  D wY  +,@         vendor/pimple/pimple/LICENSE)  D wY)  ku         vendor/pimple/pimple/README.rst  D wY  f      "   vendor/pimple/pimple/composer.jsonV  D wYV  7      )   vendor/pimple/pimple/ext/pimple/README.md   D wY   7eQ      )   vendor/pimple/pimple/ext/pimple/config.m4  D wY  o~      *   vendor/pimple/pimple/ext/pimple/config.w32(  D wY(  X"Ҷ      ,   vendor/pimple/pimple/ext/pimple/php_pimple.h"  D wY"  m~ɶ      (   vendor/pimple/pimple/ext/pimple/pimple.cTq  D wYTq  t      /   vendor/pimple/pimple/ext/pimple/pimple_compat.hg  D wYg  oē_      .   vendor/pimple/pimple/ext/pimple/tests/001.phptV  D wYV  '"      .   vendor/pimple/pimple/ext/pimple/tests/002.phpt  D wY  G-      .   vendor/pimple/pimple/ext/pimple/tests/003.phpt   D wY   3      .   vendor/pimple/pimple/ext/pimple/tests/004.phpt!  D wY!  ;VD      .   vendor/pimple/pimple/ext/pimple/tests/005.phpt  D wY  XV      .   vendor/pimple/pimple/ext/pimple/tests/006.phptU  D wYU  #L      .   vendor/pimple/pimple/ext/pimple/tests/007.phptr  D wYr  禖      .   vendor/pimple/pimple/ext/pimple/tests/008.phpt  D wY  #!      .   vendor/pimple/pimple/ext/pimple/tests/009.phptQ  D wYQ  tŶ      .   vendor/pimple/pimple/ext/pimple/tests/010.phpt  D wY  Q      .   vendor/pimple/pimple/ext/pimple/tests/011.phpt  D wY  wg      .   vendor/pimple/pimple/ext/pimple/tests/012.phpt  D wY  U      .   vendor/pimple/pimple/ext/pimple/tests/013.phptp  D wYp  \Ͷ      .   vendor/pimple/pimple/ext/pimple/tests/014.phpt  D wY  t#{      .   vendor/pimple/pimple/ext/pimple/tests/015.phpt
  D wY
  7<      .   vendor/pimple/pimple/ext/pimple/tests/016.phptA  D wYA  S      .   vendor/pimple/pimple/ext/pimple/tests/017.phpt  D wY  Q      0   vendor/pimple/pimple/ext/pimple/tests/017_1.phpt  D wY  {V;      .   vendor/pimple/pimple/ext/pimple/tests/018.phpt  D wY  ȃ      0   vendor/pimple/pimple/ext/pimple/tests/bench.phpb  D wY  ɛ      7   vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb  D wY        %   vendor/pimple/pimple/phpunit.xml.dist  D wY  L      -   vendor/pimple/pimple/src/Pimple/Container.phpc"  D wYc"  U      <   vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.phpT  D wYT  k      <   vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.phpX  D wYX  2      ?   vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php  D wY  _8`      H   vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.phpH  D wYH  s.      :   vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php  D wY  [13      L   vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php
  D wY
  I      4   vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php:3  D wY:3  J+7      $   vendor/nikic/php-parser/CHANGELOG.md9.  D wY9.  bV         vendor/nikic/php-parser/LICENSE  D wY  EF      !   vendor/nikic/php-parser/README.md  D wY  !̾      &   vendor/nikic/php-parser/UPGRADE-1.0.md  D wY  ̤g      )   vendor/nikic/php-parser/bin/php-parse.php  D wY  ?r      %   vendor/nikic/php-parser/composer.json  D wY  h;      3   vendor/nikic/php-parser/doc/0_Introduction.markdown  D wY  Wk]      3   vendor/nikic/php-parser/doc/1_Installation.markdownV  D wYV  }O      @   vendor/nikic/php-parser/doc/2_Usage_of_basic_components.markdown;<  D wY;<  Sv"A      F   vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown  D wY  eU!M      6   vendor/nikic/php-parser/doc/4_Code_generation.markdownp
  D wYp
  uL      4   vendor/nikic/php-parser/doc/component/Error.markdown1
  D wY1
  oj9      4   vendor/nikic/php-parser/doc/component/Lexer.markdown  D wY  ~s      )   vendor/nikic/php-parser/grammar/README.md#  D wY#  7N      +   vendor/nikic/php-parser/grammar/analyze.php  D wY        1   vendor/nikic/php-parser/grammar/kmyacc.php.parser%  D wY%  <j      1   vendor/nikic/php-parser/grammar/rebuildParser.php~  D wY~  fnGö      9   vendor/nikic/php-parser/grammar/zend_language_parser.phpyL  D wYL  F      4   vendor/nikic/php-parser/lib/PhpParser/Autoloader.php?  D wY?  x      1   vendor/nikic/php-parser/lib/PhpParser/Builder.php   D wY   D\      8   vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php  D wY  P      =   vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php  D wY  3a      >   vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php  D wY  U޶      ;   vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php  D wY        <   vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php  D wY  #Զ      8   vendor/nikic/php-parser/lib/PhpParser/Builder/Method.phpU  D wYU  Ϧ      <   vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php  D wY  @/      7   vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php  D wY  7CEǶ      :   vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php	  D wY	  Y      8   vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php  D wY  o      6   vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php  D wY  -;      9   vendor/nikic/php-parser/lib/PhpParser/BuilderAbstract.phpM  D wYM  o#      8   vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.phpu  D wYu  }      1   vendor/nikic/php-parser/lib/PhpParser/Comment.php  D wY  t6      5   vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.phpM   D wYM   MӶ      /   vendor/nikic/php-parser/lib/PhpParser/Error.php  D wY  4sB      /   vendor/nikic/php-parser/lib/PhpParser/Lexer.php*  D wY*  eRX      9   vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php\#  D wY\#  \ֶ      .   vendor/nikic/php-parser/lib/PhpParser/Node.php  D wY  %      2   vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php  D wY  i      5   vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php  D wY  HsͶ      3   vendor/nikic/php-parser/lib/PhpParser/Node/Expr.phpk   D wYk   Pp@      A   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php  D wY  ޖ      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.phpn  D wYn  L      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php<  D wY<  yu      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php  D wY        <   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php	  D wY	  u      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.phpx   D wYx   *      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.phpw   D wYw   !      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.phpx   D wYx   u0F      C   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.phpt   D wYt   &      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.phpq   D wYq   xEֶ      B   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.phps   D wYs         @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.phpq   D wYq   ZI       @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.phpq   D wYq   y      A   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.phpr   D wYr   [Wr      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.phpq   D wYq   :      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.phpw   D wYw   !      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.phpx   D wYx   2ֶ      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.phpE  D wYE  ٪      <   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php  D wY        G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.phpx   D wYx   H遶      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.phpw   D wYw   p׶      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.phpx   D wYx         G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.phpx   D wYx         F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.phpw   D wYw   @      E   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.phpw   D wYw   W|]      C   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.phpt   D wYt   G~      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.phpq   D wYq   p       B   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.phps   D wYs   V      D   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.phpu   D wYu   >m      K   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php|   D wY|   ق      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.phpw   D wYw   2      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.phpx   D wYx   J@      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.phpw   D wYw   <U      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.phpx   D wYx   L|,      B   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.phps   D wYs   ҆      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.phpq   D wYq   59      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.phpq   D wYq   2+l      E   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.phpv   D wYv   <      I   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpz   D wYz   C::      A   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.phpr   D wYr   p      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.phpq   D wYq   c]      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.phpw   D wYw   I
      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.phpx   D wYx   }      D   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.phpu   D wYu   ˶      K   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php|   D wY|   Lwu      F   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.phpx   D wYx   (|D      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php  D wY  9߶      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php,  D wY,        8   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php  D wY  %W      ?   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.phph   D wYh         >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.phph   D wYh   8g      ?   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.phpi   D wYi   ۯڶ      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.phpg   D wYg   6      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.phpj   D wYj   >JW      @   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.phpj   D wYj   j*      ?   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.phph   D wYh   b!      C   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php  D wY  gɶ      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php  D wY  Zq      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php  D wY  l      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php  D wY  b      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php>  D wY>  p      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php  D wY  LͶ      A   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php&  D wY&  #H/¶      9   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php  D wY  (ޱ      9   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php9  D wY9  #+"      <   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php  D wY  |(n      <   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php1  D wY1  w      ?   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php  D wY  ]      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php  D wY  ]PU      9   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.phpP  D wYP  H#      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php  D wY        8   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php[  D wY[  uF      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php  D wY  W      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php  D wY  jȶ      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php  D wY  j      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php  D wY  zp      :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php  D wY  Y      A   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php  D wY  t      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.phpE  D wYE        >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php  D wY  /      G   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php  D wY   K      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.phpv  D wYv  "k^      >   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php  D wY  8      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php*  D wY*  *v      <   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php+  D wY+  #      =   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php.  D wY.        :   vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php  D wY        ;   vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php>  D wY>  wA      3   vendor/nikic/php-parser/lib/PhpParser/Node/Name.php  D wY  1Ѷ      B   vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php  D wY  ĝi      <   vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php  D wY  w      4   vendor/nikic/php-parser/lib/PhpParser/Node/Param.php  D wY  Z      5   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.phpH   D wYH   /      =   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php  D wY  vy%      >   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php;  D wY;  )@      =   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php  D wY  wMG      @   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php4  D wY4  o      G   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php   D wY   -(+<      D   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php   D wY   }u      E   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php   D wY   JV      J   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php   D wY   B       E   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php   D wY   M      G   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php   D wY         K   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php   D wY   "8F      G   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php   D wY         =   vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php  D wY  >7      3   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.phpk   D wYk   D      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.phpT  D wYT        9   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php  D wY  0Ӷ      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php}  D wY}        >   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php^  D wY^  d      =   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.phpu  D wYu  Wڳ      ?   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.phpe  D wYe  ͻ      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php.  D wY.  PD      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.phpT  D wYT  kѶ      =   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php`  D wY`  OqI      B   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php  D wY  o      <   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php  D wY  5ֶ      7   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php  D wY  s      9   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php/  D wY/  '6ܶ      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php  D wY  7      9   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php(  D wY(  r      8   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.phpG  D wYG  |C      <   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.phpM  D wYM  EI      =   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php~  D wY~  +,J      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.phpA  D wYA  I'D      9   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php.  D wY.  ԫ      @   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php  D wY  Y      7   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php#  D wY#  8ڶ      >   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php  D wY  ZK      >   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php}  D wY}  ZP      9   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php  D wY  jи      >   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.phpz  D wYz  Y>      <   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php  D wY  5      D   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php  D wY  1      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php?  D wY?        =   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php  D wY  X;      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.phpN  D wYN  8      ;   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php  D wY  (H-      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php'  D wY'  ΰ8ֶ      <   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php$  D wY$  ˗      F   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php   D wY   ݄嘶      L   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php  D wY  	hH      Q   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.phpk  D wYk  &6'      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.phpE  D wYE   '      <   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.phpt  D wYt  G      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php:  D wY:        :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.phpZ  D wYZ  Xö      8   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php!  D wY!  fg      :   vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php  D wY  ?      6   vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php  D wY  pP      4   vendor/nikic/php-parser/lib/PhpParser/NodeDumper.phpt  D wYt  h{      7   vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.phpa  D wYa  >O      @   vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php  D wY  j      5   vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php  D wY  %(      B   vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php*  D wY*  읶      =   vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php<  D wY<  !S(̶      0   vendor/nikic/php-parser/lib/PhpParser/Parser.php8 D wY8 BZl      8   vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.phpK  D wYK  &/      @   vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.phpFp  D wYFp        ?   vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php(  D wY(  -      4   vendor/nikic/php-parser/lib/PhpParser/Serializer.php  D wY  {      8   vendor/nikic/php-parser/lib/PhpParser/Serializer/XML.phpC  D wYC  yʲS      6   vendor/nikic/php-parser/lib/PhpParser/Unserializer.php  D wY  v߶      :   vendor/nikic/php-parser/lib/PhpParser/Unserializer/XML.php8  D wY8  }Ϳ      )   vendor/nikic/php-parser/lib/bootstrap.php   D wY   ]      (   vendor/nikic/php-parser/phpunit.xml.dist  D wY  +	n      9   vendor/nikic/php-parser/test/PhpParser/AutoloaderTest.php  D wY        <   vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php\  D wY\  &`      ?   vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php	  D wY	  q2n      @   vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php  D wY        =   vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.phpS  D wYS  4_0J      @   vendor/nikic/php-parser/test/PhpParser/Builder/NamespaceTest.php  D wY        <   vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php  D wY  뽶      ?   vendor/nikic/php-parser/test/PhpParser/Builder/PropertyTest.phpv  D wYv  A      <   vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.phpa  D wYa  \a̶      :   vendor/nikic/php-parser/test/PhpParser/Builder/UseTest.php  D wY  ˶      =   vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php  D wY   ض      ;   vendor/nikic/php-parser/test/PhpParser/CodeTestAbstract.php  D wY  <JM      6   vendor/nikic/php-parser/test/PhpParser/CommentTest.php   D wY   6      4   vendor/nikic/php-parser/test/PhpParser/ErrorTest.php[  D wY[  8J      >   vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php  D wY  wK`       4   vendor/nikic/php-parser/test/PhpParser/LexerTest.php"  D wY"  h      8   vendor/nikic/php-parser/test/PhpParser/Node/NameTest.php}  D wY}        E   vendor/nikic/php-parser/test/PhpParser/Node/Scalar/MagicConstTest.php-  D wY-  l      A   vendor/nikic/php-parser/test/PhpParser/Node/Scalar/StringTest.phpe  D wYe  )      D   vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassMethodTest.php  D wY  5ζ      >   vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassTest.php  D wY   8W      B   vendor/nikic/php-parser/test/PhpParser/Node/Stmt/InterfaceTest.php'  D wY'  WA.
      A   vendor/nikic/php-parser/test/PhpParser/Node/Stmt/PropertyTest.php  D wY  9      ;   vendor/nikic/php-parser/test/PhpParser/NodeAbstractTest.php  D wY  %(      9   vendor/nikic/php-parser/test/PhpParser/NodeDumperTest.php	  D wY	  p      <   vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php   D wY   "TO      G   vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.phpP'  D wYP'  L      5   vendor/nikic/php-parser/test/PhpParser/ParserTest.php  D wY  r      <   vendor/nikic/php-parser/test/PhpParser/PrettyPrinterTest.php  D wY  Q%      =   vendor/nikic/php-parser/test/PhpParser/Serializer/XMLTest.phpN  D wYN   Iݶ      ?   vendor/nikic/php-parser/test/PhpParser/Unserializer/XMLTest.phpY  D wYY        D   vendor/nikic/php-parser/test/code/parser/errorHandling/eofError.test   D wY   EbM      D   vendor/nikic/php-parser/test/code/parser/errorHandling/recovery.test/  D wY/  /'      ;   vendor/nikic/php-parser/test/code/parser/expr/arrayDef.test  D wY  4k      9   vendor/nikic/php-parser/test/code/parser/expr/assign.test[  D wY[  TQg      7   vendor/nikic/php-parser/test/code/parser/expr/cast.test  D wY  +:      8   vendor/nikic/php-parser/test/code/parser/expr/clone.test   D wY   ȵgD      :   vendor/nikic/php-parser/test/code/parser/expr/closure.test  D wY  `|^      =   vendor/nikic/php-parser/test/code/parser/expr/comparison.test  D wY        @   vendor/nikic/php-parser/test/code/parser/expr/constant_expr.test@  D wY@  셶      @   vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test   D wY   Ɩ
      7   vendor/nikic/php-parser/test/code/parser/expr/exit.test  D wY  f      D   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/args.testd  D wYd  kB      J   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/constFetch.test  D wY  ?      M   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/constantDeref.testX  D wYX  ʑ      H   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/funcCall.test>  D wY>  :#      H   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test  D wY  C      L   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.testT  D wYT  64      Q   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.testK  D wYK  (A      J   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/staticCall.test
  D wY
  :_Ͷ      S   vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/staticPropertyFetch.test  D wY  '#̶      A   vendor/nikic/php-parser/test/code/parser/expr/includeAndEval.test  D wY  ڇ      @   vendor/nikic/php-parser/test/code/parser/expr/issetAndEmpty.test  D wY  -hU      8   vendor/nikic/php-parser/test/code/parser/expr/logic.testq
  D wYq
  M      7   vendor/nikic/php-parser/test/code/parser/expr/math.test4  D wY4  ƿ       6   vendor/nikic/php-parser/test/code/parser/expr/new.test!
  D wY!
  ^W&      B   vendor/nikic/php-parser/test/code/parser/expr/newWithoutClass.testa   D wYa   ۶      8   vendor/nikic/php-parser/test/code/parser/expr/print.test~   D wY~   /3      <   vendor/nikic/php-parser/test/code/parser/expr/shellExec.test!  D wY!  y       E   vendor/nikic/php-parser/test/code/parser/expr/ternaryAndCoalesce.test	  D wY	  rǶ      ;   vendor/nikic/php-parser/test/code/parser/expr/variable.testm  D wYm  DQ      C   vendor/nikic/php-parser/test/code/parser/scalar/constantString.test,  D wY,  zS      >   vendor/nikic/php-parser/test/code/parser/scalar/docString.test  D wY  _t      C   vendor/nikic/php-parser/test/code/parser/scalar/encapsedString.test%  D wY%  I      :   vendor/nikic/php-parser/test/code/parser/scalar/float.test  D wY  e{ö      8   vendor/nikic/php-parser/test/code/parser/scalar/int.test  D wY  Ͷ      ?   vendor/nikic/php-parser/test/code/parser/scalar/magicConst.test  D wY  z	      E   vendor/nikic/php-parser/test/code/parser/stmt/blocklessStatement.test  D wY  (8      A   vendor/nikic/php-parser/test/code/parser/stmt/class/abstract.test  D wY  mi+      B   vendor/nikic/php-parser/test/code/parser/stmt/class/anonymous.test  D wY  ]N      D   vendor/nikic/php-parser/test/code/parser/stmt/class/conditional.test7  D wY7  O2ն      >   vendor/nikic/php-parser/test/code/parser/stmt/class/final.test   D wY   i      G   vendor/nikic/php-parser/test/code/parser/stmt/class/implicitPublic.testp  D wYp        B   vendor/nikic/php-parser/test/code/parser/stmt/class/interface.test  D wY  棶      A   vendor/nikic/php-parser/test/code/parser/stmt/class/modifier.test  D wY  ΜJ7      =   vendor/nikic/php-parser/test/code/parser/stmt/class/name.test  D wY  `w      B   vendor/nikic/php-parser/test/code/parser/stmt/class/php4Style.test   D wY   Nv      ?   vendor/nikic/php-parser/test/code/parser/stmt/class/simple.testE  D wYE  4[fq      E   vendor/nikic/php-parser/test/code/parser/stmt/class/staticMethod.test  D wY  IjA      >   vendor/nikic/php-parser/test/code/parser/stmt/class/trait.testQ  D wYQ  趶      8   vendor/nikic/php-parser/test/code/parser/stmt/const.testN  D wYN  +޶      >   vendor/nikic/php-parser/test/code/parser/stmt/controlFlow.test  D wY  z      :   vendor/nikic/php-parser/test/code/parser/stmt/declare.test  D wY  >~      7   vendor/nikic/php-parser/test/code/parser/stmt/echo.test0  D wY0  g*      A   vendor/nikic/php-parser/test/code/parser/stmt/function/byRef.test  D wY  X[      G   vendor/nikic/php-parser/test/code/parser/stmt/function/conditional.testG  D wYG  sx      I   vendor/nikic/php-parser/test/code/parser/stmt/function/defaultValues.testV  D wYV  N      E   vendor/nikic/php-parser/test/code/parser/stmt/function/generator.test  D wY  }Զ      G   vendor/nikic/php-parser/test/code/parser/stmt/function/returnTypes.test  D wY  h      G   vendor/nikic/php-parser/test/code/parser/stmt/function/specialVars.test  D wY  9v      E   vendor/nikic/php-parser/test/code/parser/stmt/function/typeHints.test&  D wY&  LJ      D   vendor/nikic/php-parser/test/code/parser/stmt/function/variadic.test	  D wY	  K\      P   vendor/nikic/php-parser/test/code/parser/stmt/function/variadicDefaultValue.test   D wY   	      ?   vendor/nikic/php-parser/test/code/parser/stmt/haltCompiler.test  D wY  V<      L   vendor/nikic/php-parser/test/code/parser/stmt/haltCompilerInvalidSyntax.testy   D wYy         E   vendor/nikic/php-parser/test/code/parser/stmt/haltCompilerOffset.test  D wY  oݶ      M   vendor/nikic/php-parser/test/code/parser/stmt/haltCompilerOutermostScope.test   D wY   4f      ;   vendor/nikic/php-parser/test/code/parser/stmt/hashbang.testf  D wYf  *=.      5   vendor/nikic/php-parser/test/code/parser/stmt/if.test  D wY  aҶ      =   vendor/nikic/php-parser/test/code/parser/stmt/inlineHTML.test   D wY   Iy      :   vendor/nikic/php-parser/test/code/parser/stmt/loop/do.test   D wY   NG0      ;   vendor/nikic/php-parser/test/code/parser/stmt/loop/for.test  D wY  xŝҶ      ?   vendor/nikic/php-parser/test/code/parser/stmt/loop/foreach.testO
  D wYO
  W      =   vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test3  D wY3  1Lf5      B   vendor/nikic/php-parser/test/code/parser/stmt/namespace/alias.test#  D wY#  h      C   vendor/nikic/php-parser/test/code/parser/stmt/namespace/braced.test  D wY  7
      H   vendor/nikic/php-parser/test/code/parser/stmt/namespace/invalidName.test  D wY  '(Ҷ      @   vendor/nikic/php-parser/test/code/parser/stmt/namespace/mix.test7  D wY7  o      A   vendor/nikic/php-parser/test/code/parser/stmt/namespace/name.test  D wY  Kܶ      C   vendor/nikic/php-parser/test/code/parser/stmt/namespace/nested.test   D wY   ķ      F   vendor/nikic/php-parser/test/code/parser/stmt/namespace/notBraced.test  D wY  >)      H   vendor/nikic/php-parser/test/code/parser/stmt/namespace/outsideStmt.testO  D wYO  :      O   vendor/nikic/php-parser/test/code/parser/stmt/namespace/outsideStmtInvalid.test  D wY  L%      9   vendor/nikic/php-parser/test/code/parser/stmt/switch.testi  D wYi  ۧ}      ;   vendor/nikic/php-parser/test/code/parser/stmt/tryCatch.testa	  D wYa	  Զ      B   vendor/nikic/php-parser/test/code/parser/stmt/tryWithoutCatch.testt   D wYt          8   vendor/nikic/php-parser/test/code/parser/stmt/unset.testw  D wYw  f      :   vendor/nikic/php-parser/test/code/prettyPrinter/alias.test9  D wY9  B
      C   vendor/nikic/php-parser/test/code/prettyPrinter/anonymousClass.testw  D wYw  ni      9   vendor/nikic/php-parser/test/code/prettyPrinter/call.testp   D wYp   #lX      :   vendor/nikic/php-parser/test/code/prettyPrinter/class.test:  D wY:  Rol      <   vendor/nikic/php-parser/test/code/prettyPrinter/closure.test  D wY  eu      =   vendor/nikic/php-parser/test/code/prettyPrinter/comments.test&  D wY&  ՘ݶ      H   vendor/nikic/php-parser/test/code/prettyPrinter/function_signatures.test  D wY  r̶      <   vendor/nikic/php-parser/test/code/prettyPrinter/include.test^   D wY^   |      N   vendor/nikic/php-parser/test/code/prettyPrinter/inlineHTMLandPHPtest.file-testk  D wYk  <      9   vendor/nikic/php-parser/test/code/prettyPrinter/list.testH  D wYH        =   vendor/nikic/php-parser/test/code/prettyPrinter/literals.test  D wY  :Z      ?   vendor/nikic/php-parser/test/code/prettyPrinter/namespaces.testx  D wYx  0E]      <   vendor/nikic/php-parser/test/code/prettyPrinter/numbers.test   D wY          H   vendor/nikic/php-parser/test/code/prettyPrinter/onlyInlineHTML.file-test|   D wY|   e      A   vendor/nikic/php-parser/test/code/prettyPrinter/onlyPHP.file-testr   D wYr   P      >   vendor/nikic/php-parser/test/code/prettyPrinter/operators.test`  D wY`  ss      @   vendor/nikic/php-parser/test/code/prettyPrinter/parentheses.test  D wY  l∶      ;   vendor/nikic/php-parser/test/code/prettyPrinter/switch.test  D wY  f      =   vendor/nikic/php-parser/test/code/prettyPrinter/traitUse.testQ  D wYQ  S+B      >   vendor/nikic/php-parser/test/code/prettyPrinter/variables.test  D wY        (   vendor/nikic/php-parser/test_old/run.php  D wY  T N      =   vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md  D wY  .)B      Q   vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php  D wY  <      W   vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php  D wY  ˅      M   vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php  D wY  W      U   vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php  D wY  _      O   vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php  D wY  >H      E   vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php
  D wY
  !,$      8   vendor/symfony/process/Symfony/Component/Process/LICENSE)  D wY)  NmQ      H   vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php  D wY  mp      ?   vendor/symfony/process/Symfony/Component/Process/PhpProcess.php  D wY  @s      <   vendor/symfony/process/Symfony/Component/Process/Process.php  D wY  Z/%      C   vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php  D wY  9%      A   vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php  D wY  Rڶ      :   vendor/symfony/process/Symfony/Component/Process/README.mdP  D wYP  u˶      N   vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.phpE  D wYE  L      M   vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php*  D wY*  .D϶      R   vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.phpM  D wYM  D      I   vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php  D wY  x[      M   vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.phpf  D wYf  3      U   vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php
  D wY
  WKo      W   vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php  D wY  m      L   vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php  D wY  K      K   vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php(  D wY(  F(m      V   vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php8  D wY8  HdɎ      U   vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php\  D wY\  t,ʶ      I   vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php   D wY   >'ض      L   vendor/symfony/process/Symfony/Component/Process/Tests/SimpleProcessTest.php  D wY  "{      >   vendor/symfony/process/Symfony/Component/Process/composer.json  D wY  -4      A   vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist  D wY  p\H      J   vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php  D wY  	      N   vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php)  D wY)  QJ      K   vendor/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php  D wY        I   vendor/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
  D wY
  kA      I   vendor/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
  D wY
  }]      E   vendor/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php{
  D wY{
  9!2      ;   vendor/symfony/finder/Symfony/Component/Finder/CHANGELOG.md  D wY  @"S      H   vendor/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php
  D wY
  |Fz      L   vendor/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php  D wY         N   vendor/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php
  D wY
  P      R   vendor/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php  D wY  cW޶      T   vendor/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php  D wY  >@      O   vendor/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php  D wY  7      Z   vendor/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php  D wY  P      Y   vendor/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php9  D wY9        H   vendor/symfony/finder/Symfony/Component/Finder/Expression/Expression.php$
  D wY$
        B   vendor/symfony/finder/Symfony/Component/Finder/Expression/Glob.php  D wY  E      C   vendor/symfony/finder/Symfony/Component/Finder/Expression/Regex.php  D wY  [ǐ      L   vendor/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.phpH  D wYH  "      9   vendor/symfony/finder/Symfony/Component/Finder/Finder.phpV  D wYV        7   vendor/symfony/finder/Symfony/Component/Finder/Glob.phpb  D wYb  g@M      P   vendor/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php  D wY  z"      S   vendor/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php  D wY   U      T   vendor/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php  D wY  ils_      Z   vendor/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php  D wY  ߶      M   vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php
  D wY
  ɔ      R   vendor/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.phpb  D wYb  n       U   vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php  D wY  ʽ      R   vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php  D wY  {^Ƕ      J   vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php  D wY  ,G      V   vendor/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php*  D wY*  n      N   vendor/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php  D wY  )      V   vendor/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php  D wY  6s      S   vendor/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php  D wY  xP      L   vendor/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php	  D wY	  j      6   vendor/symfony/finder/Symfony/Component/Finder/LICENSE)  D wY)  NmQ      8   vendor/symfony/finder/Symfony/Component/Finder/README.md  D wY  "Z      @   vendor/symfony/finder/Symfony/Component/Finder/Shell/Command.php  D wY  lJ30      >   vendor/symfony/finder/Symfony/Component/Finder/Shell/Shell.php  D wY  2      >   vendor/symfony/finder/Symfony/Component/Finder/SplFileInfo.php  D wY  s<T      R   vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php  D wY  Ŷ      V   vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php	  D wY	  ˟׶      X   vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php/  D wY/  W.      R   vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php  D wY        L   vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.phpi  D wYi  "F      M   vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php  D wY         Q   vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php  D wY  I޶      S   vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.phpm  D wYm        Q   vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php  D wY  `2      W   vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php0  D wY0  /      C   vendor/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.phpw  D wYw  ?5      K   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat    D wY              H   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat    D wY              E   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/a.dat    D wY              U   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy    D wY              R   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy    D wY              O   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy    D wY              G   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt   D wY   o5|      G   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt)   D wY)   '      G   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt5   D wY5         C   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/a    D wY              J   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/c.neon    D wY              J   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/d.neon    D wY              P   vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/with space/foo.txt    D wY              Z   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php  D wY  ߫Ӷ      ]   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php=  D wY=  (g      ^   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php)  D wY)  Zo*      d   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.phpa  D wYa  4/      W   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php
  D wY
  :@      \   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.phpM  D wYM  Ybk      _   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php  D wY  ٶ      \   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php  D wY  7      T   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php6  D wY6  -ڶ      J   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php(  D wY(  $e#      R   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.phpo	  D wYo	  {)\      V   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php&  D wY&  u8      Q   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php  D wY  o      `   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php+  D wY+  캶      X   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php  D wY  nI      V   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php5  D wY5  fb      `   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php  D wY  fT      ]   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php   D wY   F      V   vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.phpa	  D wYa	  7W      <   vendor/symfony/finder/Symfony/Component/Finder/composer.json  D wY  4j      ?   vendor/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist5  D wY5        @   vendor/symfony/console/Symfony/Component/Console/Application.phpЊ  D wYЊ  &      =   vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md  D wY  q]      D   vendor/symfony/console/Symfony/Component/Console/Command/Command.php>  D wY>  u      H   vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php
  D wY
  #      H   vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.phpV
  D wYV
  7Ȕ      B   vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php  D wY  \0E      V   vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php  D wY  sX      J   vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php>  D wY>  {      S   vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php|  D wY|        N   vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php/  D wY/  |,      R   vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php  D wY  l      N   vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php  D wY  _F\      M   vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php!  D wY!  (      N   vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.phpZ  D wYZ  C      G   vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php  D wY  
      P   vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php;  D wY;  4֍      P   vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php   D wY   (      N   vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php  D wY  l      W   vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php  D wY  Jp˶      S   vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php  D wY  Ʃ      \   vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php  D wY  5      X   vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php
  D wY
  dB      L   vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php
  D wY
  GЇ      H   vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php?  D wY?  _E      K   vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php  D wY  gse      B   vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php  D wY  Z\      K   vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php  D wY  z׶      E   vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.phpv	  D wYv	        J   vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php-  D wY-  #e!Q      G   vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php(  D wY(  wkM      D   vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.phpq(  D wYq(  p)      E   vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php%  D wY%  p      @   vendor/symfony/console/Symfony/Component/Console/Input/Input.php  D wY  (Q/      H   vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php  D wY  '      J   vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.phpN.  D wYN.  +f      I   vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php&  D wY&  o3޶      F   vendor/symfony/console/Symfony/Component/Console/Input/InputOption.phpV  D wYV  Oö      F   vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php
  D wY
  ΂=      8   vendor/symfony/console/Symfony/Component/Console/LICENSE)  D wY)  NmQ      I   vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php  D wY  e      R   vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php  D wY  :2p      F   vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php  D wY  F$_      B   vendor/symfony/console/Symfony/Component/Console/Output/Output.php  D wY  D{r      K   vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.phpM  D wYM  z      H   vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.phpM  D wYM  Xz      :   vendor/symfony/console/Symfony/Component/Console/README.md  D wY  ~b      N   vendor/symfony/console/Symfony/Component/Console/Resources/bin/hiddeninput.exe $  D wY $  v      :   vendor/symfony/console/Symfony/Component/Console/Shell.phpF  D wYF  _      M   vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.phpY  D wYY  A$ܶ      I   vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php  D wY  ֿ      J   vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php  D wY  ip      N   vendor/symfony/console/Symfony/Component/Console/Tests/Command/CommandTest.phpZ9  D wYZ9  o"      R   vendor/symfony/console/Symfony/Component/Console/Tests/Command/HelpCommandTest.php2  D wY2        R   vendor/symfony/console/Symfony/Component/Console/Tests/Command/ListCommandTest.phpC	  D wYC	  ˶      \   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/AbstractDescriptorTest.php:  D wY:        X   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/JsonDescriptorTest.php:  D wY:  oW      \   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/MarkdownDescriptorTest.phpD  D wYD  1HD      U   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.phpv  D wYv  ۶      X   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/TextDescriptorTest.php9  D wY9  K       W   vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/XmlDescriptorTest.php6  D wY6  ₶      Z   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication1.php  D wY  q+      Z   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication2.phpM  D wYM  1"8      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand1.php  D wY  Y'A      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand2.php?  D wY?  XzԶ      O   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo1Command.phpU  D wYU  <|4      O   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo2Command.php  D wY  	      O   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo3Command.phpm  D wYm        O   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo4Command.php   D wY   ed      N   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/FooCommand.php   D wY   c      O   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/TestCommand.php  D wY  9*A      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.json  D wY  0      P   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.md  D wY  F3      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.txt6  D wY6  a      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.xml4  D wY4  P      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.json  D wY  m      P   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.md  D wY  76      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.txt  D wY  (      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.xml!  D wY!  4~      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext1.txt  D wY  55      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext2.txt#  D wY#  (K      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt  D wY  ¶      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml2.txt  D wY  W'      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_gethelp.txt  D wY  #ɤb      `   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt   D wY   &      `   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt   D wY   ,Ǆ      `   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt   D wY   	緶      `   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt   D wY   Xl      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run1.txt:  D wY:  e      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run2.txt  D wY  6u      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run3.txt  D wY  U      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run4.txt   D wY   8UT      N   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.json   D wY   [%      L   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.md   D wY   9yX      M   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.txt   D wY   '̶      M   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.xmlO  D wYO  E      N   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.json  D wY  #"      L   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.md  D wY  U϶      M   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.txt   D wY   h      M   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.xmlf  D wYf  A(      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_astext.txt  D wY  ׶      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_asxml.txt  D wY  S      U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_astext.txt  D wY  ,ݶ      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_asxml.txt  D wY  ia      U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.json]   D wY]   <      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.mdt   D wYt         T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.txt   D wY   (?h      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.xml   D wY   _n      U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.jsono   D wYo         S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.md   D wY   zՌ      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.txt1   D wY1   g/      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.xml   D wY   f      U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.json}   D wY}   󚲱      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.md   D wY   Wض      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.txt_   D wY_   NY      T   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.xml   D wY   : 8      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.json   D wY         U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.md    D wY              V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.txt    D wY              V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.xml^   D wY^   ^tݶ      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.json   D wY         U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.md   D wY   [      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.txt;   D wY;   Ѹ      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.xml   D wY   3P߶      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.json   D wY   *q      U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.md   D wY   p      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.txt=   D wY=         V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.xml  D wY  F:c      W   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.json$  D wY$        U   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.md4  D wY4  *[Ǿ      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.txt~   D wY~   .p      V   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.xml  D wY  CQu      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.json   D wY   X      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.md   D wY   Y-Ͷ      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.txt!   D wY!   u$´      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.xml   D wY   G      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.json   D wY   ۴2      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.md   D wY   	Ӷ      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.txtb   D wYb   K@      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.xml  D wY  J2      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.json   D wY   I      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.md   D wY   c      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.txt4   D wY4   x      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.xml   D wY   XX      S   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.json   D wY    a      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.md   D wY   P      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.txta   D wYa   t;      R   vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.xml   D wY   I ƶ      b   vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleStackTest.phpv  D wYv  ¶      ]   vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleTest.phpw  D wYw  rH      X   vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php  D wY  M      R   vendor/symfony/console/Symfony/Component/Console/Tests/Helper/DialogHelperTest.phpT!  D wYT!  uB[      U   vendor/symfony/console/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php
  D wY
  gO      O   vendor/symfony/console/Symfony/Component/Console/Tests/Helper/HelperSetTest.phpV  D wYV  йnY      T   vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php  D wY  R      Q   vendor/symfony/console/Symfony/Component/Console/Tests/Helper/TableHelperTest.php{  D wY{  -Sn      N   vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArgvInputTest.php7  D wY7  )U      O   vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArrayInputTest.php<  D wY<  1E      R   vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputArgumentTest.php  D wY  F5      T   vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputDefinitionTest.phpH  D wYH  @      P   vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputOptionTest.php#  D wY#  KUö      J   vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputTest.php  D wY  p      P   vendor/symfony/console/Symfony/Component/Console/Tests/Input/StringInputTest.php  D wY        S   vendor/symfony/console/Symfony/Component/Console/Tests/Output/ConsoleOutputTest.php  D wY  }R      P   vendor/symfony/console/Symfony/Component/Console/Tests/Output/NullOutputTest.php  D wY  ;!      L   vendor/symfony/console/Symfony/Component/Console/Tests/Output/OutputTest.php  D wY  w}Xa      R   vendor/symfony/console/Symfony/Component/Console/Tests/Output/StreamOutputTest.php  D wY        W   vendor/symfony/console/Symfony/Component/Console/Tests/Tester/ApplicationTesterTest.php  D wY        S   vendor/symfony/console/Symfony/Component/Console/Tests/Tester/CommandTesterTest.php  D wY  n}      >   vendor/symfony/console/Symfony/Component/Console/composer.jsone  D wYe  v      A   vendor/symfony/console/Symfony/Component/Console/phpunit.xml.disti  D wYi  ?UH         vendor/bin/validate-json	  D wY	  ,Z         vendor/bin/jsonlintv  D wYv  "Q         vendor/bin/box  D wY  FkO         vendor/autoload.php   D wY   B1         src/AbstractBaseMessage.php  D wY  %Ќ1         src/CLIOutputInterface.php   D wY   6<         src/CLIResultPrinter.php  D wY  ƶ      .   src/CodePreprocessor/PreprocessorInterface.php   D wY   ן`      '   src/CodePreprocessor/ShebangRemover.php  D wY  
      .   src/CompatibilityViolation/AbstractContext.php  D wY  T      ,   src/CompatibilityViolation/CheckMetadata.php  D wY  hf      /   src/CompatibilityViolation/ContextInterface.php  D wY        *   src/CompatibilityViolation/FileContext.php  D wY        &   src/CompatibilityViolation/Message.phpL  D wYL        ,   src/CompatibilityViolation/StringContext.php  D wY  uyŶ         src/ContextChecker.php$  D wY$  y         src/Error/CheckError.php  D wY  ѷ~      !   src/ExcludedPathCanonicalizer.php  D wY  !Ź         src/Helper/OSDetector.php   D wY   /      %   src/Helper/Path/PathHelperFactory.php  D wY  VV Ӷ      '   src/Helper/Path/PathHelperInterface.php4  D wY4  Ƕ      "   src/Helper/Path/UnixPathHelper.phpr  D wYr  y      %   src/Helper/Path/WindowsPathHelper.php  D wY  ho2         src/Helper/RegExp/RegExp.phpD
  D wYD
  3      "   src/Helper/RegExp/RegExpParser.php  D wY  K      "   src/Infrastructure/Application.php  D wY  T      &   src/Infrastructure/CLIOutputBridge.php  D wY  /      '   src/Infrastructure/ContainerBuilder.phpR/  D wYR/  =Y      $   src/Infrastructure/PHP7CCCommand.php  D wY  e9|      0   src/Iterator/AbstractRecursiveFilterIterator.php/  D wY/  bkMy      7   src/Iterator/ExcludedPathFilteringRecursiveIterator.phpO  D wYO  +s]      4   src/Iterator/ExtensionFilteringRecursiveIterator.php  D wY  @      3   src/Iterator/FileDirectoryListRecursiveIterator.phpo  D wYo           src/JsonResultPrinter.phpD  D wYD  5         src/Lexer/ExtendedLexer.php  D wY  0      %   src/NodeAnalyzer/FunctionAnalyzer.php6  D wY6  [ ;         src/NodeStatementsRemover.php  D wY  "k         src/NodeTraverser/Traverser.php?  D wY?  mѶ      -   src/NodeVisitor/AbstractNestedLoopVisitor.php  D wY  aP8      .   src/NodeVisitor/AbstractNewFunctionVisitor.php  D wY  D%      #   src/NodeVisitor/AbstractVisitor.php  D wY  a9k      B   src/NodeVisitor/ArrayOrObjectValueAssignmentByReferenceVisitor.phpM  D wYM  (      '   src/NodeVisitor/BitwiseShiftVisitor.php  D wY  v      3   src/NodeVisitor/ContinueBreakOutsideLoopVisitor.php?  D wY?  ݐ      /   src/NodeVisitor/DivisionModuloByZeroVisitor.php  D wY  IU      5   src/NodeVisitor/DuplicateFunctionParameterVisitor.php  D wY  0EЁ      2   src/NodeVisitor/EscapedUnicodeCodepointVisitor.php  D wY  (t      "   src/NodeVisitor/ForeachVisitor.php  D wY  ѭ޶      &   src/NodeVisitor/FuncGetArgsVisitor.php  D wY        ,   src/NodeVisitor/GlobalNewFunctionVisitor.php`  D wY`  b      1   src/NodeVisitor/GlobalVariableVariableVisitor.php<  D wY<  >
      *   src/NodeVisitor/HTTPRawPostDataVisitor.php  D wY  @϶      2   src/NodeVisitor/HexadecimalNumberStringVisitor.phph  D wYh  -;L      9   src/NodeVisitor/IndirectVariableOrMethodAccessVisitor.php<  D wY<        .   src/NodeVisitor/InvalidOctalLiteralVisitor.php  D wY  0         src/NodeVisitor/ListVisitor.phpB  D wYB  :d      !   src/NodeVisitor/MktimeVisitor.php  D wY  دq      1   src/NodeVisitor/MultipleSwitchDefaultsVisitor.php   D wY   
iƶ      0   src/NodeVisitor/NamespacedNewFunctionVisitor.php  D wY  
*      3   src/NodeVisitor/NewAssignmentByReferenceVisitor.php(  D wY(  <#w      #   src/NodeVisitor/NewClassVisitor.php0  D wY0  As      *   src/NodeVisitor/PHP4ConstructorVisitor.phpg  D wYg   i      +   src/NodeVisitor/PasswordHashSaltVisitor.php?  D wY?  ;      *   src/NodeVisitor/PregReplaceEvalVisitor.php6  D wY6  ?]      .   src/NodeVisitor/RemovedFunctionCallVisitor.php  D wY  R>Ŷ      ,   src/NodeVisitor/ReservedClassNameVisitor.php
  D wY
  A         src/NodeVisitor/Resolver.php  D wY  7      %   src/NodeVisitor/ResolverInterface.php   D wY   Aξ      0   src/NodeVisitor/SessionSetSaveHandlerVisitor.php  D wY  B      -   src/NodeVisitor/SetcookieEmptyNameVisitor.php  D wY  ;
=      $   src/NodeVisitor/VisitorInterface.php+  D wY+  ()      *   src/NodeVisitor/YieldExpressionVisitor.phpv  D wYv  rf      3   src/NodeVisitor/YieldInExpressionContextVisitor.php7  D wY7  R         src/PathCheckExecutor.php  D wY  I         src/PathCheckSettings.php  D wY  kÀ         src/PathChecker.php  D wY  тw         src/PathTraversableFactory.php  D wY           src/ResultPrinterInterface.php  D wY  ]d         src/Token/TokenCollection.phpl  D wYl  Ꞁi      
   bin/php7cc:   D wY:   yl         bin/php7cc.php  D wY  Miض         LICENSE3  D wY3  V      	   README.md  D wY  %]Զ      5   vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php  D wY        /   vendor/herrera-io/json/src/lib/json_version.php   D wY   r[nҶ      3   vendor/herrera-io/phar-update/src/lib/constants.php   D wY   L_      <?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Exception;

/**
 * Class ParsingError
 * @package Bcn\Component\Json\Exception
 */
class ParsingError extends \Exception
{
    /**
     * @param int    $line
     * @param int    $char
     * @param string $message
     */
    public function __construct($line, $char, $message)
    {
        parent::__construct("Parsing error in [$line:$char]: " . $message);
    }
}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Exception;

/**
 * Class ReadingError
 * @package Bcn\Component\Json\Exception
 */
class ReadingError extends \Exception
{

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Exception;

/**
 * Class WritingError
 * @package Bcn\Component\Json\Exception
 */
class WritingError extends \Exception
{

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json;

use Bcn\Component\Json\Parser\ListenerInterface;
use Bcn\Component\Json\Exception\ParsingError;

/**
 * Class Parser
 * @package Bcn\Component\Json
 */
class Parser
{
    const STATE_START_DOCUMENT     = 0;
    const STATE_DONE               = -1;
    const STATE_IN_ARRAY           = 1;
    const STATE_IN_OBJECT          = 2;
    const STATE_END_KEY            = 3;
    const STATE_AFTER_KEY          = 4;
    const STATE_IN_STRING          = 5;
    const STATE_START_ESCAPE       = 6;
    const STATE_UNICODE            = 7;
    const STATE_IN_NUMBER          = 8;
    const STATE_IN_TRUE            = 9;
    const STATE_IN_FALSE           = 10;
    const STATE_IN_NULL            = 11;
    const STATE_AFTER_VALUE        = 12;

    const STACK_OBJECT             = 0;
    const STACK_ARRAY              = 1;
    const STACK_KEY                = 2;
    const STACK_STRING             = 3;

    /** @var array */
    private $stack;
    /** @var int */
    private $state;
    /** @var resource */
    private $stream;
    /** @var Parser/Listener */
    private $listener;
    /** @var string */
    private $buffer;
    /** @var int */
    private $bufferSize;
    /** @var array */
    private $unicodeBuffer;
    /** @var int */
    private $unicodeHighCodePoint;
    /** @var string */
    private $lineEnding;
    /** @var integer */
    private $lineNumber;
    /** @var integer */
    private $charNumber;

    /**
     * @param  resource                  $stream
     * @param  ListenerInterface         $listener
     * @param  string                    $lineEnding
     * @throws \InvalidArgumentException
     */
    public function __construct($stream, ListenerInterface $listener, $lineEnding = null)
    {
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
            throw new \InvalidArgumentException("Argument is not a stream");
        }

        $this->stream = $stream;
        $this->listener = $listener;

        $this->state = self::STATE_START_DOCUMENT;
        $this->stack = array();

        $this->buffer = '';
        $this->bufferSize = 8192;
        $this->unicodeBuffer = array();
        $this->unicodeHighCodePoint = -1;
        $this->lineEnding = $lineEnding ?: "\n";;
    }

    /**
     * Parse stream content
     */
    public function parse()
    {
        $this->lineNumber = 1;
        $this->charNumber = 1;

        while (!feof($this->stream)) {
            $pos = ftell($this->stream);
            $line = stream_get_line($this->stream, $this->bufferSize, $this->lineEnding);
            $ended = (bool) (ftell($this->stream) - strlen($line) - $pos);

            $byteLen = strlen($line);
            for ($i = 0; $i < $byteLen; $i++) {
                $this->listener->file_position($this->lineNumber, $this->charNumber);
                $this->consumeChar($line[$i]);
                $this->charNumber++;
            }

            if ($ended) {
                $this->lineNumber++;
                $this->charNumber = 1;
            }

        }
    }

    /**
     * @param  string       $c
     * @throws ParsingError
     */
    private function consumeChar($c)
    {
        // valid whitespace characters in JSON (from RFC4627 for JSON) include:
        // space, horizontal tab, line feed or new line, and carriage return.
        // thanks: http://stackoverflow.com/questions/16042274/definition-of-whitespace-in-json
        if (($c === " " || $c === "\t" || $c === "\n" || $c === "\r") &&
            !($this->state === self::STATE_IN_STRING ||
                $this->state === self::STATE_UNICODE ||
                $this->state === self::STATE_START_ESCAPE ||
                $this->state === self::STATE_IN_NUMBER ||
                $this->state === self::STATE_START_DOCUMENT)) {
            return;
        }

        switch ($this->state) {

            case self::STATE_START_DOCUMENT:
                $this->listener->start_document();
                if ($c === '[') {
                    $this->startArray();
                } elseif ($c === '{') {
                    $this->startObject();
                } else {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Document must start with object or array.");
                }
                break;

            case self::STATE_IN_ARRAY:
                if ($c === ']') {
                    $this->endArray();
                } else {
                    $this->startValue($c);
                }
                break;

            case self::STATE_IN_OBJECT:
                if ($c === '}') {
                    $this->endObject();
                } elseif ($c === '"') {
                    $this->startKey();
                } else {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Start of string expected for object key. Instead got: ".$c);
                }
                break;

            case self::STATE_END_KEY:
                if ($c !== ':') {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Expected ':' after key.");
                }
                $this->state = self::STATE_AFTER_KEY;
                break;

            case self::STATE_AFTER_KEY:
                $this->startValue($c);
                break;

            case self::STATE_IN_STRING:
                if ($c === '"') {
                    $this->endString();
                } elseif ($c === '\\') {
                    $this->state = self::STATE_START_ESCAPE;
                } elseif (($c < "\x1f") || ($c === "\x7f")) {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Unescaped control character encountered: " . $c);
                } else {
                    $this->buffer .= $c;
                }
                break;

            case self::STATE_START_ESCAPE:
                $this->processEscapeCharacter($c);
                break;

            case self::STATE_UNICODE:
                $this->processUnicodeCharacter($c);
                break;

            case self::STATE_AFTER_VALUE:
                $within = end($this->stack);
                if ($within === self::STACK_OBJECT) {
                    if ($c === '}') {
                        $this->endObject();
                    } elseif ($c === ',') {
                        $this->state = self::STATE_IN_OBJECT;
                    } else {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Expected ',' or '}' while parsing object. Got: ".$c);
                    }
                } elseif ($within === self::STACK_ARRAY) {
                    if ($c === ']') {
                        $this->endArray();
                    } elseif ($c === ',') {
                        $this->state = self::STATE_IN_ARRAY;
                    } else {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Expected ',' or ']' while parsing array. Got: ".$c);
                    }
                } else {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Finished a literal, but unclear what state to move to. Last state: ".$within);
                }
                break;

            case self::STATE_IN_NUMBER:
                if (preg_match('/\d/', $c)) {
                    $this->buffer .= $c;
                } elseif ($c === '.') {
                    if (strpos($this->buffer, '.') !== false) {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Cannot have multiple decimal points in a number.");
                    } elseif (stripos($this->buffer, 'e') !== false) {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Cannot have a decimal point in an exponent.");
                    }
                    $this->buffer .= $c;
                } elseif ($c === 'e' || $c === 'E') {
                    if (stripos($this->buffer, 'e') !== false) {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Cannot have multiple exponents in a number.");
                    }
                    $this->buffer .= $c;
                } elseif ($c === '+' || $c === '-') {
                    $last = mb_substr($this->buffer, -1);
                    if (!($last === 'e' || $last === 'E')) {
                        throw new ParsingError($this->lineNumber, $this->charNumber,
                            "Can only have '+' or '-' after the 'e' or 'E' in a number.");
                    }
                    $this->buffer .= $c;
                } else {
                    $this->endNumber();
                    // we have consumed one beyond the end of the number
                    $this->consumeChar($c);
                }
                break;

            case self::STATE_IN_TRUE:
                $this->buffer .= $c;
                if (mb_strlen($this->buffer) === 4) {
                    $this->endTrue();
                }
                break;

            case self::STATE_IN_FALSE:
                $this->buffer .= $c;
                if (mb_strlen($this->buffer) === 5) {
                    $this->endFalse();
                }
                break;

            case self::STATE_IN_NULL:
                $this->buffer .= $c;
                if (mb_strlen($this->buffer) === 4) {
                    $this->endNull();
                }
                break;

            case self::STATE_DONE:
                throw new ParsingError($this->lineNumber, $this->charNumber,
                    "Expected end of document.");

            default:
                throw new ParsingError($this->lineNumber, $this->charNumber,
                    "Internal error. Reached an unknown state: ".$this->state);
        }
    }

    /**
     * @param $c
     * @return int
     */
    private function isHexCharacter($c)
    {
        return preg_match('/[0-9a-fA-F]/u', $c);
    }

    /**
     * @param $num
     * @return string
     *
     * Thanks: http://stackoverflow.com/questions/1805802/php-convert-unicode-codepoint-to-utf-8
     */
    private function convertCodepointToCharacter($num)
    {
        if($num<=0x7F)       return chr($num);
        if($num<=0x7FF)      return chr(($num>>6)+192)  . chr(($num&63)+128);
        if($num<=0xFFFF)     return chr(($num>>12)+224) . chr((($num>>6)&63)+128)  . chr(($num&63)+128);
        if($num<=0x1FFFFF)   return chr(($num>>18)+240) . chr((($num>>12)&63)+128) .
                                    chr((($num>>6)&63)+128) . chr(($num&63)+128);

        return '';
    }

    /**
     * @param $c
     * @return int
     */
    private function isDigit($c)
    {
        // Only concerned with the first character in a number.
        return preg_match('/[0-9]|-/u', $c);
    }

    /**
     * @param $c
     * @throws JsonParser\ParsingError
     */
    private function startValue($c)
    {
        if ($c === '[') {
            $this->startArray();
        } elseif ($c === '{') {
            $this->startObject();
        } elseif ($c === '"') {
            $this->startString();
        } elseif ($this->isDigit($c)) {
            $this->startNumber($c);
        } elseif ($c === 't') {
            $this->state = self::STATE_IN_TRUE;
            $this->buffer .= $c;
        } elseif ($c === 'f') {
            $this->state = self::STATE_IN_FALSE;
            $this->buffer .= $c;
        } elseif ($c === 'n') {
            $this->state = self::STATE_IN_NULL;
            $this->buffer .= $c;
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Unexpected character for value: " . $c);
        }
    }

    /**
     *
     */
    private function startArray()
    {
        $this->listener->start_array();
        $this->state = self::STATE_IN_ARRAY;
        array_push($this->stack, self::STACK_ARRAY);
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endArray()
    {
        $popped = array_pop($this->stack);
        if ($popped !== self::STACK_ARRAY) {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Unexpected end of array encountered.");
        }
        $this->listener->end_array();
        $this->state = self::STATE_AFTER_VALUE;

        if (empty($this->stack)) {
            $this->endDocument();
        }
    }

    /**
     *
     */
    private function startObject()
    {
        $this->listener->start_object();
        $this->state = self::STATE_IN_OBJECT;
        array_push($this->stack, self::STACK_OBJECT);
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endObject()
    {
        $popped = array_pop($this->stack);
        if ($popped !== self::STACK_OBJECT) {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Unexpected end of object encountered.");
        }
        $this->listener->end_object();
        $this->state = self::STATE_AFTER_VALUE;

        if (empty($this->stack)) {
            $this->endDocument();
        }
    }

    /**
     *
     */
    private function startString()
    {
        array_push($this->stack, self::STACK_STRING);
        $this->state = self::STATE_IN_STRING;
    }

    /**
     *
     */
    private function startKey()
    {
        array_push($this->stack, self::STACK_KEY);
        $this->state = self::STATE_IN_STRING;
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endString()
    {
        $popped = array_pop($this->stack);
        if ($popped === self::STACK_KEY) {
            $this->listener->key($this->buffer);
            $this->state = self::STATE_END_KEY;
        } elseif ($popped === self::STACK_STRING) {
            $this->listener->value($this->buffer);
            $this->state = self::STATE_AFTER_VALUE;
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Unexpected end of string.");
        }
        $this->buffer = '';
    }

    /**
     * @param $c
     * @throws JsonParser\ParsingError
     */
    private function processEscapeCharacter($c)
    {
        if ($c === '"') {
            $this->buffer .= '"';
        } elseif ($c === '\\') {
            $this->buffer .= '\\';
        } elseif ($c === '/') {
            $this->buffer .= '/';
        } elseif ($c === 'b') {
            $this->buffer .= "\x08";
        } elseif ($c === 'f') {
            $this->buffer .= "\f";
        } elseif ($c === 'n') {
            $this->buffer .= "\n";
        } elseif ($c === 'r') {
            $this->buffer .= "\r";
        } elseif ($c === 't') {
            $this->buffer .= "\t";
        } elseif ($c === 'u') {
            $this->state = self::STATE_UNICODE;
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Expected escaped character after backslash. Got: ".$c);
        }

        if ($this->state !== self::STATE_UNICODE) {
            $this->state = self::STATE_IN_STRING;
        }
    }

    /**
     * @param $c
     * @throws JsonParser\ParsingError
     */
    private function processUnicodeCharacter($c)
    {
        if (!$this->isHexCharacter($c)) {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Expected hex character for escaped unicode character. Unicode parsed: " . implode($this->unicodeBuffer) . " and got: ".$c);
        }
        array_push($this->unicodeBuffer, $c);
        if (count($this->unicodeBuffer) === 4) {
            $codepoint = hexdec(implode($this->unicodeBuffer));

            if ($codepoint >= 0xD800 && $codepoint < 0xDC00) {
                $this->unicodeHighCodePoint = $codepoint;
                $this->unicodeBuffer = array();
            } elseif ($codepoint >= 0xDC00 && $codepoint <= 0xDFFF) {
                if ($this->unicodeHighCodePoint === -1) {
                    throw new ParsingError($this->lineNumber, $this->charNumber,
                        "Missing high codepoint for unicode low codepoint.");
                }
                $combined_codepoint = (($this->unicodeHighCodePoint - 0xD800) * 0x400) + ($codepoint - 0xDC00) + 0x10000;

                $this->endUnicodeCharacter($combined_codepoint);
            } else {
                $this->endUnicodeCharacter($codepoint);
            }
        }
    }

    /**
     * @param $codepoint
     */
    private function endUnicodeCharacter($codepoint)
    {
        $this->buffer .= $this->convertCodepointToCharacter($codepoint);
        $this->unicodeBuffer = array();
        $this->unicodeHighCodePoint = -1;
        $this->state = self::STATE_IN_STRING;
    }

    /**
     * @param $c
     */
    private function startNumber($c)
    {
        $this->state = self::STATE_IN_NUMBER;
        $this->buffer .= $c;
    }

    /**
     *
     */
    private function endNumber()
    {
        $num = $this->buffer;
        if (preg_match('/\./', $num)) {
            $num = (float) ($num);
        } else {
            $num = (int) ($num);
        }
        $this->listener->value($num);

        $this->buffer = '';
        $this->state = self::STATE_AFTER_VALUE;
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endTrue()
    {
        $true = $this->buffer;
        if ($true === 'true') {
            $this->listener->value(true);
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Expected 'true'. Got: ".$true);
        }
        $this->buffer = '';
        $this->state = self::STATE_AFTER_VALUE;
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endFalse()
    {
        $false = $this->buffer;
        if ($false === 'false') {
            $this->listener->value(false);
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Expected 'false'. Got: ".$false);
        }
        $this->buffer = '';
        $this->state = self::STATE_AFTER_VALUE;
    }

    /**
     * @throws JsonParser\ParsingError
     */
    private function endNull()
    {
        $null = $this->buffer;
        if ($null === 'null') {
            $this->listener->value(null);
        } else {
            throw new ParsingError($this->lineNumber, $this->charNumber,
                "Expected 'null'. Got: ".$null);
        }
        $this->buffer = '';
        $this->state = self::STATE_AFTER_VALUE;
    }

    /**
     *
     */
    private function endDocument()
    {
        $this->listener->end_document();
        $this->state = self::STATE_DONE;
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Parser;

/**
 * Interface ListenerInterface
 * @package Bcn\Component\Json\Parser
 */
interface ListenerInterface
{
    /**
     * @param  integer $line
     * @param  integer $char
     * @return void
     */
    public function file_position($line, $char);

    /**
     * @return void
     */
    public function start_document();

    /**
     * @return void
     */
    public function end_document();

    /**
     * @return void
     */
    public function start_object();

    /**
     * @return void
     */
    public function end_object();

    /**
     * @return void
     */
    public function start_array();

    /**
     * @return void
     */
    public function end_array();

    /**
     * @param  string $key
     * @return void
     */
    public function key($key);

    /**
     * @param  string $value
     * @return void
     */
    public function value($value);

}
PHP JSON Component
==================

This project is a rewritten fork of few very nice JSON libs for PHP:

- [salsify/jsonstreamingparser](https://github.com/salsify/jsonstreamingparser) - Json Stream Parser
- [rayward/json-stream](https://github.com/rayward/json-stream) - Json Stream Writer


JSON Writer
-----------

There is an example of product catalog export using JSON Writer

```php
$fh = fopen($filename, "w");
$writer = new Writer($fh);

$writer->enter(Writer::TYPE_OBJECT);                // enter root object
    $writer->write("catalog", $catalog['id']);      // write key-value entry
    $writer->enter("items", Writer::TYPE_ARRAY);    // enter items array
        foreach($catalog['products'] as $product) {
            $writer->write(null, array(             // write an array item
                'sku'  => $product['sku'],
                'name' => $product['name']
            ));
        }
    $writer->leave();                               // leave items array
$writer->leave();                                   // leave root object

fclose($fh);
```

**Output**

```json
{"catalog":19,"items":[{"sku":"0001","name":"Product #1"},{"sku":"0002","name":"Product #2"}]}
```


JSON Reader
-----------

Using JSON Reader you can easily read json generated by code above

```php
$fh = fopen($filename, "r");

$reader = new Reader($fh);
$reader->enter(Reader::TYPE_OBJECT);                // enter root object
    $catalog['id'] = $reader->read("catalog");      // read catalog node
    $reader->enter("items", Reader::TYPE_ARRAY);    // enter item array
        while($product = $reader->read()) {         // read product structure
            $catalog['products'][] = $product;
        }
    $reader->leave();                               // leave item node
$reader->leave();                                   // leave root object

fclose($fh);
```<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json;

use Bcn\Component\Json\Exception\ReadingError;
use Bcn\Component\Json\Reader\Tokenizer;

class Reader
{

    const TYPE_OBJECT = 1;
    const TYPE_ARRAY  = 2;

    /** @var \Bcn\Component\Json\Reader\Tokenizer */
    protected $tokenizer;

    /** @var array */
    protected $token;

    /**
     * @param resource $resource
     */
    public function __construct($resource)
    {
        $this->tokenizer = new Tokenizer($resource);
        $this->token = $this->tokenizer->next();
    }

    /**
     * @param null|string $key
     * @param null|string $type
     * @return bool
     */
    public function enter($key = null, $type = null)
    {
        if ($type === null && in_array($key, array(self::TYPE_OBJECT, self::TYPE_ARRAY))) {
            $type = $key;
            $key  = null;
        }

        switch ($type) {
            case self::TYPE_ARRAY:
                $tokens = array(Tokenizer::TOKEN_ARRAY_START);
                break;
            case self::TYPE_OBJECT:
                $tokens = array(Tokenizer::TOKEN_OBJECT_START);
                break;
            default:
                $tokens = array(Tokenizer::TOKEN_ARRAY_START, Tokenizer::TOKEN_OBJECT_START);
        }

        if ($this->token['key'] != $key || !in_array($this->token['token'], $tokens)) {
            return false;
        }

        $this->next();

        return true;
    }

    /**
     * @return bool
     */
    public function leave()
    {
        if (!($context = $this->tokenizer->context())) {
            return false;
        }

        $level = 0;
        do {
            switch ($this->token['token']) {
                case Tokenizer::TOKEN_ARRAY_START:
                case Tokenizer::TOKEN_OBJECT_START:
                    $level++;
                    break;
                case Tokenizer::TOKEN_ARRAY_END:
                case Tokenizer::TOKEN_OBJECT_END:
                    $level--;
                    break;
            }

        } while ($this->next() && $level >= 0 && $this->tokenizer->context());

        return true;
    }

    /**
     * @param string|null $key
     * @return mixed
     */
    public function read($key = null)
    {
        if ($this->token['key'] != $key) {
            return false;
        }

        switch ($this->token['token']) {
            case Tokenizer::TOKEN_SCALAR:
                $value = $this->token['content'];
                $this->next();

                return $value;
            case Tokenizer::TOKEN_ARRAY_START:
                $items = array();
                $this->enter($this->token['key']);
                while ($this->token['token'] != Tokenizer::TOKEN_ARRAY_END) {
                    $items[] = $this->read();

                    if (!$this->token) {
                        throw new ReadingError("Unexpected object ending");
                    }
                }
                $this->leave();

                return $items;
            case Tokenizer::TOKEN_OBJECT_START:
                $items = array();
                $this->enter($this->token['key']);
                while ($this->token['token'] != Tokenizer::TOKEN_OBJECT_END) {
                    $items[$this->token['key']] = $this->read($this->token['key']);

                    if (!$this->token) {
                        throw new ReadingError("Unexpected object ending");
                    }
                }
                $this->leave();

                return $items;
        }

        return null;
    }

    /**
     *
     */
    protected function next()
    {
        return $this->token = $this->tokenizer->next();
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <skolodyazhnyy@ebay.com>
 *
 */

namespace Bcn\Component\Json\Reader;

use Bcn\Component\Json\Exception\ReadingError;

class Tokenizer
{

    const CONTEXT_OBJECT = 1;
    const CONTEXT_ARRAY  = 2;

    const TOKEN_OBJECT_START   = 1;
    const TOKEN_OBJECT_END     = 2;
    const TOKEN_ARRAY_START    = 4;
    const TOKEN_ARRAY_END      = 8;
    const TOKEN_SCALAR         = 16;
    const TOKEN_KEY            = 32;
    const TOKEN_ITEM_SEPARATOR = 64;

    const EXPECTED_ANY         = 127;
    const EXPECTED_ARRAY_ITEM  = 29;  // Object Start, Array Start, Scalar, Array End
    const EXPECTED_OBJECT_ITEM = 23;  // Object Start, Array Start, Scalar, Object End
    const EXPECTED_SEPARATOR   = 64;
    const EXPECTED_KEY         = 32;
    const EXPECTED_ARRAY_END   = 8;
    const EXPECTED_OBJECT_END  = 2;

    /** @var resource */
    protected $stream;

    /** @var array */
    protected $context  = array();
    protected $expected;

    /** @var array */
    protected $token = array();

    /** @var array */
    protected $buffered = array();

    /** @var array */
    protected $first = array();

    /**
     * @param  resource                  $stream
     * @throws \InvalidArgumentException
     */
    public function __construct($stream)
    {
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
            throw new \InvalidArgumentException("Argument is not a stream");
        }

        $this->stream = $stream;
        $this->expected = self::EXPECTED_ANY;
    }

    /**
     * @return array
     * @throws ReadingError
     */
    public function next()
    {
        $this->token = $this->fetch();

        if (!$this->token['token']) {
            return false;
        }

        if (!($this->token['token'] & $this->expected)) {
            throw new ReadingError(sprintf("Read unexpected token %s/%s", $this->token['token'], $this->expected));
        }

        switch ($this->token['token']) {
            case self::TOKEN_ARRAY_START:
                $this->context[] = self::CONTEXT_ARRAY;
                $this->expected  = self::EXPECTED_ARRAY_ITEM;
                break;
            case self::TOKEN_OBJECT_START:
                $this->context[] = self::CONTEXT_OBJECT;
                $this->expected  = self::EXPECTED_OBJECT_ITEM;
                break;
            case self::TOKEN_OBJECT_END:
            case self::TOKEN_ARRAY_END:
                array_pop($this->context);
            // no break;
            case self::TOKEN_SCALAR:
                if ($this->context()) {
                    $this->expected  = self::EXPECTED_SEPARATOR;
                    $this->expected |= $this->context() == self::CONTEXT_ARRAY ?
                        self::EXPECTED_ARRAY_END :self::EXPECTED_OBJECT_END;
                } else {
                    $this->expected = 0;
                }
                break;
            case self::TOKEN_ITEM_SEPARATOR:
                $this->expected = $this->context() == self::CONTEXT_ARRAY ?
                    self::EXPECTED_ARRAY_ITEM : self::EXPECTED_OBJECT_ITEM;

                return $this->next();

        }

        return $this->token;
    }

    /**
     * @return array
     * @throws ReadingError
     */
    protected function fetch()
    {
        if ($this->context() == self::CONTEXT_OBJECT) {
            list($token, $key) = $this->readKey();
            if ($token != self::TOKEN_KEY) {
                return array(
                    'key'     => null,
                    'token'   => $token,
                    'content' => null
                );
            }
        } else {
            $key = null;
        }

        list($token, $content) = $this->readValue();

        return array(
            'key'     => $key,
            'token'   => $token,
            'content' => $content
        );
    }

    /**
     * @throws ReadingError
     */
    protected function readKey()
    {
        list($token, $key) = $this->readKeyToken();

        if ($token == self::TOKEN_KEY) {
            $char = $this->findSymbol();
            if ($char != ":") {
                throw new ReadingError(sprintf("Expecting key-value separator, got \"%s\"", $char));
            }
        }

        return array($token, $key);
    }

    /**
     * @return array
     */
    protected function readKeyToken()
    {
        $char = $this->findSymbol();

        switch ($char) {
            case "}":
                return array(self::TOKEN_OBJECT_END,     null);
            case "]":
                return array(self::TOKEN_ARRAY_END,      null);
            case ",":
                return array(self::TOKEN_ITEM_SEPARATOR, null);
            case "\"":
                return array(self::TOKEN_KEY, $this->completeStringReading($char));
        }

        return array(null, null);
    }

    /**
     * @return array
     * @throws ReadingError
     */
    protected function readValue()
    {
        $char = $this->findSymbol();

        if ($char === "" || $char === false) {
            return array(null, null);
        }

        switch ($char) {
            case "{":
                return array(self::TOKEN_OBJECT_START,   null);
            case "}":
                return array(self::TOKEN_OBJECT_END,     null);
            case "[":
                return array(self::TOKEN_ARRAY_START,    null);
            case "]":
                return array(self::TOKEN_ARRAY_END,      null);
            case ",":
                return array(self::TOKEN_ITEM_SEPARATOR, null);
            case "\"":
                return array(self::TOKEN_SCALAR, $this->completeStringReading($char));
            default:
                return array(self::TOKEN_SCALAR, $this->completeScalarReading($char));
        }

    }

    /**
     * @param $char
     * @throws ReadingError
     * @return string
     */
    protected function completeStringReading($char)
    {
        $quotes  = $char;
        $buffer  = "";
        $escaped = false;

        while (true) {
            $char = $this->readSymbol();
            if ($char === false || $char === "") {
                break;
            }
            if ($quotes == $char && !$escaped) {
                return json_decode($quotes . $buffer . $quotes);
            }
            $buffer .= $char;
            $escaped = $quotes === "\"" && $char == "\\";
        }

        throw new ReadingError("String not terminated correctly " . ftell($this->stream));
    }

    /**
     * @param $char
     * @return string
     * @throws ReadingError
     */
    protected function completeScalarReading($char)
    {
        $buffer = $char;

        while (true) {
            $char = $this->readSymbol();
            if ($char === "" || $char === false || strpos(",}] \t\n\r", $char) !== false) {
                if ($char && strpos(",}]", $char) !== false) {
                    $this->buffered[] = $char;
                }
                break;
            }
            $buffer .= $char;
        }

        switch ($buffer) {
            case "true":
                return true;
            case "false":
                return false;
            case "null":
                return null;
        }

        if (!preg_match("/^-?[0-9]*(\.[0-9]+)?(e[0-9]+)?$/", $buffer)) {
            throw new ReadingError(sprintf("Scalar value \"%s\" is invalid", $buffer));
        }

        return floatval($buffer);
    }

    /**
     * @return string
     */
    protected function findSymbol()
    {
        while (($char = $this->readSymbol()) && strpos(" \n\r\t", $char) !== false);

        return $char;
    }

    /**
     * @return string
     */
    protected function readSymbol()
    {
        if ($this->buffered) {
            return array_pop($this->buffered);
        }

        return fread($this->stream, 1);
    }

    /**
     * @return mixed
     */
    public function context()
    {
        return end($this->context);
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests;

use Bcn\Component\Json\Reader;
use Bcn\Component\Json\Writer;
use Bcn\Component\StreamWrapper\Stream;

class ExampleTest extends \PHPUnit_Framework_TestCase
{

    public function testWriting()
    {
        $catalog = $this->getData();
        $filename = new Stream();

        $fh = fopen($filename, "w");
        $writer = new Writer($fh);

        $writer->enter(Writer::TYPE_OBJECT);                // enter root object
            $writer->write("catalog", $catalog['id']);      // write key-value entry
            $writer->enter("items", Writer::TYPE_ARRAY);    // enter items array
                foreach ($catalog['products'] as $product) {
                    $writer->write(null, array(             // write an array item
                        'sku'  => $product['sku'],
                        'name' => $product['name']
                    ));
                }
            $writer->leave();                               // leave items array
        $writer->leave();                                   // leave root object

        fclose($fh);

        $this->assertEquals($this->getJSON(), $filename->getContent());
    }

    /**
     *
     */
    public function testReading()
    {
        $filename = new Stream($this->getJSON());
        $catalog = array();

        $fh = fopen($filename, "r");

        $reader = new Reader($fh);
        $reader->enter(Reader::TYPE_OBJECT);                // enter root object
            $catalog['id'] = $reader->read("catalog");      // read catalog node
            $reader->enter("items", Reader::TYPE_ARRAY);    // enter item array
                while ($product = $reader->read()) {         // read product structure
                    $catalog['products'][] = $product;
                }
            $reader->leave();                               // leave item node
        $reader->leave();                                   // leave root object

        fclose($fh);

        $this->assertEquals($this->getData(), $catalog);
    }

    /**
     * @return array
     */
    protected function getData()
    {
        return array(
            'id' => 19,
            'products' => array(
                array("sku" => "0001", "name" => "Product #1"),
                array("sku" => "0002", "name" => "Product #2")
            )
        );
    }

    /**
     * @return string
     */
    protected function getJSON()
    {
        return '{"catalog":19,"items":[{"sku":"0001","name":"Product #1"},{"sku":"0002","name":"Product #2"}]}';
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests\Parser;

use Bcn\Component\Json\Parser;
use Bcn\Component\Json\Tests\Parser\FunctionalTest\TestListener;

/**
 * Class FunctionalTest
 * @package Bcn\Component\Json\Tests\Parser
 */
class FunctionalTest extends \PHPUnit_Framework_TestCase
{

    /**
     *
     */
    public function testTraverseOrder()
    {
        $listener = new TestListener();
        $parser = new Parser(fopen($this->getFixtureName('example.json'), 'r'), $listener);
        $parser->parse();

        $this->assertEquals(
            array(
                'start_document',
                'start_array',
                'start_object',
                'key = name',
                'value = example document for wicked fast parsing of huge json docs',
                'key = integer',
                'value = 123',
                'key = totally sweet scientific notation',
                'value = -1.23123',
                'key = unicode? you betcha!',
                'value = ú™£¢∞§♥',
                'key = zero character',
                'value = 0',
                'key = null is boring',
                'value = NULL',
                'end_object',
                'start_object',
                'key = name',
                'value = another object',
                'key = cooler than first object?',
                'value = 1',
                'key = nested object',
                'start_object',
                'key = nested object?',
                'value = 1',
                'key = is nested array the same combination i have on my luggage?',
                'value = 1',
                'key = nested array',
                'start_array',
                'value = 1',
                'value = 2',
                'value = 3',
                'value = 4',
                'value = 5',
                'end_array',
                'end_object',
                'key = false',
                'value = false',
                'end_object',
                'end_array',
                'end_document',
            ),
            $listener->order
        );
    }

    /**
     *
     */
    public function testListenerGetsNotifiedAboutPositionInFileOfDataRead()
    {
        $listener = new TestListener();
        $parser = new Parser(fopen($this->getFixtureName('data-ranges.json'), 'r'), $listener);
        $parser->parse();

        $this->assertEquals(
            array(
                array('value' => '2013-10-24', 'line' => 5,  'char' => 42),
                array('value' => '2013-10-25', 'line' => 5,  'char' => 67),
                array('value' => '2013-10-26', 'line' => 6,  'char' => 42),
                array('value' => '2013-10-27', 'line' => 6,  'char' => 67),
                array('value' => '2013-11-01', 'line' => 10, 'char' => 46),
                array('value' => '2013-11-10', 'line' => 10, 'char' => 71),
            ),
            $listener->positions
        );
    }

    public function testCountsLongLinesCorrectly()
    {
        $value = str_repeat('!', 10000);
        $longStream = self::inMemoryStream(<<<JSON
[
  "$value",
  "$value"
]
JSON
        );

        $listener = new TestListener();
        $parser = new Parser($longStream, $listener);
        $parser->parse();

        unset($listener->positions[0]['value']);
        unset($listener->positions[1]['value']);

        $this->assertSame(array(
                array('line' => 2, 'char' => 10004,),
                array('line' => 3, 'char' => 10004,),
            ),
            $listener->positions
        );
    }

    /**
     *
     */
    public function testThrowsParingError()
    {
        $listener = new TestListener();
        $parser = new Parser(self::inMemoryStream('{ invalid json }'), $listener);

        $this->setExpectedException('Bcn\Component\Json\Exception\ParsingError', 'Parsing error in [1:3]');
        $parser->parse();
    }

    /**
     * @param $content
     * @return resource
     */
    private static function inMemoryStream($content)
    {
        $stream = fopen('php://memory', 'rw');
        fwrite($stream, $content);
        fseek($stream, 0);

        return $stream;
    }

    /**
     * @param  string $name
     * @return string
     */
    protected function getFixtureName($name)
    {
        return __DIR__ . '/FunctionalTest/fixtures/' . $name;
    }
}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests\Parser\FunctionalTest;
use Bcn\Component\Json\Parser\ListenerInterface;

/**
 * Class TestListener
 * @package Bcn\Component\Json\Tests\Parser\FunctionalTest
 */
class TestListener implements ListenerInterface
{

    /** @var array */
    public $order = array();
    /** @var array */
    public $positions = array();
    /** @var integer */
    private $currentLine;
    /** @var integer */
    private $currentChar;

    public function file_position($line, $char)
    {
        $this->currentLine = $line;
        $this->currentChar = $char;
    }

    public function start_document()
    {
        $this->order[] = __FUNCTION__;
    }

    public function end_document()
    {
        $this->order[] = __FUNCTION__;
    }

    public function start_object()
    {
        $this->order[] = __FUNCTION__;
    }

    public function end_object()
    {
        $this->order[] = __FUNCTION__;
    }

    public function start_array()
    {
        $this->order[] = __FUNCTION__;
    }

    public function end_array()
    {
        $this->order[] = __FUNCTION__;
    }

    public function key($key)
    {
        $this->order[] = __FUNCTION__ . ' = ' . self::stringify($key);
    }

    public function value($value)
    {
        $this->order[] = __FUNCTION__ . ' = ' . self::stringify($value);
        $this->positions[] = array('value' => $value, 'line' => $this->currentLine, 'char' => $this->currentChar);
    }

    private static function stringify($value)
    {
        return strlen($value) ? $value : var_export($value, true);
    }
}
{
    "data": [
        {"rows" :
            [
                {"startDate": "2013-10-24", "endDate": "2013-10-25"},
                {"startDate": "2013-10-26", "endDate": "2013-10-27"}
            ]
        }
    ],
    "anotherPlace": {"startDate": "2013-11-01", "endDate": "2013-11-10"}
}{
    "key": "First line\nSecond line\ttabulation\\back slash\rcarriage return\fformfeed\/slash\bbackspace"
}[
    {
        "name": "example document for wicked fast parsing of huge json docs",
        "integer": 123,
        "totally sweet scientific notation": -123.123e-2,
        "unicode? you betcha!": "ú™£¢∞§\u2665",
        "zero character": "0",
        "null is boring": null
    },
    {
        "name": "another object",
        "cooler than first object?": true,
        "nested object": {
            "nested object?": true,
            "is nested array the same combination i have on my luggage?": true,
            "nested array": [1,2,3,4,5]
        },
        "false": false
    }
]{
    "key 1" : "value 1",
    "key 2" : "value 2",
    "key 3" : "value 3"
}<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests\Reader;

use Bcn\Component\Json\Reader\Tokenizer;
use Bcn\Component\StreamWrapper\Stream;
use Symfony\Component\Yaml\Yaml;

class TokenizerTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @param string $content
     * @param array  $tokens
     *
     * @dataProvider provideTokens
     */
    public function testTokenizer($content, array $tokens)
    {
        $resource = fopen(new Stream($content), "r");

        $reader = new Tokenizer($resource);
        foreach ($tokens as $token) {
            $token['token'] = $this->toTokenCode($token['token']);
            $this->assertEquals($token, $reader->next());
        }

        fclose($resource);
    }

    protected function toTokenCode($code)
    {
        switch ($code) {
            case 'scalar':       return Tokenizer::TOKEN_SCALAR;
            case 'array_start':  return Tokenizer::TOKEN_ARRAY_START;
            case 'object_start': return Tokenizer::TOKEN_OBJECT_START;
            case 'array_end':    return Tokenizer::TOKEN_ARRAY_END;
            case 'object_end':   return Tokenizer::TOKEN_OBJECT_END;
        }

        return $code;
    }

    /**
     * @return array
     */
    public function provideTokens()
    {
        return Yaml::parse(__DIR__ . DIRECTORY_SEPARATOR . "fixtures" . DIRECTORY_SEPARATOR . "tokenizer.yml");
    }

}
'Double-quoted string':
    content: '"Hello"'
    tokens:
      - { key: null, token: scalar, content: Hello }

'Zero string - 0000001':
    content: '"0000001"'
    tokens:
      - { key: null, token: scalar, content: "0000001" }

'Double-quoted string with special chars':
    content: '"Hello \"World\""'
    tokens: [{ key: null, token: scalar, content: 'Hello "World"' }]

'Number 99.99':
    content: '99.99'
    tokens: [{ key: null, token: scalar, content: 99.99 }]

'Number .99':
    content: '.99'
    tokens: [{ key: null, token: scalar, content: 0.99 }]

'Number 0.001':
    content: '0.001'
    tokens: [{ key: null, token: scalar, content: 0.001 }]

'Number 000123':
    content: '000123'
    tokens: [{ key: null, token: scalar, content: 123 }]

'Number -.5':
    content: '-.5'
    tokens: [{ key: null, token: scalar, content: -0.5 }]

'Number -.01e15':
    content: '-.01e15'
    tokens: [{ key: null, token: scalar, content: -10000000000000 }]

'True':
    content: 'true'
    tokens: [{ key: null, token: scalar, content: true }]

'False':
    content: 'false'
    tokens: [{ key: null, token: scalar, content: false }]

'Null':
    content: 'null'
    tokens: [{ key: null, token: scalar, content: null }]

'Object':
    content: '{}'
    tokens:
        - { key: null, token: object_start, content: null }
        - { key: null, token: object_end,   content: null }

'Array':
    content: '[]'
    tokens:
        - { key: null, token: array_start, content: null }
        - { key: null, token: array_end,   content: null }

'Skip leading whitespaces':
    content: "\n\t\r    \"String\""
    tokens: [{ key: null, token: scalar, content: String }]

'Single item array':
    content: "[1.55]"
    tokens:
        - { key: null, token: array_start, content: null }
        - { key: null, token: scalar,      content: 1.55 }
        - { key: null, token: array_end,   content: null }

'Multiple items array':
    content: "[1.55,99.2,\"Hola\"]"
    tokens:
        - { key: null, token: array_start, content: null }
        - { key: null, token: scalar,      content: 1.55 }
        - { key: null, token: scalar,      content: 99.2 }
        - { key: null, token: scalar,      content: Hola }
        - { key: null, token: array_end,   content: null }

'Multiple items array with spaces':
    content: "[   1.55 ,\n 99.2 , \"Abc\"     ]"
    tokens:
        - { key: null, token: array_start, content: null }
        - { key: null, token: scalar,      content: 1.55 }
        - { key: null, token: scalar,      content: 99.2 }
        - { key: null, token: scalar,      content: "Abc" }
        - { key: null, token: array_end,   content: null }

'Nested array':
    content: "[[1,[\"a\",[],\"b\"],2]]"
    tokens:
        - { key: null, token: array_start, content: null }
        - { key: null, token: array_start, content: null }
        - { key: null, token: scalar,      content: 1 }
        - { key: null, token: array_start, content: null }
        - { key: null, token: scalar,      content: 'a' }
        - { key: null, token: array_start, content: null }
        - { key: null, token: array_end,   content: null }
        - { key: null, token: scalar,      content: 'b' }
        - { key: null, token: array_end,   content: null }
        - { key: null, token: scalar,      content: 2 }
        - { key: null, token: array_end,   content: null }
        - { key: null, token: array_end,   content: null }

'Single property object':
    content: "{ \"Root\":001}"
    tokens:
        - { key: null,   token: object_start, content: null }
        - { key: "Root", token: scalar,       content: 1 }

'Multiple property object':
    content: "{\"Child A\": \"VALUE1\", \"Child B\": [] }"
    tokens:
        - { key: null,      token: object_start, content: null }
        - { key: "Child A", token: scalar,       content: "VALUE1" }
        - { key: "Child B", token: array_start,  content: null }
        - { key: null,      token: array_end,    content: null }
        - { key: null,      token: object_end,   content: null }

'Nested object':
    content: "{\"Root\": { \"Child A\": \"VALUE1\", \"Child B\": [] } }"
    tokens:
        - { key: null,      token: object_start, content: null }
        - { key: "Root",    token: object_start, content: null }
        - { key: "Child A", token: scalar,       content: "VALUE1" }
        - { key: "Child B", token: array_start,  content: null }
        - { key: null,      token: array_end,    content: null }
        - { key: null,      token: object_end,   content: null }
        - { key: null,      token: object_end,   content: null }
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests;

use Bcn\Component\Json\Reader;
use Bcn\Component\StreamWrapper\Stream;

class ReaderTest extends \PHPUnit_Framework_TestCase
{

    /**
     *
     */
    public function testObjectReading()
    {
        $stream = new Stream(<<<JSON
        {
            "catalog": "catalog_code",
            "stock": "stock_code",
            "items": [
                {"sku":"ABC","qty":1},
                {"sku":"A\"BC","qty":.095},
                {"sku":"CDE","qty":0}
            ]
        }
JSON
        );

        $reader = new Reader(fopen($stream, "r"));

        $this->assertTrue($reader->enter(null, Reader::TYPE_OBJECT));  // enter root object
            $catalog = $reader->read("catalog");                       // read property catalog
            $stock   = $reader->read("stock");                         // read property stock
            $items   = array();
            $this->assertTrue($reader->enter("items"));                // enter property items
                while ($reader->enter()) {                             // enter each item
                    $sku = $reader->read("sku");                       // read property sku
                    $qty = $reader->read("qty");                       // read property qty
                    $reader->leave();                                  // leave item node

                    $items[] = array("sku" => $sku, "qty" => $qty);
                }
            $reader->leave();                                          // leave items node
        $reader->leave();                                              // leave root node

        $this->assertEquals("catalog_code", $catalog);
        $this->assertEquals("stock_code",   $stock);
        $this->assertEquals(array(
            array('sku' => 'ABC',  'qty' => 1),
            array('sku' => 'A"BC', 'qty' => .095),
            array('sku' => 'CDE',  'qty' => 0)
        ),   $items);

    }
    /**
     *
     */
    public function testExtractReading()
    {
        $stream = new Stream(<<<JSON
        {
            "catalog": "catalog_code",
            "stock": "stock_code",
            "items": [
                {"sku":"ABC","qty":1},
                {"sku":"A\"BC","qty":.095},
                {"sku":"CDE","qty":0}
            ]
        }
JSON
        );

        $reader = new Reader(fopen($stream, "r"));
        $this->assertEquals(array(
            'catalog' => 'catalog_code',
            'stock' => 'stock_code',
            'items' => array(
                array('sku' => 'ABC',  'qty' => 1),
                array('sku' => 'A"BC', 'qty' => 0.095),
                array('sku' => 'CDE',  'qty' => 0),
            )
        ), $reader->read());
    }

    /**
     *
     */
    public function testReaderLeaving()
    {
        $stream = new Stream(<<<JSON
        {
            "first": "1",
            "items": [
                "String",
                {"sku":"ABC","qty":1},
                {"sku":"A\"BC","qty":.095},
                999,
                {"sku":"CDE","qty":[{  }]}
            ],
            "last": "2"
        }
JSON
        );

        $reader = new Reader(fopen($stream, "r"));

        $this->assertTrue($reader->enter(null, Reader::TYPE_OBJECT));  // enter root object
        $first = $reader->read("first");                               // read property first
        $this->assertTrue($reader->enter("items"));                    // enter property items
        $this->assertTrue($reader->leave());                           // leave items node
        $last = $reader->read("last");                                 // leave root node

        $this->assertEquals("1", $first);
        $this->assertEquals("2", $last);

    }

    /**
     * @param $string
     * @param $data
     *
     * @dataProvider provideReadingData
     */
    public function testReading($string, $data)
    {
        $stream = new Stream($string);
        $reader = new Reader(fopen($stream, "r"));
        $this->assertEquals($data, $reader->read());
    }

    /**
     * @return array
     */
    public function provideReadingData()
    {
        return array(
            "Double quoted string" => array('"test"', "test"),
            "Escaped string"       => array(json_encode("\"!@\n\t#$%^&*()_+/\\\"\\'"), "\"!@\n\t#$%^&*()_+/\\\"\\'"),
            "Integer"              => array("12345", 12345),
            "Float"                => array("123.45", 123.45),
        );
    }

    /**
     * @param $content
     * @dataProvider provideMalformedFiles
     */
    public function testMalformedFileReading($content)
    {
        $this->setExpectedException('Bcn\Component\Json\Exception\ReadingError');

        $reader = new Reader(fopen(new Stream($content), "r"));
        $reader->read();
    }

    /**
     * @return array
     */
    public function provideMalformedFiles()
    {
        return array(
            'Unquoted string'                       => array('string'),
            'Unwrapped array items'                 => array('"Array", "Array"'),
            'Property name in non-object context'   => array('"key": "Value"'),
            'Property name in array'                => array('["key": "Value"]'),
            'Malformed object'                     => array('{"key": "Value": "Test"}')
        );
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json\Tests;

use Bcn\Component\Json\Writer;
use Bcn\Component\StreamWrapper\Stream;

/**
 * Class WriterTest
 * @package Bcn\Component\Json\Tests
 */
class WriterTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @param $data
     *
     * @dataProvider provideEncodeData
     */
    public function testWrite($data)
    {
        $stream = fopen("php://memory", "r+");
        $writer = new Writer($stream);
        $writer->write(null, $data);

        rewind($stream);
        $encoded = stream_get_contents($stream);
        fclose($stream);

        $this->assertEquals($data, json_decode($encoded, true), $encoded);
    }

    /**
     * @return array
     */
    public function provideEncodeData()
    {
        return array(
            'String'                    => array("String"),
            'String with special chars' => array("String\"\'\\\/!@#$%^&*()!@¡™£¢"),
            'Integer'                   => array(1),
            'Decimal'                   => array(9.9991119),
            'Array'                     => array(1, 2, 3),
            'Object'                    => array('a' => 1, 'b' => 2),
            'Array of objects'          => array(array(array('a' => 1, 'b' => 2), array('a' => 1, 'b' => 2))),
            'Multilevel object'         => array(array('a' => array('a' => 1, 'b' => 2), 'b' => array('a' => 1, 'b' => 2))),
            'Object with special keys'  => array(array('¡Hola!' => 'Hello!'))
        );
    }

    /**
     * @param $items
     *
     * @dataProvider provideArrayData
     */
    public function testArrayWriting($items)
    {
        $stream = fopen("php://memory", "r+");
        $writer = new Writer($stream);
        $writer->enter();
        foreach ($items as $item) {
           $writer->write(null, $item);
        }
        $writer->leave();

        rewind($stream);
        $encoded = stream_get_contents($stream);
        fclose($stream);

        $this->assertEquals($items, json_decode($encoded, true), $encoded);
    }

    /**
     * @return array
     */
    public function provideArrayData()
    {
        return array(
            'Array without items'       => array(array()),
            'Array with one item'       => array(array(1)),
            'Array with multiple items' => array(array(1, 2, 3)),
            'Nested array'              => array(array(array(1, 2), array(2, 3), 4))
        );
    }

    /**
     * @param $items
     *
     * @dataProvider provideObjectData
     */
    public function testObjectWriting($items)
    {
        $stream = fopen("php://memory", "r+");
        $writer = new Writer($stream);
        $writer->enter(Writer::TYPE_OBJECT);
        foreach ($items as $key => $item) {
           $writer->write($key, $item);
        }
        $writer->leave();

        rewind($stream);
        $encoded = stream_get_contents($stream);
        fclose($stream);

        $this->assertEquals($items, json_decode($encoded, true), $encoded);
    }

    /**
     * @return array
     */
    public function provideObjectData()
    {
        return array(
            'Empty object'               => array(array()),
            'Object with one item'       => array(array('a' => 1)),
            'Object with numeric keys'   => array(array('2' => 1, '11' => 3)),
            'Object with multiple items' => array(array('a' => 1, 'b' => 2, 'c' => 3)),
            'Nested object'              => array(array(
                    'a' => array('aa' => 1, 'ab' => 2),
                    'b' => array('ba' => 2, 'bb' => 3),
                    'c' => 4
                )
            )
        );
    }

    /**
     *
     */
    public function testBrackets()
    {
        $stream = fopen("php://memory", "r+");
        $writer = new Writer($stream);
        $writer
            ->enter(Writer::TYPE_OBJECT)
                    ->enter("key", Writer::TYPE_ARRAY)
                        ->enter(Writer::TYPE_OBJECT)
                            ->enter("inner", Writer::TYPE_ARRAY)
                                ->enter()
                                ->leave()
                            ->leave()
                        ->leave()
                    ->leave()
                ->leave()
            ->leave()
        ;

        rewind($stream);
        $encoded = stream_get_contents($stream);
        fclose($stream);

        $this->assertEquals(
            array("key" => array(array('inner' => array(array())))),
            json_decode($encoded, true),
            $encoded
        );
    }

}
<?php
/**
 *
 * This file is part of the JSON Stream Project.
 *
 * @author Sergey Kolodyazhnyy <sergey.kolodyazhnyy@gmail.com>
 *
 */

namespace Bcn\Component\Json;
use Bcn\Component\Json\Exception\WritingError;

/**
 * Class Writer
 * @package Bcn\Component\Json
 */
class Writer
{

    const TYPE_OBJECT = 1;
    const TYPE_ARRAY  = 2;
    const TYPE_NULL   = 3;
    const TYPE_BOOL   = 4;
    const TYPE_SCALAR = 5;

    const CONTEXT_NONE         = 0;
    const CONTEXT_ARRAY        = 1;
    const CONTEXT_ARRAY_START  = 2;
    const CONTEXT_OBJECT       = 3;
    const CONTEXT_OBJECT_START = 4;

    protected $stream;
    protected $context;

    protected $parents = array();

    /**
     * @param  resource                  $stream A stream resource.
     * @throws \InvalidArgumentException If $stream is not a stream resource.
     */
    public function __construct($stream)
    {
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
            throw new \InvalidArgumentException("Resource is not a stream");
        }

        $this->stream  = $stream;
        $this->context = self::CONTEXT_NONE;
    }

    /**
     * @param $key
     * @param $value
     * @param null $type
     * @return $this
     * @throws Exception\WritingError
     */
    public function write($key, $value, $type = null)
    {
        if ($value instanceof \JsonSerializable) {
            $value = $value->jsonSerialize();
        }

        switch ($type ? : $this->getType($value)) {
            case self::TYPE_NULL:
                $this->prefix($key);
                $this->streamWrite('null');
                break;
            case self::TYPE_BOOL:
                $this->prefix($key);
                $this->streamWrite($value ? 'true' : 'false');
                break;
            case self::TYPE_SCALAR:
                $this->prefix($key);
                $this->scalar($value);
                break;
            case self::TYPE_ARRAY:
                $this->encodeArray($key, $value);
                break;
            case self::TYPE_OBJECT:
                $this->encodeObject($key, $value);
                break;
            default:
                throw new WritingError("Unrecognized type");
        }

        $this->streamFlush();

        return $this;
    }

    /**
     * @param string $key
     * @param int $type
     * @return $this
     */
    public function enter($key = null, $type = null)
    {
        if ($type === null) {
            if (in_array($key, array(self::TYPE_OBJECT, self::TYPE_ARRAY))) {
                $type = $key;
                $key  = null;
            } else {
                $type = self::TYPE_ARRAY;
            }
        }
        $this->prefix($key);

        array_push($this->parents, $this->context);
        $this->context = $type == self::TYPE_OBJECT ? self::CONTEXT_OBJECT_START : self::CONTEXT_ARRAY_START;

        return $this;
    }

    /**
     * @return $this
     */
    public function leave()
    {
        switch ($this->context) {
            case self::CONTEXT_OBJECT:
                $this->streamWrite("}");
                break;
            case self::CONTEXT_OBJECT_START:
                $this->streamWrite("{}");
                break;
            case self::CONTEXT_ARRAY:
                $this->streamWrite("]");
                break;
            case self::CONTEXT_ARRAY_START:
                $this->streamWrite("[]");
                break;
        }

        $this->context = array_pop($this->parents);

        return $this;
    }

    /**
     * @param $value
     * @return string
     */
    protected function getType($value)
    {
        if (is_bool($value)) {
            return self::TYPE_BOOL;
        }
        if (is_scalar($value)) {
            return self::TYPE_SCALAR;
        }
        if (is_array($value)  && !$this->isAssociative($value)) {
            return self::TYPE_ARRAY;
        }
        if (is_object($value) && $value instanceof \Traversable) {
            return self::TYPE_ARRAY;
        }
        if (is_object($value) || is_array($value)) {
            return self::TYPE_OBJECT;
        }

        return self::TYPE_NULL;
    }

    /**
     * @param $value
     * @return $this
     */
    public function scalar($value)
    {
        $this->streamWrite(json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT));

        return $this;
    }

    /**
     * @param $value
     * @return bool
     */
    protected function isAssociative($value)
    {
        if (is_object($value) && $value instanceof \Traversable && !($value instanceof \ArrayAccess)) {
            return false;
        }

        if (!is_array($value)) {
            return true;
        }

        $keys = array_keys($value);
        sort($keys);

        return end($keys) !== count($keys) - 1;
    }

    /**
     * @param $key
     * @param $array
     */
    protected function encodeArray($key, $array)
    {
        $this->enter($key, self::TYPE_ARRAY);
        foreach ($array as $value) {
            $this->write(null, $value);
        }
        $this->leave();
    }

    /**
     * @param $key
     * @param $object
     */
    protected function encodeObject($key, $object)
    {
        $this->enter($key, self::TYPE_OBJECT);
        foreach ($object as $key => $value) {
            $this->write((string) $key, $value);
        }
        $this->leave();
    }

    /**
     * @param $value
     */
    protected function streamWrite($value)
    {
        fwrite($this->stream, $value);
    }

    /**
     *
     */
    protected function streamFlush()
    {
        fflush($this->stream);
    }

    /**
     * @param $key
     */
    protected function key($key)
    {
        $this->scalar((string) $key);
        $this->streamWrite(":");
    }

    /**
     * @param $key
     */
    protected function prefix($key)
    {
        switch ($this->context) {
            case self::CONTEXT_OBJECT_START:
                $this->streamWrite("{");
                $this->key($key);
                $this->context = self::CONTEXT_OBJECT;
                break;
            case self::CONTEXT_ARRAY_START:
                $this->streamWrite("[");
                $this->context = self::CONTEXT_ARRAY;
                break;
            case self::CONTEXT_OBJECT:
                $this->streamWrite(',');
                $this->key($key);
                break;
            case self::CONTEXT_ARRAY:
                $this->streamWrite(',');
                break;
        }
    }

}
{
    "name":         "bcncommerce/json-stream",
    "description":  "A bundle of tools to work with JSON in PHP",
    "keywords":     ["json", "parser", "streaming", "writer", "reader"],
    "type":         "library",
    "license":      "MIT",
    "authors": [
        {
            "name":  "Sergey Kolodyazhnyy",
            "email": "sergey.kolodyazhnyy@gmail.com"
        }
    ],
    "require": {
        "php": ">=5.3"
    },
    "require-dev": {
        "phpunit/phpunit": "~3.7.0",
        "bcncommerce/stream-wrapper": "dev-master"
    },
    "autoload": {
        "psr-0": { "Bcn\\Component\\Json\\": "" }
    },
    "target-dir": "Bcn/Component/Json"
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="true"
         syntaxCheck="true"
         bootstrap="./vendor/autoload.php"
        >
    <testsuites>
        <testsuite name="All">
            <directory>./Tests</directory>
        </testsuite>
    </testsuites>
</phpunit>[
    {
        "name": "bcncommerce/json-stream",
        "version": "0.3.0",
        "version_normalized": "0.3.0.0",
        "target-dir": "Bcn/Component/Json",
        "source": {
            "type": "git",
            "url": "https://github.com/skolodyazhnyy/json-stream.git",
            "reference": "9ce2aa962bc881fb0222246ba2d6d6e52d1ea5aa"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/skolodyazhnyy/json-stream/zipball/9ce2aa962bc881fb0222246ba2d6d6e52d1ea5aa",
            "reference": "9ce2aa962bc881fb0222246ba2d6d6e52d1ea5aa",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3"
        },
        "require-dev": {
            "bcncommerce/stream-wrapper": "dev-master",
            "phpunit/phpunit": "~3.7.0"
        },
        "time": "2014-04-27T23:53:40+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Bcn\\Component\\Json\\": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Sergey Kolodyazhnyy",
                "email": "sergey.kolodyazhnyy@gmail.com"
            }
        ],
        "description": "A bundle of tools to work with JSON in PHP",
        "keywords": [
            "json",
            "parser",
            "reader",
            "streaming",
            "writer"
        ]
    },
    {
        "name": "pimple/pimple",
        "version": "v3.0.0",
        "version_normalized": "3.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/silexphp/Pimple.git",
            "reference": "876bf0899d01feacd2a2e83f04641e51350099ef"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/silexphp/Pimple/zipball/876bf0899d01feacd2a2e83f04641e51350099ef",
            "reference": "876bf0899d01feacd2a2e83f04641e51350099ef",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2014-07-24T09:48:15+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Pimple": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            }
        ],
        "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
        "homepage": "http://pimple.sensiolabs.org",
        "keywords": [
            "container",
            "dependency injection"
        ]
    },
    {
        "name": "nikic/php-parser",
        "version": "v1.4.0",
        "version_normalized": "1.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/nikic/PHP-Parser.git",
            "reference": "196f177cfefa0f1f7166c0a05d8255889be12418"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/196f177cfefa0f1f7166c0a05d8255889be12418",
            "reference": "196f177cfefa0f1f7166c0a05d8255889be12418",
            "shasum": ""
        },
        "require": {
            "ext-tokenizer": "*",
            "php": ">=5.3"
        },
        "time": "2015-07-14T17:31:05+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "files": [
                "lib/bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Nikita Popov"
            }
        ],
        "description": "A PHP parser written in PHP",
        "keywords": [
            "parser",
            "php"
        ]
    },
    {
        "name": "symfony/process",
        "version": "v2.3.0",
        "version_normalized": "2.3.0.0",
        "target-dir": "Symfony/Component/Process",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/process.git",
            "reference": "bb721b29e033594512f8b08386e13593b0faaf0f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/process/zipball/bb721b29e033594512f8b08386e13593b0faaf0f",
            "reference": "bb721b29e033594512f8b08386e13593b0faaf0f",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2013-05-06T20:03:44+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.3-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Symfony\\Component\\Process\\": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Symfony Community",
                "homepage": "http://symfony.com/contributors"
            },
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            }
        ],
        "description": "Symfony Process Component",
        "homepage": "http://symfony.com"
    },
    {
        "name": "symfony/finder",
        "version": "v2.3.0",
        "version_normalized": "2.3.0.0",
        "target-dir": "Symfony/Component/Finder",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/finder.git",
            "reference": "cbe6c533f9ca17bbb5844252147fd8386cc234c0"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/finder/zipball/cbe6c533f9ca17bbb5844252147fd8386cc234c0",
            "reference": "cbe6c533f9ca17bbb5844252147fd8386cc234c0",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2013-06-02T12:05:51+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.3-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Symfony\\Component\\Finder\\": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Symfony Community",
                "homepage": "http://symfony.com/contributors"
            },
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            }
        ],
        "description": "Symfony Finder Component",
        "homepage": "http://symfony.com"
    },
    {
        "name": "symfony/console",
        "version": "v2.3.0",
        "version_normalized": "2.3.0.0",
        "target-dir": "Symfony/Component/Console",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/console.git",
            "reference": "36e238358617de042f7c1d93e012f06b3d7e5f40"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/console/zipball/36e238358617de042f7c1d93e012f06b3d7e5f40",
            "reference": "36e238358617de042f7c1d93e012f06b3d7e5f40",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "require-dev": {
            "symfony/event-dispatcher": "~2.1"
        },
        "suggest": {
            "symfony/event-dispatcher": ""
        },
        "time": "2013-05-30T05:11:26+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.3-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Symfony\\Component\\Console\\": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Symfony Community",
                "homepage": "http://symfony.com/contributors"
            },
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            }
        ],
        "description": "Symfony Console Component",
        "homepage": "http://symfony.com"
    },
    {
        "name": "phine/exception",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge-abandoned/lib-exception.git",
            "reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge-abandoned/lib-exception/zipball/150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
            "reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "require-dev": {
            "league/phpunit-coverage-listener": "~1.0"
        },
        "time": "2013-08-27T17:43:25+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Phine\\Exception": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A PHP library for improving the use of exceptions.",
        "homepage": "https://github.com/phine/lib-exception",
        "keywords": [
            "exception"
        ],
        "abandoned": true
    },
    {
        "name": "phpseclib/phpseclib",
        "version": "0.3.0",
        "version_normalized": "0.3.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpseclib/phpseclib.git",
            "reference": "4eaf6db3ab40093ce8e97b37ba2f6f77fe0db6ca"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4eaf6db3ab40093ce8e97b37ba2f6f77fe0db6ca",
            "reference": "4eaf6db3ab40093ce8e97b37ba2f6f77fe0db6ca",
            "shasum": ""
        },
        "require": {
            "php": ">=5.0.0"
        },
        "suggest": {
            "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
            "ext-mcrypt": "Install the Mcrypt extension in order to speed up a wide variety of cryptographic operations.",
            "pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP >= 4.3.3."
        },
        "time": "2012-07-07T22:24:45+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Crypt": "phpseclib/",
                "File": "phpseclib/",
                "Math": "phpseclib/",
                "Net": "phpseclib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "include-path": [
            "phpseclib/"
        ],
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jim Wigginton",
                "email": "terrafrost@php.net",
                "role": "Lead Developer"
            }
        ],
        "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
        "homepage": "http://phpseclib.sourceforge.net",
        "keywords": [
            "BigInteger",
            "aes",
            "asn.1",
            "asn1",
            "crypto",
            "cryptography",
            "encryption",
            "rsa",
            "security",
            "sftp",
            "signature",
            "signing",
            "ssh",
            "x.509",
            "x509"
        ]
    },
    {
        "name": "phine/path",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/box-project/box2-path.git",
            "reference": "139c527514629d6e291cb0c28b2f4833b3dc53aa"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/box-project/box2-path/zipball/139c527514629d6e291cb0c28b2f4833b3dc53aa",
            "reference": "139c527514629d6e291cb0c28b2f4833b3dc53aa",
            "shasum": ""
        },
        "require": {
            "phine/exception": "~1.0",
            "php": ">=5.3.3"
        },
        "require-dev": {
            "league/phpunit-coverage-listener": "~1.0"
        },
        "time": "2013-08-27T23:33:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Phine\\Path": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A PHP library for improving the use of file system paths.",
        "homepage": "https://github.com/phine/lib-path",
        "keywords": [
            "file",
            "path",
            "system"
        ],
        "abandoned": true
    },
    {
        "name": "justinrainbow/json-schema",
        "version": "1.3.0",
        "version_normalized": "1.3.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/justinrainbow/json-schema.git",
            "reference": "93907953341530a748aa88a5380c418eb774b895"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/93907953341530a748aa88a5380c418eb774b895",
            "reference": "93907953341530a748aa88a5380c418eb774b895",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2013-02-18T05:00:27+00:00",
        "bin": [
            "bin/validate-json"
        ],
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "JsonSchema": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Igor Wiedler",
                "email": "igor@wiedler.ch",
                "homepage": "http://wiedler.ch/igor/"
            },
            {
                "name": "Bruno Prieto Reis",
                "email": "bruno.p.reis@gmail.com"
            },
            {
                "name": "Justin Rainbow",
                "email": "justin.rainbow@gmail.com"
            },
            {
                "name": "Robert Schönthal",
                "email": "robert.schoenthal@gmail.com",
                "homepage": "http://digitalkaoz.net"
            }
        ],
        "description": "A library to validate a json schema.",
        "homepage": "https://github.com/justinrainbow/json-schema",
        "keywords": [
            "json",
            "schema"
        ]
    },
    {
        "name": "seld/jsonlint",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/Seldaek/jsonlint.git",
            "reference": "3b4bc2a96ff5d3fe6866bfe9dd0c845246705791"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/3b4bc2a96ff5d3fe6866bfe9dd0c845246705791",
            "reference": "3b4bc2a96ff5d3fe6866bfe9dd0c845246705791",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2012-03-12T07:52:32+00:00",
        "bin": [
            "bin/jsonlint"
        ],
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Seld\\JsonLint": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jordi Boggiano",
                "email": "j.boggiano@seld.be",
                "homepage": "http://seld.be",
                "role": "Developer"
            }
        ],
        "description": "JSON Linter",
        "keywords": [
            "json",
            "linter",
            "parser",
            "validator"
        ]
    },
    {
        "name": "herrera-io/json",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge-php/json.git",
            "reference": "ce3b3840164c51591387690e51106651c4671473"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge-php/json/zipball/ce3b3840164c51591387690e51106651c4671473",
            "reference": "ce3b3840164c51591387690e51106651c4671473",
            "shasum": ""
        },
        "require": {
            "justinrainbow/json-schema": ">=1.0,<2.0-dev",
            "php": ">=5.3.3",
            "seld/jsonlint": ">=1.0,<2.0-dev"
        },
        "require-dev": {
            "herrera-io/phpunit-test-case": "1.*",
            "phpunit/phpunit": "3.7.*"
        },
        "time": "2013-03-08T23:31:40+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "files": [
                "src/lib/json_version.php"
            ],
            "psr-0": {
                "Herrera\\Json": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A library for simplifying JSON linting and validation.",
        "homepage": "http://herrera-io.github.com/php-json",
        "keywords": [
            "json",
            "lint",
            "schema",
            "validate"
        ],
        "abandoned": "kherge/json"
    },
    {
        "name": "kherge/version",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge-abandoned/Version.git",
            "reference": "c9a10b2d889b266aa38243d921a6eaf1be384ce2"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge-abandoned/Version/zipball/c9a10b2d889b266aa38243d921a6eaf1be384ce2",
            "reference": "c9a10b2d889b266aa38243d921a6eaf1be384ce2",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2012-07-25T21:09:21+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "KevinGH\\Version": "src/lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "me@kevingh.com"
            }
        ],
        "description": "A parsing and comparison library for semantic versioning.",
        "homepage": "http://github.com/kherge/Version",
        "abandoned": true
    },
    {
        "name": "herrera-io/phar-update",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge-abandoned/php-phar-update.git",
            "reference": "506d81336ec1f7682fa6652658a744d90c97723a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge-abandoned/php-phar-update/zipball/506d81336ec1f7682fa6652658a744d90c97723a",
            "reference": "506d81336ec1f7682fa6652658a744d90c97723a",
            "shasum": ""
        },
        "require": {
            "herrera-io/json": "1.*",
            "kherge/version": "1.*",
            "php": ">=5.3.3"
        },
        "require-dev": {
            "herrera-io/phpunit-test-case": "1.*",
            "mikey179/vfsstream": "1.1.0",
            "phpunit/phpunit": "3.7.*"
        },
        "time": "2013-03-13T21:57:48+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "files": [
                "src/lib/constants.php"
            ],
            "psr-0": {
                "Herrera\\Phar\\Update": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A library for self-updating Phars.",
        "homepage": "http://herrera-io.github.com/php-phar-update",
        "keywords": [
            "phar",
            "update"
        ],
        "abandoned": true
    },
    {
        "name": "kherge/amend",
        "version": "3.0.0",
        "version_normalized": "3.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge/php-amend.git",
            "reference": "64aeb87394edb797a1fd7eda4ee83503d6b6fe1b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge/php-amend/zipball/64aeb87394edb797a1fd7eda4ee83503d6b6fe1b",
            "reference": "64aeb87394edb797a1fd7eda4ee83503d6b6fe1b",
            "shasum": ""
        },
        "require": {
            "herrera-io/phar-update": "~1.0",
            "php": ">=5.3.3",
            "symfony/console": "~2.1"
        },
        "require-dev": {
            "herrera-io/box": "~1.0",
            "herrera-io/phpunit-test-case": "1.*",
            "phpunit/phpunit": "3.7.*"
        },
        "time": "2013-03-14T21:22:18+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "KevinGH\\Amend": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io/",
                "role": "Developer"
            }
        ],
        "description": "Integrates Phar Update to Symfony Console.",
        "homepage": "http://kherge.github.com/Amend",
        "keywords": [
            "console",
            "phar",
            "update"
        ]
    },
    {
        "name": "tedivm/jshrink",
        "version": "v1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/tedious/JShrink.git",
            "reference": "471d5ca318d452f0b4d6f328dade1cd3094517ef"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/tedious/JShrink/zipball/471d5ca318d452f0b4d6f328dade1cd3094517ef",
            "reference": "471d5ca318d452f0b4d6f328dade1cd3094517ef",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "require-dev": {
            "fabpot/php-cs-fixer": "0.4.0",
            "phpunit/phpunit": "4.0.*",
            "satooshi/php-coveralls": "dev-master"
        },
        "time": "2014-05-05T20:44:19+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "JShrink": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Robert Hafner",
                "email": "tedivm@tedivm.com"
            }
        ],
        "description": "Javascript Minifier built in PHP",
        "homepage": "http://github.com/tedivm/JShrink",
        "keywords": [
            "javascript",
            "minifier"
        ]
    },
    {
        "name": "herrera-io/box",
        "version": "1.6.0",
        "version_normalized": "1.6.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/box-project/box2-lib.git",
            "reference": "9ed245926523a1b3dc531f201e6b4ff6b642d0c1"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/box-project/box2-lib/zipball/9ed245926523a1b3dc531f201e6b4ff6b642d0c1",
            "reference": "9ed245926523a1b3dc531f201e6b4ff6b642d0c1",
            "shasum": ""
        },
        "require": {
            "ext-phar": "*",
            "phine/path": "~1.0",
            "php": ">=5.3.3",
            "tedivm/jshrink": "~1.0"
        },
        "require-dev": {
            "herrera-io/annotations": "~1.0",
            "herrera-io/phpunit-test-case": "1.*",
            "mikey179/vfsstream": "1.1.0",
            "phpseclib/phpseclib": "~0.3",
            "phpunit/phpunit": "3.7.*"
        },
        "suggest": {
            "herrera-io/annotations": "For compacting annotated docblocks.",
            "phpseclib/phpseclib": "For verifying OpenSSL signed phars without the phar extension."
        },
        "time": "2014-12-03T22:34:14+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Herrera\\Box": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A library for simplifying the PHAR build process.",
        "homepage": "https://github.com/box-project/box2-lib",
        "keywords": [
            "phar"
        ]
    },
    {
        "name": "doctrine/lexer",
        "version": "v1.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/doctrine/lexer.git",
            "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb",
            "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.2"
        },
        "time": "2013-01-12T18:59:04+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Doctrine\\Common\\Lexer\\": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Guilherme Blanco",
                "email": "guilhermeblanco@gmail.com",
                "homepage": "http://www.instaclick.com"
            },
            {
                "name": "Roman Borschel",
                "email": "roman@code-factory.org"
            },
            {
                "name": "Johannes Schmitt",
                "email": "schmittjoh@gmail.com",
                "homepage": "https://github.com/schmittjoh",
                "role": "Developer of wrapped JMSSerializerBundle"
            }
        ],
        "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
        "homepage": "http://www.doctrine-project.org",
        "keywords": [
            "lexer",
            "parser"
        ]
    },
    {
        "name": "doctrine/annotations",
        "version": "v1.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/doctrine/annotations.git",
            "reference": "fae359b3efd908e407a0105ff8956b5c94ddca8e"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/doctrine/annotations/zipball/fae359b3efd908e407a0105ff8956b5c94ddca8e",
            "reference": "fae359b3efd908e407a0105ff8956b5c94ddca8e",
            "shasum": ""
        },
        "require": {
            "doctrine/lexer": "1.*",
            "php": ">=5.3.2"
        },
        "require-dev": {
            "doctrine/cache": "1.*"
        },
        "time": "2013-01-12T19:23:32+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Doctrine\\Common\\Annotations\\": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jonathan Wage",
                "email": "jonwage@gmail.com",
                "homepage": "http://www.jwage.com/",
                "role": "Creator"
            },
            {
                "name": "Guilherme Blanco",
                "email": "guilhermeblanco@gmail.com",
                "homepage": "http://www.instaclick.com"
            },
            {
                "name": "Roman Borschel",
                "email": "roman@code-factory.org"
            },
            {
                "name": "Benjamin Eberlei",
                "email": "kontakt@beberlei.de"
            },
            {
                "name": "Johannes Schmitt",
                "email": "schmittjoh@gmail.com",
                "homepage": "https://github.com/schmittjoh",
                "role": "Developer of wrapped JMSSerializerBundle"
            }
        ],
        "description": "Docblock Annotations Parser",
        "homepage": "http://www.doctrine-project.org",
        "keywords": [
            "annotations",
            "docblock",
            "parser"
        ]
    },
    {
        "name": "herrera-io/annotations",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/kherge-abandoned/php-annotations.git",
            "reference": "43838d74a258e7305f98dea4eafe09c14389e5d0"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/kherge-abandoned/php-annotations/zipball/43838d74a258e7305f98dea4eafe09c14389e5d0",
            "reference": "43838d74a258e7305f98dea4eafe09c14389e5d0",
            "shasum": ""
        },
        "require": {
            "doctrine/annotations": "~1.0",
            "php": ">=5.3.3"
        },
        "require-dev": {
            "herrera-io/phpunit-test-case": "1.*",
            "phpunit/phpunit": "3.7.*"
        },
        "time": "2013-08-09T22:12:24+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Herrera\\Annotations": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A tokenizer for Doctrine annotations.",
        "homepage": "https://github.com/herrera-io/php-annotations",
        "keywords": [
            "annotations",
            "doctrine",
            "tokenizer"
        ],
        "abandoned": true
    },
    {
        "name": "kherge/box",
        "version": "2.5.0",
        "version_normalized": "2.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/box-project/box2.git",
            "reference": "2cdd3de72de4382223cf8dbf83b49d81249bad19"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/box-project/box2/zipball/2cdd3de72de4382223cf8dbf83b49d81249bad19",
            "reference": "2cdd3de72de4382223cf8dbf83b49d81249bad19",
            "shasum": ""
        },
        "require": {
            "herrera-io/annotations": "~1.0",
            "herrera-io/box": "~1.6",
            "herrera-io/json": "~1.0",
            "justinrainbow/json-schema": "~1.3",
            "kherge/amend": "~3.0",
            "phine/path": "~1.0",
            "php": ">=5.3.3",
            "phpseclib/phpseclib": "~0.3",
            "symfony/console": "~2.1",
            "symfony/finder": "~2.1",
            "symfony/process": "~2.1"
        },
        "require-dev": {
            "herrera-io/phpunit-test-case": "1.*",
            "mikey179/vfsstream": "1.1.0",
            "phpunit/phpunit": "3.7.*"
        },
        "suggest": {
            "ext-openssl": "To accelerate private key generation."
        },
        "time": "2014-12-03T23:19:13+00:00",
        "bin": [
            "bin/box"
        ],
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "KevinGH\\Box": "src/lib"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Kevin Herrera",
                "email": "kevin@herrera.io",
                "homepage": "http://kevin.herrera.io"
            }
        ],
        "description": "A tool to simplify building PHARs.",
        "homepage": "http://box-project.org",
        "keywords": [
            "phar"
        ]
    }
]
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Seld\\JsonLint' => array($vendorDir . '/seld/jsonlint/src'),
    'Pimple' => array($vendorDir . '/pimple/pimple/src'),
    'Phine\\Path' => array($vendorDir . '/phine/path/src/lib'),
    'Phine\\Exception' => array($vendorDir . '/phine/exception/src/lib'),
    'Net' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
    'Math' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
    'KevinGH\\Version' => array($vendorDir . '/kherge/version/src/lib'),
    'KevinGH\\Box' => array($vendorDir . '/kherge/box/src/lib'),
    'KevinGH\\Amend' => array($vendorDir . '/kherge/amend/src/lib'),
    'JsonSchema' => array($vendorDir . '/justinrainbow/json-schema/src'),
    'JShrink' => array($vendorDir . '/tedivm/jshrink/src'),
    'Herrera\\Phar\\Update' => array($vendorDir . '/herrera-io/phar-update/src/lib'),
    'Herrera\\Json' => array($vendorDir . '/herrera-io/json/src/lib'),
    'Herrera\\Box' => array($vendorDir . '/herrera-io/box/src/lib'),
    'Herrera\\Annotations' => array($vendorDir . '/herrera-io/annotations/src/lib'),
    'File' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
    'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib'),
    'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib'),
    'Crypt' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
    'Bcn\\Component\\Json\\' => array($vendorDir . '/bcncommerce/json-stream'),
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Sstalle\\php7cc\\' => array($baseDir . '/src'),
);
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
);
<?php

// include_paths.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    $vendorDir . '/phpseclib/phpseclib/phpseclib',
);
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    '742b7e606e92b28dd726e835467f413a' => $vendorDir . '/herrera-io/json/src/lib/json_version.php',
    'f0e9d233388e461ee3c460665eb265f0' => $vendorDir . '/herrera-io/phar-update/src/lib/constants.php',
    'fe1bcd0336136e435eaf197895daf81a' => $vendorDir . '/nikic/php-parser/lib/bootstrap.php',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa
{
    public static $files = array (
        '742b7e606e92b28dd726e835467f413a' => __DIR__ . '/..' . '/herrera-io/json/src/lib/json_version.php',
        'f0e9d233388e461ee3c460665eb265f0' => __DIR__ . '/..' . '/herrera-io/phar-update/src/lib/constants.php',
        'fe1bcd0336136e435eaf197895daf81a' => __DIR__ . '/..' . '/nikic/php-parser/lib/bootstrap.php',
    );

    public static $prefixLengthsPsr4 = array (
        'S' => 
        array (
            'Sstalle\\php7cc\\' => 15,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'Sstalle\\php7cc\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'S' => 
        array (
            'Symfony\\Component\\Process\\' => 
            array (
                0 => __DIR__ . '/..' . '/symfony/process',
            ),
            'Symfony\\Component\\Finder\\' => 
            array (
                0 => __DIR__ . '/..' . '/symfony/finder',
            ),
            'Symfony\\Component\\Console\\' => 
            array (
                0 => __DIR__ . '/..' . '/symfony/console',
            ),
            'Seld\\JsonLint' => 
            array (
                0 => __DIR__ . '/..' . '/seld/jsonlint/src',
            ),
        ),
        'P' => 
        array (
            'Pimple' => 
            array (
                0 => __DIR__ . '/..' . '/pimple/pimple/src',
            ),
            'Phine\\Path' => 
            array (
                0 => __DIR__ . '/..' . '/phine/path/src/lib',
            ),
            'Phine\\Exception' => 
            array (
                0 => __DIR__ . '/..' . '/phine/exception/src/lib',
            ),
        ),
        'N' => 
        array (
            'Net' => 
            array (
                0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
            ),
        ),
        'M' => 
        array (
            'Math' => 
            array (
                0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
            ),
        ),
        'K' => 
        array (
            'KevinGH\\Version' => 
            array (
                0 => __DIR__ . '/..' . '/kherge/version/src/lib',
            ),
            'KevinGH\\Box' => 
            array (
                0 => __DIR__ . '/..' . '/kherge/box/src/lib',
            ),
            'KevinGH\\Amend' => 
            array (
                0 => __DIR__ . '/..' . '/kherge/amend/src/lib',
            ),
        ),
        'J' => 
        array (
            'JsonSchema' => 
            array (
                0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src',
            ),
            'JShrink' => 
            array (
                0 => __DIR__ . '/..' . '/tedivm/jshrink/src',
            ),
        ),
        'H' => 
        array (
            'Herrera\\Phar\\Update' => 
            array (
                0 => __DIR__ . '/..' . '/herrera-io/phar-update/src/lib',
            ),
            'Herrera\\Json' => 
            array (
                0 => __DIR__ . '/..' . '/herrera-io/json/src/lib',
            ),
            'Herrera\\Box' => 
            array (
                0 => __DIR__ . '/..' . '/herrera-io/box/src/lib',
            ),
            'Herrera\\Annotations' => 
            array (
                0 => __DIR__ . '/..' . '/herrera-io/annotations/src/lib',
            ),
        ),
        'F' => 
        array (
            'File' => 
            array (
                0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
            ),
        ),
        'D' => 
        array (
            'Doctrine\\Common\\Lexer\\' => 
            array (
                0 => __DIR__ . '/..' . '/doctrine/lexer/lib',
            ),
            'Doctrine\\Common\\Annotations\\' => 
            array (
                0 => __DIR__ . '/..' . '/doctrine/annotations/lib',
            ),
        ),
        'C' => 
        array (
            'Crypt' => 
            array (
                0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
            ),
        ),
        'B' => 
        array (
            'Bcn\\Component\\Json\\' => 
            array (
                0 => __DIR__ . '/..' . '/bcncommerce/json-stream',
            ),
        ),
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa::$prefixesPsr0;

        }, null, ClassLoader::class);
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit4dc88d039c3303ecad17ebc1942bf9aa
{
    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('ComposerAutoloaderInit4dc88d039c3303ecad17ebc1942bf9aa', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit4dc88d039c3303ecad17ebc1942bf9aa', 'loadClassLoader'));

        $includePaths = require __DIR__ . '/include_paths.php';
        array_push($includePaths, get_include_path());
        set_include_path(implode(PATH_SEPARATOR, $includePaths));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa::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\ComposerStaticInit4dc88d039c3303ecad17ebc1942bf9aa::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire4dc88d039c3303ecad17ebc1942bf9aa($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire4dc88d039c3303ecad17ebc1942bf9aa($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?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;
    private $missingClasses = array();
    private $apcuPrefix;

    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;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath.'\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        $length = $this->prefixLengthsPsr4[$first][$search];
                        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;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}

Copyright (c) Nils Adermann, Jordi Boggiano

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.

* 3.0.0 (2014-07-24)

 * removed the Pimple class alias (use Pimple\Container instead)

* 2.1.1 (2014-07-24)

 * fixed compiler warnings for the C extension
 * fixed code when dealing with circular references

* 2.1.0 (2014-06-24)

 * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a
   deprecated alias which will be removed in Pimple 3.0)
 * added Pimple\ServiceProviderInterface (and Pimple::register())

* 2.0.0 (2014-02-10)

 * changed extend to automatically re-assign the extended service and keep it as shared or factory
   (to keep BC, extend still returns the extended service)
 * changed services to be shared by default (use factory() for factory
   services)

* 1.0.0

 * initial version
Copyright (c) 2009-2014 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Pimple
======

.. caution::

    This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read
    the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good
    way to learn more about how to create a simple Dependency Injection
    Container (recent versions of Pimple are more focused on performance).

Pimple is a small Dependency Injection Container for PHP.

Installation
------------

Before using Pimple in your project, add it to your ``composer.json`` file:

.. code-block:: bash

    $ ./composer.phar require pimple/pimple ~3.0

Alternatively, Pimple is also available as a PHP C extension:

.. code-block:: bash

    $ cd ext/pimple
    $ phpize
    $ ./configure
    $ make
    $ make install

Usage
-----

Creating a container is a matter of creating a ``Container`` instance:

.. code-block:: php

    use Pimple\Container;

    $container = new Container();

As many other dependency injection containers, Pimple manages two different
kind of data: **services** and **parameters**.

Defining Services
~~~~~~~~~~~~~~~~~

A service is an object that does something as part of a larger system. Examples
of services: a database connection, a templating engine, or a mailer. Almost
any **global** object can be a service.

Services are defined by **anonymous functions** that return an instance of an
object:

.. code-block:: php

    // define some services
    $container['session_storage'] = function ($c) {
        return new SessionStorage('SESSION_ID');
    };

    $container['session'] = function ($c) {
        return new Session($c['session_storage']);
    };

Notice that the anonymous function has access to the current container
instance, allowing references to other services or parameters.

As objects are only created when you get them, the order of the definitions
does not matter.

Using the defined services is also very easy:

.. code-block:: php

    // get the session object
    $session = $container['session'];

    // the above call is roughly equivalent to the following code:
    // $storage = new SessionStorage('SESSION_ID');
    // $session = new Session($storage);

Defining Factory Services
~~~~~~~~~~~~~~~~~~~~~~~~~

By default, each time you get a service, Pimple returns the **same instance**
of it. If you want a different instance to be returned for all calls, wrap your
anonymous function with the ``factory()`` method

.. code-block:: php

    $container['session'] = $container->factory(function ($c) {
        return new Session($c['session_storage']);
    });

Now, each call to ``$container['session']`` returns a new instance of the
session.

Defining Parameters
~~~~~~~~~~~~~~~~~~~

Defining a parameter allows to ease the configuration of your container from
the outside and to store global values:

.. code-block:: php

    // define some parameters
    $container['cookie_name'] = 'SESSION_ID';
    $container['session_storage_class'] = 'SessionStorage';

If you change the ``session_storage`` service definition like below:

.. code-block:: php

    $container['session_storage'] = function ($c) {
        return new $c['session_storage_class']($c['cookie_name']);
    };

You can now easily change the cookie name by overriding the
``session_storage_class`` parameter instead of redefining the service
definition.

Protecting Parameters
~~~~~~~~~~~~~~~~~~~~~

Because Pimple sees anonymous functions as service definitions, you need to
wrap anonymous functions with the ``protect()`` method to store them as
parameters:

.. code-block:: php

    $container['random_func'] = $container->protect(function () {
        return rand();
    });

Modifying Services after Definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In some cases you may want to modify a service definition after it has been
defined. You can use the ``extend()`` method to define additional code to be
run on your service just after it is created:

.. code-block:: php

    $container['session_storage'] = function ($c) {
        return new $c['session_storage_class']($c['cookie_name']);
    };

    $container->extend('session_storage', function ($storage, $c) {
        $storage->...();

        return $storage;
    };

The first argument is the name of the service to extend, the second a function
that gets access to the object instance and the container.

Extending a Container
~~~~~~~~~~~~~~~~~~~~~

If you use the same libraries over and over, you might want to reuse some
services from one project to the next one; package your services into a
**provider** by implementing ``Pimple\ServiceProviderInterface``:

.. code-block:: php

    use Pimple\Container;

    class FooProvider implements Pimple\ServiceProviderInterface
    {
        public function register(Container $pimple)
        {
            // register some services and parameters
            // on $pimple
        }
    }

Then, register the provider on a Container:

.. code-block:: php

    $pimple->register(new FooProvider());

Fetching the Service Creation Function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When you access an object, Pimple automatically calls the anonymous function
that you defined, which creates the service object for you. If you want to get
raw access to this function, you can use the ``raw()`` method:

.. code-block:: php

    $container['session'] = function ($c) {
        return new Session($c['session_storage']);
    };

    $sessionFunction = $container->raw('session');

.. _Pimple 1.x documentation: https://github.com/fabpot/Pimple/tree/1.1
{
    "name": "pimple/pimple",
    "type": "library",
    "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
    "keywords": ["dependency injection", "container"],
    "homepage": "http://pimple.sensiolabs.org",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        }
    ],
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-0": { "Pimple": "src/" }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.0.x-dev"
        }
    }
}
This is Pimple 2 implemented in C

* PHP >= 5.3
* Not tested under Windows, might work

Install
=======

    > phpize
    > ./configure
    > make
    > make install
dnl $Id$
dnl config.m4 for extension pimple

dnl Comments in this file start with the string 'dnl'.
dnl Remove where necessary. This file will not work
dnl without editing.

dnl If your extension references something external, use with:

dnl PHP_ARG_WITH(pimple, for pimple support,
dnl Make sure that the comment is aligned:
dnl [  --with-pimple             Include pimple support])

dnl Otherwise use enable:

PHP_ARG_ENABLE(pimple, whether to enable pimple support,
dnl Make sure that the comment is aligned:
[  --enable-pimple           Enable pimple support])

if test "$PHP_PIMPLE" != "no"; then
  dnl Write more examples of tests here...

  dnl # --with-pimple -> check with-path
  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
  dnl SEARCH_FOR="/include/pimple.h"  # you most likely want to change this
  dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter
  dnl   PIMPLE_DIR=$PHP_PIMPLE
  dnl else # search default path list
  dnl   AC_MSG_CHECKING([for pimple files in default path])
  dnl   for i in $SEARCH_PATH ; do
  dnl     if test -r $i/$SEARCH_FOR; then
  dnl       PIMPLE_DIR=$i
  dnl       AC_MSG_RESULT(found in $i)
  dnl     fi
  dnl   done
  dnl fi
  dnl
  dnl if test -z "$PIMPLE_DIR"; then
  dnl   AC_MSG_RESULT([not found])
  dnl   AC_MSG_ERROR([Please reinstall the pimple distribution])
  dnl fi

  dnl # --with-pimple -> add include path
  dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include)

  dnl # --with-pimple -> check for lib and symbol presence
  dnl LIBNAME=pimple # you may want to change this
  dnl LIBSYMBOL=pimple # you most likely want to change this 

  dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  dnl [
  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD)
  dnl   AC_DEFINE(HAVE_PIMPLELIB,1,[ ])
  dnl ],[
  dnl   AC_MSG_ERROR([wrong pimple lib version or lib not found])
  dnl ],[
  dnl   -L$PIMPLE_DIR/lib -lm
  dnl ])
  dnl
  dnl PHP_SUBST(PIMPLE_SHARED_LIBADD)

  PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared)
fi
// $Id$
// vim:ft=javascript

// If your extension references something external, use ARG_WITH
// ARG_WITH("pimple", "for pimple support", "no");

// Otherwise, use ARG_ENABLE
// ARG_ENABLE("pimple", "enable pimple support", "no");

if (PHP_PIMPLE != "no") {
	EXTENSION("pimple", "pimple.c");
}


/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2014 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef PHP_PIMPLE_H
#define PHP_PIMPLE_H

extern zend_module_entry pimple_module_entry;
#define phpext_pimple_ptr &pimple_module_entry

#ifdef PHP_WIN32
#	define PHP_PIMPLE_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#	define PHP_PIMPLE_API __attribute__ ((visibility("default")))
#else
#	define PHP_PIMPLE_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

#define PIMPLE_VERSION "3.0.0"
#define PIMPLE_NS "Pimple"

#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM   5
#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10

PHP_MINIT_FUNCTION(pimple);
PHP_MSHUTDOWN_FUNCTION(pimple);
PHP_RINIT_FUNCTION(pimple);
PHP_RSHUTDOWN_FUNCTION(pimple);
PHP_MINFO_FUNCTION(pimple);

PHP_METHOD(Pimple, __construct);
PHP_METHOD(Pimple, factory);
PHP_METHOD(Pimple, protect);
PHP_METHOD(Pimple, raw);
PHP_METHOD(Pimple, extend);
PHP_METHOD(Pimple, keys);
PHP_METHOD(Pimple, register);
PHP_METHOD(Pimple, offsetSet);
PHP_METHOD(Pimple, offsetUnset);
PHP_METHOD(Pimple, offsetGet);
PHP_METHOD(Pimple, offsetExists);

PHP_METHOD(PimpleClosure, __invoke);

typedef struct _pimple_bucket_value {
	zval *value; /* Must be the first element */
	zval *raw;
	zend_fcall_info_cache *fcc;
	zend_object_handle handle_num;
	enum {
		PIMPLE_IS_PARAM   = 0,
		PIMPLE_IS_SERVICE = 2
	} type;
	zend_bool initialized;
} pimple_bucket_value;

typedef struct _pimple_object {
	zend_object zobj;
	HashTable values;
	HashTable factories;
	HashTable protected;
} pimple_object;

typedef struct _pimple_closure_object {
	zend_object zobj;
	zval *callable;
	zval *factory;
} pimple_closure_object;

static const char sensiolabs_logo[] = "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHYAAAAUCAMAAABvRTlyAAAAz1BMVEUAAAAAAAAAAAAsThWB5j4AAACD6T8AAACC6D+C6D6C6D+C6D4AAAAAAACC6D4AAAAAAACC6D8AAAAAAAAAAAAAAAAAAAAAAACC6D4AAAAAAAAAAACC6D4AAAAAAAAAAAAAAAAAAAAAAACC6D8AAACC6D4AAAAAAAAAAAAAAAAAAACC6D8AAACC6D6C6D+B6D+C6D+C6D+C6D8AAACC6D6C6D4AAACC6D/K/2KC6D+B6D6C6D6C6D+C6D8sTxUyWRhEeiEAAACC6D+C5z6B6D7drnEVAAAAQXRSTlMAE3oCNSUuDHFHzxaF9UFsu+irX+zlKzYimaJXktyOSFD6BolxqT7QGMMdarMIpuO28r9EolXKgR16OphfXYd4V14GtB4AAAMpSURBVEjHvVSJctowEF1jjME2RziMwUCoMfd9heZqG4n//6buLpJjkmYm03byZmxJa2nf6u2uQcG2bfhqRN4LoTKBzyGDm68M7mAwcOEdjo4zhA/Rf9Go/CVtTgiRhXfIC3EDH8F/eUX1/9KexRo+QgOdtHDsEe/sM7QT32/+K61Z1LFXcXJxN4pTbu1aTQUzuy2PIA0rDo0/0Aa5XFaJvKaVTrubywXvaa1Wq4Vu/Snr3Y7Aojh4VccwykW2N2oQ8wmjyut6+Q1t5ywIG5Npj1sh5E0B7YOzFDjfuRfaOh3O+MbbVNfTWS9COZk3Obd2su5d0a6IU9KLREbw8gEehWSr1r2sPWciXLG38r5NdW0xu9eioU87omjC9yNaMi5GNf6WppVSOqXCFkmCvMB3p9SROLoYQn5pDgQOujA1xjYvqH+plUdkwnmII8VxR/PKYkrfLLomhVlE3b/LhNbNr7hp0H2JaOc4v8dFB58HSsFTSafaqtY1sT3GO8wsy5rhokYPlRJdjPMajyYqTt1EHF/2uqSWQWmAjCUSmQ1MS3g8Btf1XOsy7YIC0CB1b5Xw1Vhba0zbxiCAQLH9TNPmHJXQUtJAN0KcDsoqLxsNvJrJExa7mKIdp2lRE2WexiS4pqWk/0jROlw6K6bV9YOBDGAuqMJ0bnuUKGB0L27bxgRhGEbzihbhxxXaQC88Vkwq8ldCi86RApWUb0Q+4VDosBCc+1s81lUdnBavH4Zp2mm3O44USwOfvSo9oBiwpFg71lMS1VKJLKljS3j9p+fOTvXXlsSNuEv6YPaZda9uRope0VJfKdo7fPiYfSmvFjXQbkhY0d9hCbBWIktRgEDieDhf1N3wbbkmNNgRy8hyl620yGQat/grV3HMpc2HDKTVmOPFz6ylPCKt/nXcAyV260jaAowwIW0YuBzrOgb/KrddZS9OmJaLgpWK4JX2DDuklcLZSDGcn8Vmx9YDNvT6UsjyBApRyFQVX7Vxm9TGxE16nmfRd8/zQoDmggQOTRh5Hv8pMt9Q/L2JmSwkMCE7dA4BuDjHJwfu0Om4QAhOjrN5XkIatglfiN/bUPdCQFjTYgAAAABJRU5ErkJggg==\">";

static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC);
static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC);

static void pimple_init_bucket(pimple_bucket_value *bucket);
static void pimple_bucket_dtor(pimple_bucket_value *bucket);
static void pimple_free_bucket(pimple_bucket_value *bucket);

static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC);
static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC);
static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC);
static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC);
static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC);
static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC);

static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC);
static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC);
static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC);

#ifdef ZTS
#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v)
#else
#define PIMPLE_G(v) (pimple_globals.v)
#endif

#endif	/* PHP_PIMPLE_H */


/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2014 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_pimple.h"
#include "pimple_compat.h"
#include "zend_interfaces.h"
#include "zend.h"
#include "ext/spl/spl_exceptions.h"
#include "Zend/zend_exceptions.h"
#include "main/php_output.h"
#include "SAPI.h"

static zend_class_entry *pimple_ce;
static zend_object_handlers pimple_object_handlers;
static zend_class_entry *pimple_closure_ce;
static zend_class_entry *pimple_serviceprovider_ce;
static zend_object_handlers pimple_closure_object_handlers;

#define FETCH_DIM_HANDLERS_VARS 	pimple_object *pimple_obj = NULL; \
									ulong index; \
									pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \

#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS	do { \
	if (ce != pimple_ce) { \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \
		if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \
			pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
		} \
	} else { \
		pimple_object_handlers.read_dimension = pimple_object_read_dimension; \
		pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
		pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
		pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
	}\
											} while(0);

#define PIMPLE_CALL_CB	do { \
			zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \
			fci.size           = sizeof(fci); \
			fci.object_ptr     = retval->fcc->object_ptr; \
			fci.function_name  = retval->value; \
			fci.no_separation  = 1; \
			fci.retval_ptr_ptr = &retval_ptr_ptr; \
\
			zend_call_function(&fci, retval->fcc TSRMLS_CC); \
			efree(fci.params); \
			if (EG(exception)) { \
				return EG(uninitialized_zval_ptr); \
			} \
						} while(0);

ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0)
ZEND_ARG_ARRAY_INFO(0, value, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1)
ZEND_ARG_INFO(0, id)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2)
ZEND_ARG_INFO(0, id)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0)
ZEND_ARG_ARRAY_INFO(0, values, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_pimpleclosure___invoke, 0, 0, 1)
ZEND_ARG_INFO(0, callarg)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0)
ZEND_END_ARG_INFO()

static const zend_function_entry pimple_ce_functions[] = {
	PHP_ME(Pimple, __construct,	arginfo___construct, ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, factory,         arginfo_factory,         ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, protect,         arginfo_protect,         ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, raw,             arginfo_raw,             ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, extend,          arginfo_extend,          ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, keys,            arginfo_keys,            ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, register,		arginfo_register,		 ZEND_ACC_PUBLIC)

	PHP_ME(Pimple, offsetSet,       arginfo_offsetset,       ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetGet,       arginfo_offsetget,       ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetExists,    arginfo_offsetexists,    ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetUnset,     arginfo_offsetunset,     ZEND_ACC_PUBLIC)
	PHP_FE_END
};

static const zend_function_entry pimple_closure_ce_functions[] = {
	PHP_ME(PimpleClosure, __invoke,     arginfo_pimpleclosure___invoke,    ZEND_ACC_PRIVATE)
	PHP_FE_END
};

static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = {
	PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register)
	PHP_FE_END
};

static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC)
{
	zend_object_std_dtor(&obj->zobj TSRMLS_CC);
	if (obj->factory) {
		zval_ptr_dtor(&obj->factory);
	}
	if (obj->callable) {
		zval_ptr_dtor(&obj->callable);
	}
	efree(obj);
}

static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC)
{
	zend_hash_destroy(&obj->factories);
	zend_hash_destroy(&obj->protected);
	zend_hash_destroy(&obj->values);
	zend_object_std_dtor(&obj->zobj TSRMLS_CC);
	efree(obj);
}

static void pimple_free_bucket(pimple_bucket_value *bucket)
{
	if (bucket->fcc) {
		efree(bucket->fcc);
		bucket->fcc = NULL;
	}
	if (bucket->raw) {
		zval_ptr_dtor(&bucket->raw);
	}
}

static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC)
{
	zend_object_value retval;
	pimple_closure_object *pimple_closure_obj = NULL;

	pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object));
	ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce);

	pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor;
	retval.handlers = &pimple_closure_object_handlers;
	retval.handle   = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC);

	return retval;
}

static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC)
{
	zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated");

	return NULL;
}

static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC)
{
	zend_object_value retval;
	pimple_object *pimple_obj  = NULL;
	zend_function *function    = NULL;

	pimple_obj = emalloc(sizeof(pimple_object));
	ZEND_OBJ_INIT(&pimple_obj->zobj, ce);

	PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS

	retval.handlers = &pimple_object_handlers;
	retval.handle   = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC);

	zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
	zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
	zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);

	return retval;
}

static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value pimple_value = {0}, *found_value = NULL;
	ulong hash;

	pimple_init_bucket(&pimple_value);
	pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC);

	if (!offset) {/* $p[] = 'foo' when not overloaded */
		zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(value);
		return;
	}

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value);
		if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
			pimple_free_bucket(&pimple_value);
			zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%s\".", Z_STRVAL_P(offset));
			return;
		}
		if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
			pimple_free_bucket(&pimple_value);
			return;
		}
		Z_ADDREF_P(value);
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value);
		if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
			pimple_free_bucket(&pimple_value);
			zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%ld\".", index);
			return;
		}
		if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
			pimple_free_bucket(&pimple_value);
			return;
		}
		Z_ADDREF_P(value);
	break;
	case IS_NULL: /* $p[] = 'foo' when overloaded */
		zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(value);
	break;
	default:
		pimple_free_bucket(&pimple_value);
		zend_error(E_WARNING, "Unsupported offset type");
	}
}

static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		zend_hash_index_del(&pimple_obj->values, index);
		zend_hash_index_del(&pimple_obj->factories, index);
		zend_hash_index_del(&pimple_obj->protected, index);
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
	}
}

static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value *retval = NULL;

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) {
			switch (check_empty) {
			case 0: /* isset */
				return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */
			case 1: /* empty */
			default:
				return zend_is_true(retval->value);
			}
		}
		return 0;
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) {
			switch (check_empty) {
				case 0: /* isset */
					return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/
				case 1: /* empty */
				default:
					return zend_is_true(retval->value);
			}
		}
		return 0;
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
		return 0;
	}
}

static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value *retval = NULL;
	zend_fcall_info fci         = {0};
	zval *retval_ptr_ptr        = NULL;

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) {
			zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
			return EG(uninitialized_zval_ptr);
		}
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) {
			return EG(uninitialized_zval_ptr);
		}
	break;
	case IS_NULL: /* $p[][3] = 'foo' first dim access */
		return EG(uninitialized_zval_ptr);
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
		return EG(uninitialized_zval_ptr);
	}

	if(retval->type == PIMPLE_IS_PARAM) {
		return retval->value;
	}

	if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) {
		/* Service is protected, return the value every time */
		return retval->value;
	}

	if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) {
		/* Service is a factory, call it everytime and never cache its result */
		PIMPLE_CALL_CB
		Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */
		return retval_ptr_ptr;
	}

	if (retval->initialized == 1) {
		/* Service has already been called, return its cached value */
		return retval->value;
	}

	ALLOC_INIT_ZVAL(retval->raw);
	MAKE_COPY_ZVAL(&retval->value, retval->raw);

	PIMPLE_CALL_CB

	retval->initialized = 1;
	zval_ptr_dtor(&retval->value);
	retval->value = retval_ptr_ptr;

	return retval->value;
}

static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
{
	if (Z_TYPE_P(_zval) != IS_OBJECT) {
		return FAILURE;
	}

	if (_pimple_bucket_value->fcc->called_scope) {
		return SUCCESS;
	}

	if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc->calling_scope, &_pimple_bucket_value->fcc->function_handler, &_pimple_bucket_value->fcc->object_ptr TSRMLS_CC) == SUCCESS) {
		_pimple_bucket_value->fcc->called_scope = _pimple_bucket_value->fcc->calling_scope;
		return SUCCESS;
	} else {
		return FAILURE;
	}
}

static void pimple_init_bucket(pimple_bucket_value *bucket)
{
	memset(bucket, 0, sizeof(pimple_bucket_value));
	bucket->fcc = ecalloc(1, sizeof(zend_fcall_info_cache));
}

static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
{
	_pimple_bucket_value->value = _zval;

	if (Z_TYPE_P(_zval) != IS_OBJECT) {
		return PIMPLE_IS_PARAM;
	}

	if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) {
		_pimple_bucket_value->type       = PIMPLE_IS_SERVICE;
		_pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval);
	}

	return PIMPLE_IS_SERVICE;
}

static void pimple_bucket_dtor(pimple_bucket_value *bucket)
{
	zval_ptr_dtor(&bucket->value);
	pimple_free_bucket(bucket);
}

PHP_METHOD(Pimple, protect)
{
	zval *protected     = NULL;
	pimple_object *pobj = NULL;
	pimple_bucket_value bucket = {0};

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) {
		return;
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC);
		return;
	}

	pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC);
	pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);

	if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
		RETURN_ZVAL(protected, 1 , 0);
	} else {
		pimple_free_bucket(&bucket);
	}
	RETURN_FALSE;
}

PHP_METHOD(Pimple, raw)
{
	zval *offset = NULL;
	pimple_object *pobj        = NULL;
	pimple_bucket_value *value = NULL;
	ulong index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	switch (Z_TYPE_P(offset)) {
		case IS_STRING:
			if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
		break;
		case IS_DOUBLE:
		case IS_BOOL:
		case IS_LONG:
			if (Z_TYPE_P(offset) == IS_DOUBLE) {
				index = (ulong)Z_DVAL_P(offset);
			} else {
				index = Z_LVAL_P(offset);
			}
			if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
				RETURN_NULL();
			}
		break;
		case IS_NULL:
		default:
			zend_error(E_WARNING, "Unsupported offset type");
	}

	if (value->raw) {
		RETVAL_ZVAL(value->raw, 1, 0);
	} else {
		RETVAL_ZVAL(value->value, 1, 0);
	}
}

PHP_METHOD(Pimple, extend)
{
	zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL;
	pimple_bucket_value bucket = {0}, *value = NULL;
	pimple_object *pobj          = NULL;
	pimple_closure_object *pcobj = NULL;
	ulong index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	switch (Z_TYPE_P(offset)) {
		case IS_STRING:
			if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
			if (value->type != PIMPLE_IS_SERVICE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" does not contain an object definition.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
		break;
		case IS_DOUBLE:
		case IS_BOOL:
		case IS_LONG:
			if (Z_TYPE_P(offset) == IS_DOUBLE) {
				index = (ulong)Z_DVAL_P(offset);
			} else {
				index = Z_LVAL_P(offset);
			}
			if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" is not defined.", index);
				RETURN_NULL();
			}
			if (value->type != PIMPLE_IS_SERVICE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" does not contain an object definition.", index);
				RETURN_NULL();
			}
		break;
		case IS_NULL:
		default:
			zend_error(E_WARNING, "Unsupported offset type");
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
		RETURN_NULL();
	}
	pimple_free_bucket(&bucket);

	ALLOC_INIT_ZVAL(pimple_closure_obj);
	object_init_ex(pimple_closure_obj, pimple_closure_ce);

	pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC);
	pcobj->callable = callable;
	pcobj->factory  = value->value;
	Z_ADDREF_P(callable);
	Z_ADDREF_P(value->value);

	if (zend_hash_index_exists(&pobj->factories, value->handle_num)) {
		pimple_init_bucket(&bucket);
		pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC);
		zend_hash_index_del(&pobj->factories, value->handle_num);
		zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(pimple_closure_obj);
	}

	pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC);

	RETVAL_ZVAL(pimple_closure_obj, 1, 1);
}

PHP_METHOD(Pimple, keys)
{
	HashPosition pos;
	pimple_object *pobj = NULL;
	zval **value        = NULL;
	zval *endval        = NULL;
	char *str_index     = NULL;
	int str_len;
	ulong num_index;

	if (zend_parse_parameters_none() == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);
	array_init_size(return_value, zend_hash_num_elements(&pobj->values));

	zend_hash_internal_pointer_reset_ex(&pobj->values, &pos);

	while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) {
		MAKE_STD_ZVAL(endval);
		switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) {
			case HASH_KEY_IS_STRING:
				ZVAL_STRINGL(endval, str_index, str_len - 1, 1);
				zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
			break;
			case HASH_KEY_IS_LONG:
				ZVAL_LONG(endval, num_index);
				zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
			break;
		}
	zend_hash_move_forward_ex(&pobj->values, &pos);
	}
}

PHP_METHOD(Pimple, factory)
{
	zval *factory       = NULL;
	pimple_object *pobj = NULL;
	pimple_bucket_value bucket = {0};

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) {
		return;
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
		return;
	}

	pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC);
	pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);

	if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
		Z_ADDREF_P(factory);
		RETURN_ZVAL(factory, 1 , 0);
	} else {
		pimple_free_bucket(&bucket);
	}

	RETURN_FALSE;
}

PHP_METHOD(Pimple, offsetSet)
{
	zval *offset = NULL, *value = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) {
		return;
	}

	pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC);
}

PHP_METHOD(Pimple, offsetGet)
{
	zval *offset = NULL, *retval = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC);

	RETVAL_ZVAL(retval, 1, 0);
}

PHP_METHOD(Pimple, offsetUnset)
{
	zval *offset = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	pimple_object_unset_dimension(getThis(), offset TSRMLS_CC);
}

PHP_METHOD(Pimple, offsetExists)
{
	zval *offset = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC));
}

PHP_METHOD(Pimple, register)
{
	zval *provider;
	zval **data;
	zval *retval = NULL;
	zval key;

	HashTable *array = NULL;
	HashPosition pos;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) {
		return;
	}

	zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis());

	if (retval) {
		zval_ptr_dtor(&retval);
	}

	if (!array) {
		return;
	}

	zend_hash_internal_pointer_reset_ex(array, &pos);

	while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) {
		zend_hash_get_current_key_zval_ex(array, &key, &pos);
		pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC);
		zend_hash_move_forward_ex(array, &pos);
	}

	RETVAL_ZVAL(getThis(), 1, 0);
}

PHP_METHOD(Pimple, __construct)
{
	zval *values = NULL, **pData = NULL, offset = {0};
	HashPosition pos;
	char *str_index = NULL;
	zend_uint str_length;
	ulong num_index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE || !values) {
		return;
	}

	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos);
	while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) {
			zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos);
			zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos);
			if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) {
				ZVAL_LONG(&offset, num_index);
			} else {
				ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0);
			}
		pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC);
		zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos);
	}
}

/*
 * This is PHP code snippet handling extend()s calls :

  $extended = function ($c) use ($callable, $factory) {
      return $callable($factory($c), $c);
  };

 */
PHP_METHOD(PimpleClosure, __invoke)
{
	pimple_closure_object *pcobj = NULL;
	zval *arg = NULL, *retval = NULL, *newretval = NULL;
	zend_fcall_info fci        = {0};
	zval **args[2];

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) {
		return;
	}

	pcobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	fci.function_name = pcobj->factory;
	args[0] = &arg;
	zend_fcall_info_argp(&fci TSRMLS_CC, 1, args);
	fci.retval_ptr_ptr = &retval;
	fci.size = sizeof(fci);

	if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
		efree(fci.params);
		return; /* Should here return default zval */
	}

	efree(fci.params);
	memset(&fci, 0, sizeof(fci));
	fci.size = sizeof(fci);

	fci.function_name = pcobj->callable;
	args[0] = &retval;
	args[1] = &arg;
	zend_fcall_info_argp(&fci TSRMLS_CC, 2, args);
	fci.retval_ptr_ptr = &newretval;

	if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
		efree(fci.params);
		zval_ptr_dtor(&retval);
		return;
	}

	efree(fci.params);
	zval_ptr_dtor(&retval);
	RETVAL_ZVAL(newretval, 1 ,1);
}

PHP_MINIT_FUNCTION(pimple)
{
	zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce;
	INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions);
	INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", pimple_closure_ce_functions);
	INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions);

	tmp_pimple_ce.create_object         = pimple_object_create;
	tmp_pimple_closure_ce.create_object = pimple_closure_object_create;

	pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC);
	zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess);

	pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC);
	pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;

	pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC);

	pimple_closure_object_handlers = std_object_handlers;
	pimple_object_handlers         = std_object_handlers;

	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_RINIT_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_MINFO_FUNCTION(pimple)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled");
	php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION);
	php_info_print_table_end();

	php_info_print_box_start(0);
	php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC);
	if (!sapi_module.phpinfo_as_text) {
		php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC);
	}
	php_info_print_box_end();
}

zend_module_entry pimple_module_entry = {
	STANDARD_MODULE_HEADER,
	"pimple",
	NULL,
	PHP_MINIT(pimple),
	PHP_MSHUTDOWN(pimple),
	PHP_RINIT(pimple),
	PHP_RSHUTDOWN(pimple),
	PHP_MINFO(pimple),
	PIMPLE_VERSION,
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_PIMPLE
ZEND_GET_MODULE(pimple)
#endif

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2014 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef PIMPLE_COMPAT_H_
#define PIMPLE_COMPAT_H_

#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */

#define PHP_5_0_X_API_NO		220040412
#define PHP_5_1_X_API_NO		220051025
#define PHP_5_2_X_API_NO		220060519
#define PHP_5_3_X_API_NO		220090626
#define PHP_5_4_X_API_NO		220100525
#define PHP_5_5_X_API_NO		220121212
#define PHP_5_6_X_API_NO		220131226

#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO
#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO

#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO
#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO

#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO
#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO

#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO
#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO

#if IS_PHP_53
#define object_properties_init(obj, ce) do { \
		 zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \
		} while (0);
#endif

#define ZEND_OBJ_INIT(obj, ce) do { \
		zend_object_std_init(obj, ce TSRMLS_CC); \
		object_properties_init((obj), (ce)); \
	} while(0);

#if IS_PHP_53 || IS_PHP_54
static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) {
    Bucket *p;

    p = pos ? (*pos) : ht->pInternalPointer;

    if (!p) {
        Z_TYPE_P(key) = IS_NULL;
    } else if (p->nKeyLength) {
        Z_TYPE_P(key) = IS_STRING;
        Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1);
        Z_STRLEN_P(key) = p->nKeyLength - 1;
    } else {
        Z_TYPE_P(key) = IS_LONG;
        Z_LVAL_P(key) = p->h;
    }
}
#endif

#endif /* PIMPLE_COMPAT_H_ */
--TEST--
Test for read_dim/write_dim handlers
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p[42] = 'foo';
$p['foo'] = 42;

echo $p[42];
echo "\n";
echo $p['foo'];
echo "\n";
try {
	var_dump($p['nonexistant']);
	echo "Exception excpected";
} catch (InvalidArgumentException $e) { }

$p[54.2] = 'foo2';
echo $p[54];
echo "\n";
$p[242.99] = 'foo99';
echo $p[242];

echo "\n";

$p[5] = 'bar';
$p[5] = 'baz';
echo $p[5];

echo "\n";

$p['str'] = 'str';
$p['str'] = 'strstr';
echo $p['str'];
?>

--EXPECTF--
foo
42
foo2
foo99
baz
strstr--TEST--
Test for constructor
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
var_dump($p[42]);

$p = new Pimple\Container(array(42=>'foo'));
var_dump($p[42]);
?>
--EXPECT--
NULL
string(3) "foo"
--TEST--
Test empty dimensions
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p[] = 42;
var_dump($p[0]);
$p[41] = 'foo';
$p[] = 'bar';
var_dump($p[42]);
?>
--EXPECT--
int(42)
string(3) "bar"--TEST--
Test has/unset dim handlers
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p[] = 42;
var_dump($p[0]);
unset($p[0]);
var_dump($p[0]);
$p['foo'] = 'bar';
var_dump(isset($p['foo']));
unset($p['foo']);
try {
	var_dump($p['foo']);
	echo "Excpected exception";
} catch (InvalidArgumentException $e) { }
var_dump(isset($p['bar']));
$p['bar'] = NULL;
var_dump(isset($p['bar']));
var_dump(empty($p['bar']));
?>
--EXPECT--
int(42)
NULL
bool(true)
bool(false)
bool(true)
bool(true)--TEST--
Test simple class inheritance
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
class MyPimple extends Pimple\Container
{
	public $someAttr = 'fooAttr';

    public function offsetget($o)
    {
        var_dump("hit");
        return parent::offsetget($o);
    }
}

$p = new MyPimple;
$p[42] = 'foo';
echo $p[42];
echo "\n";
echo $p->someAttr;
?>
--EXPECT--
string(3) "hit"
foo
fooAttr--TEST--
Test complex class inheritance
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
class MyPimple extends Pimple\Container
{
    public function offsetget($o)
    {
        var_dump("hit offsetget in " . __CLASS__);
        return parent::offsetget($o);
    }
}

class TestPimple extends MyPimple
{
    public function __construct($values)
    {
        array_shift($values);
        parent::__construct($values);
    }
    
    public function offsetget($o)
    {
        var_dump('hit offsetget in ' . __CLASS__);
        return parent::offsetget($o);
    }
    
    public function offsetset($o, $v)
    {
        var_dump('hit offsetset');
        return parent::offsetset($o, $v);
    }
}

$defaultValues = array('foo' => 'bar', 88 => 'baz');

$p = new TestPimple($defaultValues);
$p[42] = 'foo';
var_dump($p[42]);
var_dump($p[0]);
?>
--EXPECT--
string(13) "hit offsetset"
string(27) "hit offsetget in TestPimple"
string(25) "hit offsetget in MyPimple"
string(3) "foo"
string(27) "hit offsetget in TestPimple"
string(25) "hit offsetget in MyPimple"
string(3) "baz"--TEST--
Test for read_dim/write_dim handlers
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p[42] = 'foo';
$p['foo'] = 42;

echo $p[42];
echo "\n";
echo $p['foo'];
echo "\n";
try {
	var_dump($p['nonexistant']);
	echo "Exception excpected";
} catch (InvalidArgumentException $e) { }
?>
--EXPECTF--
foo
42--TEST--
Test frozen services
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p[42] = 'foo';
$p[42] = 'bar';

$p['foo'] = function () { };
$p['foo'] = function () { };

$a = $p['foo'];

try {
	$p['foo'] = function () { };
	echo "Exception excpected";
} catch (RuntimeException $e) { }

$p[42] = function() { };
$a = $p[42];

try {
	$p[42] = function () { };
	echo "Exception excpected";
} catch (RuntimeException $e) { }
?>
--EXPECTF--
--TEST--
Test service is called as callback, and only once
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php 
$p = new Pimple\Container();
$p['foo'] = function($arg) use ($p) { var_dump($p === $arg); };
$a = $p['foo'];
$b = $p['foo']; /* should return not calling the callback */
?>
--EXPECTF--
bool(true)--TEST--
Test service is called as callback for every callback type
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php
function callme()
{
    return 'called';
}

$a = function() { return 'called'; };

class Foo
{
    public static function bar()
    {
        return 'called';
    }
}
 
$p = new Pimple\Container();
$p['foo'] = 'callme';
echo $p['foo'] . "\n";

$p['bar'] = $a;
echo $p['bar'] . "\n";

$p['baz'] = "Foo::bar";
echo $p['baz'] . "\n";

$p['foobar'] = array('Foo', 'bar');
var_dump($p['foobar']);

?>
--EXPECTF--
callme
called
Foo::bar
array(2) {
  [0]=>
  string(3) "Foo"
  [1]=>
  string(3) "bar"
}--TEST--
Test service callback throwing an exception
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php
class CallBackException extends RuntimeException { }

$p = new Pimple\Container();
$p['foo'] = function () { throw new CallBackException; };
try {
	echo $p['foo'] . "\n";
	echo "should not come here";
} catch (CallBackException $e) {
	echo "all right!";
}
?>
--EXPECTF--
all right!--TEST--
Test service factory
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();

$p->factory($f = function() { var_dump('called-1'); return 'ret-1';});

$p[] = $f;

$p[] = function () { var_dump('called-2'); return 'ret-2'; };

var_dump($p[0]);
var_dump($p[0]);
var_dump($p[1]);
var_dump($p[1]);
?>
--EXPECTF--
string(8) "called-1"
string(5) "ret-1"
string(8) "called-1"
string(5) "ret-1"
string(8) "called-2"
string(5) "ret-2"
string(5) "ret-2"--TEST--
Test keys()
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();

var_dump($p->keys());

$p['foo'] = 'bar';
$p[] = 'foo';

var_dump($p->keys());

unset($p['foo']);

var_dump($p->keys());
?>
--EXPECTF--
array(0) {
}
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  int(0)
}
array(1) {
  [0]=>
  int(0)
}--TEST--
Test raw()
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();
$f = function () { var_dump('called-2'); return 'ret-2'; };

$p['foo'] = $f;
$p[42]    = $f;

var_dump($p['foo']);
var_dump($p->raw('foo'));
var_dump($p[42]);

unset($p['foo']);

try {
	$p->raw('foo');
	echo "expected exception";
} catch (InvalidArgumentException $e) { }
--EXPECTF--
string(8) "called-2"
string(5) "ret-2"
object(Closure)#%i (0) {
}
string(8) "called-2"
string(5) "ret-2"--TEST--
Test protect()
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();
$f = function () { return 'foo'; };
$p['foo'] = $f;

$p->protect($f);

var_dump($p['foo']);
--EXPECTF--
object(Closure)#%i (0) {
}--TEST--
Test extend()
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php
/*
 This is part of Pimple::extend() code :

          $extended = function ($c) use ($callable, $factory) {
             return $callable($factory($c), $c);
          };
*/

$p = new Pimple\Container();
$p[12] = function ($v) { var_dump($v); return 'foo';}; /* $factory in code above */

$c = $p->extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */

var_dump($c('param'));
--EXPECTF--
string(5) "param"
string(3) "foo"
string(3) "bar"--TEST--
Test extend() with exception in service extension
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();
$p[12] = function ($v) { return 'foo';};

$c = $p->extend(12, function ($w) { throw new BadMethodCallException; });

try {
	$p[12];
	echo "Exception expected";
} catch (BadMethodCallException $e) { }
--EXPECTF--
--TEST--
Test extend() with exception in service factory
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

$p = new Pimple\Container();
$p[12] = function ($v) { throw new BadMethodCallException; };

$c = $p->extend(12, function ($w) { return 'foobar'; });

try {
	$p[12];
	echo "Exception expected";
} catch (BadMethodCallException $e) { }
--EXPECTF--
--TEST--
Test register()
--SKIPIF--
<?php if (!extension_loaded("pimple")) print "skip"; ?>
--FILE--
<?php

class Foo implements Pimple\ServiceProviderInterface
{
    public function register(Pimple\Container $p)
    {
        var_dump($p);
    }
}

$p = new Pimple\Container();
$p->register(new Foo, array(42 => 'bar'));

var_dump($p[42]);
--EXPECTF--
object(Pimple\Container)#1 (0) {
}
string(3) "bar"<?php

if (!class_exists('Pimple\Container')) {
    require_once __DIR__ . '/../../../lib/Pimple.php';
} else {
    echo "pimple-c extension detected, using...\n\n";
}

$time = microtime(true);

function foo() { }
$factory = function () { };

for ($i=0; $i<10000; $i++) {

$p = new Pimple\Container;

$p['foo'] = 'bar';

if (!isset($p[3])) {
    $p[3] = $p['foo'];
    $p[]  = 'bar';
}

$p[2] = 42;

if (isset($p[2])) {
	unset($p[2]);
}

$p[42] = $p['foo'];

$p['cb'] = function($arg) { };

$p[] = $p['cb'];

echo $p['cb'];
echo $p['cb'];
echo $p['cb'];

//$p->factory($factory);

$p['factory'] = $factory;

echo $p['factory'];
echo $p['factory'];
echo $p['factory'];

}

echo microtime(true)  - $time;
<?php

if (!class_exists('Pimple\Container')) {
    require_once __DIR__ . '/../../../lib/Pimple.php';
} else {
    echo "pimple-c extension detected, using...\n\n";
}

$time = microtime(true);


$service = function ($arg) { return "I'm a service"; };

for ($i=0; $i<10000; $i++) {

$p = new Pimple\Container;
$p['my_service'] = $service;

$a = $p['my_service'];
$b = $p['my_service'];

}

echo microtime(true) - $time;
?>
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="Pimple Test Suite">
            <directory>./src/Pimple/Tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple;

/**
 * Container main class.
 *
 * @author  Fabien Potencier
 */
class Container implements \ArrayAccess
{
    private $values = array();
    private $factories;
    private $protected;
    private $frozen = array();
    private $raw = array();
    private $keys = array();

    /**
     * Instantiate the container.
     *
     * Objects and parameters can be passed as argument to the constructor.
     *
     * @param array $values The parameters or objects.
     */
    public function __construct(array $values = array())
    {
        $this->factories = new \SplObjectStorage();
        $this->protected = new \SplObjectStorage();

        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }

    /**
     * Sets a parameter or an object.
     *
     * Objects must be defined as Closures.
     *
     * Allowing any PHP callable leads to difficult to debug problems
     * as function names (strings) are callable (creating a function with
     * the same name as an existing parameter would break your container).
     *
     * @param  string            $id    The unique identifier for the parameter or object
     * @param  mixed             $value The value of the parameter or a closure to define an object
     * @throws \RuntimeException Prevent override of a frozen service
     */
    public function offsetSet($id, $value)
    {
        if (isset($this->frozen[$id])) {
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
        }

        $this->values[$id] = $value;
        $this->keys[$id] = true;
    }

    /**
     * Gets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     */
    public function offsetGet($id)
    {
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (
            isset($this->raw[$id])
            || !is_object($this->values[$id])
            || isset($this->protected[$this->values[$id]])
            || !method_exists($this->values[$id], '__invoke')
        ) {
            return $this->values[$id];
        }

        if (isset($this->factories[$this->values[$id]])) {
            return $this->values[$id]($this);
        }

        $raw = $this->values[$id];
        $val = $this->values[$id] = $raw($this);
        $this->raw[$id] = $raw;

        $this->frozen[$id] = true;

        return $val;
    }

    /**
     * Checks if a parameter or an object is set.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return bool
     */
    public function offsetExists($id)
    {
        return isset($this->keys[$id]);
    }

    /**
     * Unsets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     */
    public function offsetUnset($id)
    {
        if (isset($this->keys[$id])) {
            if (is_object($this->values[$id])) {
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
            }

            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
        }
    }

    /**
     * Marks a callable as being a factory service.
     *
     * @param callable $callable A service definition to be used as a factory
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     */
    public function factory($callable)
    {
        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
        }

        $this->factories->attach($callable);

        return $callable;
    }

    /**
     * Protects a callable from being interpreted as a service.
     *
     * This is useful when you want to store a callable as a parameter.
     *
     * @param callable $callable A callable to protect from being evaluated
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     */
    public function protect($callable)
    {
        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
        }

        $this->protected->attach($callable);

        return $callable;
    }

    /**
     * Gets a parameter or the closure defining an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or the closure defining an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     */
    public function raw($id)
    {
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (isset($this->raw[$id])) {
            return $this->raw[$id];
        }

        return $this->values[$id];
    }

    /**
     * Extends an object definition.
     *
     * Useful when you want to extend an existing object definition,
     * without necessarily loading that object.
     *
     * @param string   $id       The unique identifier for the object
     * @param callable $callable A service definition to extend the original
     *
     * @return callable The wrapped callable
     *
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
     */
    public function extend($id, $callable)
    {
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
        }

        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
        }

        $factory = $this->values[$id];

        $extended = function ($c) use ($callable, $factory) {
            return $callable($factory($c), $c);
        };

        if (isset($this->factories[$factory])) {
            $this->factories->detach($factory);
            $this->factories->attach($extended);
        }

        return $this[$id] = $extended;
    }

    /**
     * Returns all defined value names.
     *
     * @return array An array of value names
     */
    public function keys()
    {
        return array_keys($this->values);
    }

    /**
     * Registers a service provider.
     *
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
     * @param array                    $values   An array of values that customizes the provider
     *
     * @return static
     */
    public function register(ServiceProviderInterface $provider, array $values = array())
    {
        $provider->register($this);

        foreach ($values as $key => $value) {
            $this[$key] = $value;
        }

        return $this;
    }
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple;

/**
 * Pimple service provider interface.
 *
 * @author  Fabien Potencier
 * @author  Dominik Zogg
 */
interface ServiceProviderInterface
{
    /**
     * Registers services on the given container.
     *
     * This method should only be used to configure services and parameters.
     * It should not get services.
     *
     * @param Container $pimple An Container instance
     */
    public function register(Container $pimple);
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests\Fixtures;

class Invokable
{
    public function __invoke($value = null)
    {
        $service = new Service();
        $service->value = $value;

        return $service;
    }
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests\Fixtures;

class NonInvokable
{
    public function __call($a, $b)
    {
    }
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests\Fixtures;

use Pimple\Container;
use Pimple\ServiceProviderInterface;

class PimpleServiceProvider implements ServiceProviderInterface
{
    /**
     * Registers services on the given container.
     *
     * This method should only be used to configure services and parameters.
     * It should not get services.
     *
     * @param Container $pimple An Container instance
     */
    public function register(Container $pimple)
    {
        $pimple['param'] = 'value';

        $pimple['service'] = function () {
            return new Service();
        };

        $pimple['factory'] = $pimple->factory(function () {
            return new Service();
        });
    }
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests\Fixtures;

/**
 * @author  Igor Wiedler <igor@wiedler.ch>
 */
class Service
{
    public $value;
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests;

use Pimple\Container;

/**
 * @author  Dominik Zogg <dominik.zogg@gmail.com>
 */
class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase
{
    public function testProvider()
    {
        $pimple = new Container();

        $pimpleServiceProvider = new Fixtures\PimpleServiceProvider();
        $pimpleServiceProvider->register($pimple);

        $this->assertEquals('value', $pimple['param']);
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);

        $serviceOne = $pimple['factory'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);

        $serviceTwo = $pimple['factory'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);

        $this->assertNotSame($serviceOne, $serviceTwo);
    }

    public function testProviderWithRegisterMethod()
    {
        $pimple = new Container();

        $pimple->register(new Fixtures\PimpleServiceProvider(), array(
            'anotherParameter' => 'anotherValue'
        ));

        $this->assertEquals('value', $pimple['param']);
        $this->assertEquals('anotherValue', $pimple['anotherParameter']);

        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);

        $serviceOne = $pimple['factory'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);

        $serviceTwo = $pimple['factory'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);

        $this->assertNotSame($serviceOne, $serviceTwo);
    }
}
<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple\Tests;

use Pimple\Container;

/**
 * @author  Igor Wiedler <igor@wiedler.ch>
 */
class PimpleTest extends \PHPUnit_Framework_TestCase
{
    public function testWithString()
    {
        $pimple = new Container();
        $pimple['param'] = 'value';

        $this->assertEquals('value', $pimple['param']);
    }

    public function testWithClosure()
    {
        $pimple = new Container();
        $pimple['service'] = function () {
            return new Fixtures\Service();
        };

        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
    }

    public function testServicesShouldBeDifferent()
    {
        $pimple = new Container();
        $pimple['service'] = $pimple->factory(function () {
            return new Fixtures\Service();
        });

        $serviceOne = $pimple['service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);

        $serviceTwo = $pimple['service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);

        $this->assertNotSame($serviceOne, $serviceTwo);
    }

    public function testShouldPassContainerAsParameter()
    {
        $pimple = new Container();
        $pimple['service'] = function () {
            return new Fixtures\Service();
        };
        $pimple['container'] = function ($container) {
            return $container;
        };

        $this->assertNotSame($pimple, $pimple['service']);
        $this->assertSame($pimple, $pimple['container']);
    }

    public function testIsset()
    {
        $pimple = new Container();
        $pimple['param'] = 'value';
        $pimple['service'] = function () {
            return new Fixtures\Service();
        };

        $pimple['null'] = null;

        $this->assertTrue(isset($pimple['param']));
        $this->assertTrue(isset($pimple['service']));
        $this->assertTrue(isset($pimple['null']));
        $this->assertFalse(isset($pimple['non_existent']));
    }

    public function testConstructorInjection()
    {
        $params = array("param" => "value");
        $pimple = new Container($params);

        $this->assertSame($params['param'], $pimple['param']);
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Identifier "foo" is not defined.
     */
    public function testOffsetGetValidatesKeyIsPresent()
    {
        $pimple = new Container();
        echo $pimple['foo'];
    }

    public function testOffsetGetHonorsNullValues()
    {
        $pimple = new Container();
        $pimple['foo'] = null;
        $this->assertNull($pimple['foo']);
    }

    public function testUnset()
    {
        $pimple = new Container();
        $pimple['param'] = 'value';
        $pimple['service'] = function () {
            return new Fixtures\Service();
        };

        unset($pimple['param'], $pimple['service']);
        $this->assertFalse(isset($pimple['param']));
        $this->assertFalse(isset($pimple['service']));
    }

    /**
     * @dataProvider serviceDefinitionProvider
     */
    public function testShare($service)
    {
        $pimple = new Container();
        $pimple['shared_service'] = $service;

        $serviceOne = $pimple['shared_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);

        $serviceTwo = $pimple['shared_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);

        $this->assertSame($serviceOne, $serviceTwo);
    }

    /**
     * @dataProvider serviceDefinitionProvider
     */
    public function testProtect($service)
    {
        $pimple = new Container();
        $pimple['protected'] = $pimple->protect($service);

        $this->assertSame($service, $pimple['protected']);
    }

    public function testGlobalFunctionNameAsParameterValue()
    {
        $pimple = new Container();
        $pimple['global_function'] = 'strlen';
        $this->assertSame('strlen', $pimple['global_function']);
    }

    public function testRaw()
    {
        $pimple = new Container();
        $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; });
        $this->assertSame($definition, $pimple->raw('service'));
    }

    public function testRawHonorsNullValues()
    {
        $pimple = new Container();
        $pimple['foo'] = null;
        $this->assertNull($pimple->raw('foo'));
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Identifier "foo" is not defined.
     */
    public function testRawValidatesKeyIsPresent()
    {
        $pimple = new Container();
        $pimple->raw('foo');
    }

    /**
     * @dataProvider serviceDefinitionProvider
     */
    public function testExtend($service)
    {
        $pimple = new Container();
        $pimple['shared_service'] = function () {
            return new Fixtures\Service();
        };
        $pimple['factory_service'] = $pimple->factory(function () {
            return new Fixtures\Service();
        });

        $pimple->extend('shared_service', $service);
        $serviceOne = $pimple['shared_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
        $serviceTwo = $pimple['shared_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
        $this->assertSame($serviceOne, $serviceTwo);
        $this->assertSame($serviceOne->value, $serviceTwo->value);

        $pimple->extend('factory_service', $service);
        $serviceOne = $pimple['factory_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
        $serviceTwo = $pimple['factory_service'];
        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
        $this->assertNotSame($serviceOne, $serviceTwo);
        $this->assertNotSame($serviceOne->value, $serviceTwo->value);
    }

    public function testExtendDoesNotLeakWithFactories()
    {
        if (extension_loaded('pimple')) {
            $this->markTestSkipped('Pimple extension does not support this test');
        }
        $pimple = new Container();

        $pimple['foo'] = $pimple->factory(function () { return; });
        $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; });
        unset($pimple['foo']);

        $p = new \ReflectionProperty($pimple, 'values');
        $p->setAccessible(true);
        $this->assertEmpty($p->getValue($pimple));

        $p = new \ReflectionProperty($pimple, 'factories');
        $p->setAccessible(true);
        $this->assertCount(0, $p->getValue($pimple));
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Identifier "foo" is not defined.
     */
    public function testExtendValidatesKeyIsPresent()
    {
        $pimple = new Container();
        $pimple->extend('foo', function () {});
    }

    public function testKeys()
    {
        $pimple = new Container();
        $pimple['foo'] = 123;
        $pimple['bar'] = 123;

        $this->assertEquals(array('foo', 'bar'), $pimple->keys());
    }

    /** @test */
    public function settingAnInvokableObjectShouldTreatItAsFactory()
    {
        $pimple = new Container();
        $pimple['invokable'] = new Fixtures\Invokable();

        $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']);
    }

    /** @test */
    public function settingNonInvokableObjectShouldTreatItAsParameter()
    {
        $pimple = new Container();
        $pimple['non_invokable'] = new Fixtures\NonInvokable();

        $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']);
    }

    /**
     * @dataProvider badServiceDefinitionProvider
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Service definition is not a Closure or invokable object.
     */
    public function testFactoryFailsForInvalidServiceDefinitions($service)
    {
        $pimple = new Container();
        $pimple->factory($service);
    }

    /**
     * @dataProvider badServiceDefinitionProvider
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Callable is not a Closure or invokable object.
     */
    public function testProtectFailsForInvalidServiceDefinitions($service)
    {
        $pimple = new Container();
        $pimple->protect($service);
    }

    /**
     * @dataProvider badServiceDefinitionProvider
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Identifier "foo" does not contain an object definition.
     */
    public function testExtendFailsForKeysNotContainingServiceDefinitions($service)
    {
        $pimple = new Container();
        $pimple['foo'] = $service;
        $pimple->extend('foo', function () {});
    }

    /**
     * @dataProvider badServiceDefinitionProvider
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Extension service definition is not a Closure or invokable object.
     */
    public function testExtendFailsForInvalidServiceDefinitions($service)
    {
        $pimple = new Container();
        $pimple['foo'] = function () {};
        $pimple->extend('foo', $service);
    }

    /**
     * Provider for invalid service definitions
     */
    public function badServiceDefinitionProvider()
    {
        return array(
          array(123),
          array(new Fixtures\NonInvokable())
        );
    }

    /**
     * Provider for service definitions
     */
    public function serviceDefinitionProvider()
    {
        return array(
            array(function ($value) {
                $service = new Fixtures\Service();
                $service->value = $value;

                return $service;
            }),
            array(new Fixtures\Invokable())
        );
    }

    public function testDefiningNewServiceAfterFreeze()
    {
        $pimple = new Container();
        $pimple['foo'] = function () {
            return 'foo';
        };
        $foo = $pimple['foo'];

        $pimple['bar'] = function () {
            return 'bar';
        };
        $this->assertSame('bar', $pimple['bar']);
    }

    /**
     * @expectedException RuntimeException
     * @expectedExceptionMessage Cannot override frozen service "foo".
     */
    public function testOverridingServiceAfterFreeze()
    {
        $pimple = new Container();
        $pimple['foo'] = function () {
            return 'foo';
        };
        $foo = $pimple['foo'];

        $pimple['foo'] = function () {
            return 'bar';
        };
    }

    public function testRemovingServiceAfterFreeze()
    {
        $pimple = new Container();
        $pimple['foo'] = function () {
            return 'foo';
        };
        $foo = $pimple['foo'];

        unset($pimple['foo']);
        $pimple['foo'] = function () {
            return 'bar';
        };
        $this->assertSame('bar', $pimple['foo']);
    }

    public function testExtendingService()
    {
        $pimple = new Container();
        $pimple['foo'] = function () {
            return 'foo';
        };
        $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
            return "$foo.bar";
        });
        $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
            return "$foo.baz";
        });
        $this->assertSame('foo.bar.baz', $pimple['foo']);
    }

    public function testExtendingServiceAfterOtherServiceFreeze()
    {
        $pimple = new Container();
        $pimple['foo'] = function () {
            return 'foo';
        };
        $pimple['bar'] = function () {
            return 'bar';
        };
        $foo = $pimple['foo'];

        $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) {
            return "$bar.baz";
        });
        $this->assertSame('bar.baz', $pimple['bar']);
    }
}
Version 1.4.1-dev
-----------------

Nothing yet.

Version 1.4.0 (2015-07-14)
--------------------------

### Added

* Added interface `PhpParser\Node\FunctionLike`, which is implemented by `Stmt\ClassMethod`,
  `Stmt\Function_` and `Expr\Closure` nodes. This interface provides getters for their common
  subnodes.
* Added `Node\Stmt\ClassLike::getMethod()` to look up a specific method on a class/interface/trait.

### Fixed

* Fixed `isPublic()` return value for implicitly public properties and methods that define and
  additional modifier like `static` or `abstract`.
* Properties are now accepted by the trait builder.
* Fixed `__HALT_COMPILER_OFFSET__` support on HHVM.

Version 1.3.0 (2015-05-02)
--------------------------

### Added

* Errors can now store the attributes of the node/token where the error occurred. Previously only the start line was
  stored.
* If file positions are enabled in the lexer, errors can now provide column information if it is available. See
  [documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#column-information).
* The parser now provides an experimental error recovery mode, which can be enabled by disabling the `throwOnError`
  parser option. In this mode the parser will try to construct a partial AST even if the code is not valid PHP. See
  [documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#error-recovery).
* Added support for PHP 7 `yield from` expression. It is represented by `Expr\YieldFrom`.
* Added support for PHP 7 anonymous classes. These are represented by ordinary `Stmt\Class_` nodes with the name set to
  `null`. Furthermore this implies that `Expr\New_` can now contain a `Stmt\Class_` in its `class` subnode.

### Fixed

* Fixed registration of PHP 7 aliases, for the case where the old name was used before the new name.
* Fixed handling of precedence when pretty-printing `print` expressions.
* Floating point numbers are now pretty-printed with a higher precision.
* Checks for special class names like `self` are now case-insensitive.

Version 1.2.2 (2015-04-03)
--------------------------

* The `NameResolver` now resolves parameter type hints when entering the function/method/closure node. As such other
  visitors running after it will be able to make use of the resolved names at that point already.
* The autoloader no longer sets the `unserialize_callback_func` ini option on registration - this is not necessary and
  may cause issues when running PhpUnit tests with process isolation.

Version 1.2.1 (2015-03-24)
--------------------------

* Fixed registration of the aliases introduced in 1.2.0. Previously the old class names could not be used in
  `instanceof` checks under some circumstances.

Version 1.2.0 (2015-03-22)
--------------------------

### Changed

* To ensure compatibility with PHP 7, the following node classes have been renamed:

        OLD                             => NEW
        PhpParser\Node\Expr\Cast\Bool   => PhpParser\Node\Expr\Cast\Bool_
        PhpParser\Node\Expr\Cast\Int    => PhpParser\Node\Expr\Cast\Int_
        PhpParser\Node\Expr\Cast\Object => PhpParser\Node\Expr\Cast\Object_
        PhpParser\Node\Expr\Cast\String => PhpParser\Node\Expr\Cast\String_
        PhpParser\Node\Scalar\String    => PhpParser\Node\Scalar\String_

  **The previous class names are still supported as aliases.** However it is strongly encouraged to use the new names
  in order to make your code compatible with PHP 7.

* Subnodes are now stored using real properties instead of an array. This improves performance and memory usage of the
  initial parse and subsequent node tree operations. The `NodeAbstract` class still supports the old way of specifying
  subnodes, however this is *deprecated*. In any case properties that are assigned to a node after creation will no
  longer be considered as subnodes.

* Methods and property declarations will no longer set the `Stmt\Class_::MODIFIER_PUBLIC` flag if no visibility is
  explicitly given. However the `isPublic()` method will continue to return true. This allows you to distinguish whether
  a method/property is explicitly or implicitly public and control the pretty printer output more precisely.

* The `Stmt\Class_`, `Stmt\Interface_` and `Stmt\Trait_` nodes now inherit from `Stmt\ClassLike`, which provides a
  `getMethods()` method. Previously this method was only available on `Stmt\Class_`.

* Support including the `bootstrap.php` file multiple times.

* Make documentation and tests part of the release tarball again.

* Improve support for HHVM and PHP 7.

### Added

* Added support for PHP 7 return type declarations. This adds an additional `returnType` subnode to `Stmt\Function_`,
  `Stmt\ClassMethod` and `Expr\Closure`.

* Added support for the PHP 7 null coalesce operator `??`. The operator is represented by `Expr\BinaryOp\Coalesce`.

* Added support for the PHP 7 spaceship operator `<=>`. The operator is represented by `Expr\BinaryOp\Spaceship`.

* Added use builder.

* Added global namespace support to the namespace builder.

* Added a constructor flag to `NodeTraverser`, which disables cloning of nodes.

Version 1.1.0 (2015-01-18)
--------------------------

* Methods that do not specify an explicit visibility (e.g. `function method()`) will now have the `MODIFIER_PUBLIC`
  flag set. This also means that their `isPublic()` method will return true.

* Declaring a property as abstract or final is now an error.

* The `Lexer` and `Lexer\Emulative` classes now accept an `$options` array in their constructors. Currently only the
  `usedAttributes` option is supported, which determines which attributes will be added to AST nodes. In particular
  it is now possible to add information on the token and file positions corresponding to a node. For more details see
  the [Lexer component](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Lexer.markdown) documentation.

* Node visitors can now return `NodeTraverser::DONT_TRAVERSE_CHILDREN` from `enterNode()` in order to skip all children
  of the current node, for all visitors.

* Added builders for traits and namespaces.

* The class, interface, trait, function, method and property builders now support adding doc comments using the
  `setDocComment()` method.

* Added support for fully-qualified and namespace-relative names in builders. No longer allow use of name component
  arrays.

* Do not add documentation and tests to distribution archive files.

Version 1.0.2 (2014-11-04)
--------------------------

* The `NameResolver` visitor now also resolves names in trait adaptations (aliases and precedence declarations).

* Remove stray whitespace when pretty-printing trait adaptations that only change visibility.

Version 1.0.1 (2014-10-14)
--------------------------

* Disallow `new` expressions without a class name. Previously `new;` was accidentally considered to be valid code.

* Support T_ONUMBER token used by HHVM.

* Add ability to directly pass code to the `php-parse.php` script.

* Prevent truncation of `var_dump()` output in the `php-parse.php` script if XDebug is used.

Version 1.0.0 (2014-09-12)
--------------------------

* [BC] Removed deprecated `Template` and `TemplateLoader` classes.

* Fixed XML unserializer to properly work with new namespaced node names.

Version 1.0.0-beta2 (2014-08-31)
--------------------------------

* [PHP 5.6] Updated support for constant scalar expressions to comply with latest changes. This means that arrays
  and array dimension fetches are now supported as well.

* [PHP 5.6] Direct array dereferencing of constants is supported now, i.e. both `FOO[0]` and `Foo::BAR[0]` are valid
  now.

* Fixed handling of special class names (`self`, `parent` and `static`) in the name resolver to be case insensitive.
  Additionally the name resolver now enforces that special class names are only used as unqualified names, e.g. `\self`
  is considered invalid.

* The case of references to the `static` class name is now preserved. Previously `static` was always lowercased,
  regardless of the case used in the source code.

* The autoloader now only requires a file if it exists. This allows usages like
  `class_exists('PhpParser\NotExistingClass')`.

* Added experimental `bin/php-parse.php` script, which is intended to help exploring and debugging the node tree.

* Separated the parser implemention (in `lib/PhpParser/ParserAbstract.php`) and the generated data (in
  `lib/PhpParser/Parser.php`). Furthermore the parser now uses meaningful variable names and contains comments
  explaining their usage.

Version 1.0.0-beta1 (2014-03-27)
--------------------------------

* [BC] PHP-Parser now requires PHP 5.3 or newer to run. It is however still possible to *parse* PHP 5.2 source code,
  while running on a newer version.

* [BC] The library has been moved to use namespaces with the `PhpParser` vendor prefix. However, the old names using
  underscores are still available as aliases, as such most code should continue running on the new version without
  further changes.

  However, code performing dispatch operations on `Node::getType()` may be affected by some of the name changes. For
  example a `+` node will now return type `Expr_BinaryOp_Plus` instead of `Expr_Plus`. In particular this may affect
  custom pretty printers.

  Due to conflicts with reserved keywords, some class names now end with an underscore, e.g. `PHPParser_Node_Stmt_Class`
  is now `PhpParser\Node\Stmt\Class_`. (But as usual, the old name is still available)

* [PHP 5.6] Added support for the power operator `**` (node `Expr\BinaryOp\Pow`) and the compound power assignment
  operator `**=` (node `Expr\AssignOp\Pow`).

* [PHP 5.6] Added support for variadic functions: `Param` nodes now have `variadic` as a boolean subnode.

* [PHP 5.6] Added support for argument unpacking: `Arg` nodes now have `unpack` as a boolean subnode.

* [PHP 5.6] Added support for aliasing of functions and constants. `Stmt\Use_` nodes now have an integral `type`
  subnode, which is one of `Stmt\Use_::TYPE_NORMAL` (`use`), `Stmt\Use_::TYPE_FUNCTION` (`use function`) or
  `Stmt\Use_::TYPE_CONSTANT` (`use const`).

  The `NameResolver` now also supports resolution of such aliases.

* [PHP 5.6] Added support for constant scalar expressions. This means that certain expressions are now allowed as the
  initializer for constants, properties, parameters, static variables, etc.

* [BC] Improved pretty printing of empty statements lists, which are now printed as `{\n}` instead of `{\n    \n}`.
  This changes the behavior of the protected `PrettyPrinterAbstract::pStmts()` method, so custom pretty printing code
  making use it of may need to be adjusted.

* Changed the order of some subnodes to be consistent with their order in the sour code. For example `Stmt\If->cond`
  will now appear before `Stmt\If->stmts` etc.

* Added `Scalar\MagicConstant->getName()`, which returns the name of the magic constant (e.g. `__CLASS__`).

**The following changes are also included in 0.9.5**:

* [BC] Deprecated `PHPParser_Template` and `PHPParser_TemplateLoader`. This functionality does not belong in the main project
  and - as far as I know - nobody is using it.

* Add `NodeTraverser::removeVisitor()` method, which removes a visitor from the node traverser. This also modifies the
  corresponding `NodeTraverserInterface`.

* Fix alias resolution in `NameResolver`: Class names are now correctly handled as case-insensitive.

* The undefined variable error, which is used to the lexer to reset the error state, will no longer interfere with
  custom error handlers.

---

**This changelog only includes changes from the 1.0 series. For older changes see the [0.9 series changelog][1].**

 [1]: https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md
Copyright (c) 2011 by Nikita Popov.

Some 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.

    * The names of the contributors may not be used to endorse or
      promote products derived from this software without specific
      prior written permission.

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.PHP Parser
==========

This is a PHP 5.2 to PHP 5.6 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.

[**Documentation for version 1.x**][doc_1_x] (stable; for running on PHP >= 5.3).

[Documentation for version 0.9.x][doc_0_9] (unsupported; for running on PHP 5.2).

In a Nutshell
-------------

The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the
parser:

```php
<?php
echo 'Hi', 'World';
hello\world('foo', 'bar' . 'baz');
```

You'll get a syntax tree looking roughly like this:

```php
array(
    0: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hi
            )
            1: Scalar_String(
                value: World
            )
        )
    )
    1: Expr_FuncCall(
        name: Name(
            parts: array(
                0: hello
                1: world
            )
        )
        args: array(
            0: Arg(
                value: Scalar_String(
                    value: foo
                )
                byRef: false
            )
            1: Arg(
                value: Expr_Concat(
                    left: Scalar_String(
                        value: bar
                    )
                    right: Scalar_String(
                        value: baz
                    )
                )
                byRef: false
            )
        )
    )
)
```

You can then work with this syntax tree, for example to statically analyze the code (e.g. to find
programming errors or security issues).

Additionally, you can convert a syntax tree back to PHP code. This allows you to do code preprocessing
(like automatedly porting code to older PHP versions).

Documentation
-------------

 1. [Introduction](doc/0_Introduction.markdown)
 2. [Installation](doc/1_Installation.markdown)
 3. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
 4. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
 5. [Code generation](doc/4_Code_generation.markdown)

Component documentation:

 1. [Lexer](doc/component/Lexer.markdown)

 [doc_0_9]: https://github.com/nikic/PHP-Parser/tree/0.9/doc
 [doc_1_x]: https://github.com/nikic/PHP-Parser/tree/1.x/doc
Upgrading from PHP-Parser 0.9 to 1.0
====================================

### PHP version requirements

PHP-Parser now requires PHP 5.3 or newer to run. It is however still possible to *parse* PHP 5.2 source code, while
running on a newer version.

### Move to namespaced names

The library has been moved to use namespaces with the `PhpParser` vendor prefix. However, the old names using
underscores are still available as aliases, as such most code should continue running on the new version without
further changes.

Old (still works, but discouraged):

```php
$parser = new \PHPParser_Parser(new \PHPParser_Lexer_Emulative);
$prettyPrinter = new \PHPParser_PrettyPrinter_Default;
```

New:

```php
$parser = new \PhpParser\Parser(new PhpParser\Lexer\Emulative);
$prettyPrinter = new \PhpParser\PrettyPrinter\Standard;
```

Note that the `PHPParser` prefix was changed to `PhpParser`. While PHP class names are technically case-insensitive,
the autoloader will not be able to load `PHPParser\Parser` or other case variants.

Due to conflicts with reserved keywords, some class names now end with an underscore, e.g. `PHPParser_Node_Stmt_Class`
is now `PhpParser\Node\Stmt\Class_`. (But as usual, the old name is still available.)

### Changes to `Node::getType()`

The `Node::getType()` method continues to return names using underscores instead of namespace separators and also does
not contain the trailing underscore that may be present in the class name. As such its output will not change in many
cases.

However, some node classes have been moved to a different namespace or renamed, which will result in a different
`Node::getType()` output:

```
Expr_AssignBitwiseAnd => Expr_AssignOp_BitwiseAnd
Expr_AssignBitwiseOr  => Expr_AssignOp_BitwiseOr
Expr_AssignBitwiseXor => Expr_AssignOp_BitwiseXor
Expr_AssignConcat     => Expr_AssignOp_Concat
Expr_AssignDiv        => Expr_AssignOp_Div
Expr_AssignMinus      => Expr_AssignOp_Minus
Expr_AssignMod        => Expr_AssignOp_Mod
Expr_AssignMul        => Expr_AssignOp_Mul
Expr_AssignPlus       => Expr_AssignOp_Plus
Expr_AssignShiftLeft  => Expr_AssignOp_ShiftLeft
Expr_AssignShiftRight => Expr_AssignOp_ShiftRight

Expr_BitwiseAnd       => Expr_BinaryOp_BitwiseAnd
Expr_BitwiseOr        => Expr_BinaryOp_BitwiseOr
Expr_BitwiseXor       => Expr_BinaryOp_BitwiseXor
Expr_BooleanAnd       => Expr_BinaryOp_BooleanAnd
Expr_BooleanOr        => Expr_BinaryOp_BooleanOr
Expr_Concat           => Expr_BinaryOp_Concat
Expr_Div              => Expr_BinaryOp_Div
Expr_Equal            => Expr_BinaryOp_Equal
Expr_Greater          => Expr_BinaryOp_Greater
Expr_GreaterOrEqual   => Expr_BinaryOp_GreaterOrEqual
Expr_Identical        => Expr_BinaryOp_Identical
Expr_LogicalAnd       => Expr_BinaryOp_LogicalAnd
Expr_LogicalOr        => Expr_BinaryOp_LogicalOr
Expr_LogicalXor       => Expr_BinaryOp_LogicalXor
Expr_Minus            => Expr_BinaryOp_Minus
Expr_Mod              => Expr_BinaryOp_Mod
Expr_Mul              => Expr_BinaryOp_Mul
Expr_NotEqual         => Expr_BinaryOp_NotEqual
Expr_NotIdentical     => Expr_BinaryOp_NotIdentical
Expr_Plus             => Expr_BinaryOp_Plus
Expr_ShiftLeft        => Expr_BinaryOp_ShiftLeft
Expr_ShiftRight       => Expr_BinaryOp_ShiftRight
Expr_Smaller          => Expr_BinaryOp_Smaller
Expr_SmallerOrEqual   => Expr_BinaryOp_SmallerOrEqual

Scalar_ClassConst     => Scalar_MagicConst_Class
Scalar_DirConst       => Scalar_MagicConst_Dir
Scalar_FileConst      => Scalar_MagicConst_File
Scalar_FuncConst      => Scalar_MagicConst_Function
Scalar_LineConst      => Scalar_MagicConst_Line
Scalar_MethodConst    => Scalar_MagicConst_Method
Scalar_NSConst        => Scalar_MagicConst_Namespace
Scalar_TraitConst     => Scalar_MagicConst_Trait
```

These changes may affect custom pretty printers and code comparing the return value of `Node::getType()` to specific
strings.

### Miscellaneous

  * The classes `Template` and `TemplateLoader` have been removed. You should use some other [code generation][code_gen]
    project built on top of PHP-Parser instead.

  * The `PrettyPrinterAbstract::pStmts()` method now emits a leading newline if the statement list is not empty.
    Custom pretty printers should remove the explicit newline before `pStmts()` calls.

    Old:

    ```php
    public function pStmt_Trait(PHPParser_Node_Stmt_Trait $node) {
        return 'trait ' . $node->name
             . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    }
    ```

    New:

    ```php
    public function pStmt_Trait(Stmt\Trait_ $node) {
        return 'trait ' . $node->name
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }
    ```

  [code_gen]: https://github.com/nikic/PHP-Parser/wiki/Projects-using-the-PHP-Parser#code-generation#!/usr/bin/env php
<?php

require __DIR__ . '/../lib/bootstrap.php';

ini_set('xdebug.max_nesting_level', 3000);

// Disable XDebug var_dump() output truncation
ini_set('xdebug.var_display_max_children', -1);
ini_set('xdebug.var_display_max_data', -1);
ini_set('xdebug.var_display_max_depth', -1);

list($operations, $files, $attributes) = parseArgs($argv);

/* Dump nodes by default */
if (empty($operations)) {
    $operations[] = 'dump';
}

if (empty($files)) {
    showHelp("Must specify at least one file.");
}

$lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
    'startLine', 'endLine', 'startFilePos', 'endFilePos'
)));
$parser = new PhpParser\Parser($lexer);
$dumper = new PhpParser\NodeDumper;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$serializer = new PhpParser\Serializer\XML;

$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);

foreach ($files as $file) {
    if (strpos($file, '<?php') === 0) {
        $code = $file;
        echo "====> Code $code\n";
    } else {
        if (!file_exists($file)) {
            die("File $file does not exist.\n");
        }

        $code = file_get_contents($file);
        echo "====> File $file:\n";
    }

    try {
        $stmts = $parser->parse($code);
    } catch (PhpParser\Error $e) {
        if ($attributes['with-column-info'] && $e->hasColumnInfo()) {
            $startLine = $e->getStartLine();
            $endLine = $e->getEndLine();
            $startColumn = $e->getStartColumn($code);
            $endColumn   = $e->getEndColumn($code);
            $message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
        } else {
            $message = $e->getMessage();
        }

        die($message . "\n");
    }

    foreach ($operations as $operation) {
        if ('dump' === $operation) {
            echo "==> Node dump:\n";
            echo $dumper->dump($stmts), "\n";
        } elseif ('pretty-print' === $operation) {
            echo "==> Pretty print:\n";
            echo $prettyPrinter->prettyPrintFile($stmts), "\n";
        } elseif ('serialize-xml' === $operation) {
            echo "==> Serialized XML:\n";
            echo $serializer->serialize($stmts), "\n";
        } elseif ('var-dump' === $operation) {
            echo "==> var_dump():\n";
            var_dump($stmts);
        } elseif ('resolve-names' === $operation) {
            echo "==> Resolved names.\n";
            $stmts = $traverser->traverse($stmts);
        }
    }
}

function showHelp($error) {
    die($error . "\n\n" .
        <<<OUTPUT
Usage: php php-parse.php [operations] file1.php [file2.php ...]
   or: php php-parse.php [operations] "<?php code"
Turn PHP source code into an abstract syntax tree.

Operations is a list of the following options (--dump by default):

    -d, --dump              Dump nodes using NodeDumper
    -p, --pretty-print      Pretty print file using PrettyPrinter\Standard
        --serialize-xml     Serialize nodes using Serializer\XML
        --var-dump          var_dump() nodes (for exact structure)
    -N, --resolve-names     Resolve names using NodeVisitor\NameResolver
    -c, --with-column-info  Show column-numbers for errors (if available)

Example:
    php php-parse.php -d -p -N -d file.php

    Dumps nodes, pretty prints them, then resolves names and dumps them again.


OUTPUT
    );
}

function parseArgs($args) {
    $operations = array();
    $files = array();
    $attributes = array(
        'with-column-info' => false,
    );

    array_shift($args);
    $parseOptions = true;
    foreach ($args as $arg) {
        if (!$parseOptions) {
            $files[] = $arg;
            continue;
        }

        switch ($arg) {
            case '--dump':
            case '-d':
                $operations[] = 'dump';
                break;
            case '--pretty-print':
            case '-p':
                $operations[] = 'pretty-print';
                break;
            case '--serialize-xml':
                $operations[] = 'serialize-xml';
                break;
            case '--var-dump':
                $operations[] = 'var-dump';
                break;
            case '--resolve-names':
            case '-N';
                $operations[] = 'resolve-names';
                break;
            case '--with-column-info':
            case '-c';
                $attributes['with-column-info'] = true;
                break;
            case '--':
                $parseOptions = false;
                break;
            default:
                if ($arg[0] === '-') {
                    showHelp("Invalid operation $arg.");
                } else {
                    $files[] = $arg;
                }
        }
    }

    return array($operations, $files, $attributes);
}
{
    "name": "nikic/php-parser",
    "description": "A PHP parser written in PHP",
    "keywords": ["php", "parser"],
    "type": "library",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Nikita Popov"
        }
    ],
    "require": {
        "php": ">=5.3",
        "ext-tokenizer": "*"
    },
    "autoload": {
        "files": ["lib/bootstrap.php"]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.4-dev"
        }
    }
}
Introduction
============

This project is a PHP 5.2 to PHP 5.6 parser **written in PHP itself**.

What is this for?
-----------------

A parser is useful for [static analysis][0], manipulation of code and basically any other
application dealing with code programmatically. A parser constructs an [Abstract Syntax Tree][1]
(AST) of the code and thus allows dealing with it in an abstract and robust way.

There are other ways of processing source code. One that PHP supports natively is using the
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
the AST and thus has different applications: It allows to also analyze the exact formatting of
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
For example an AST abstracts away the fact that in PHP variables can be written as `$foo`, but also
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
all the different syntaxes from a stream of tokens.

Another questions is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be
a language especially suited for fast parsing, but processing the AST is much easier in PHP than it
would be in other, faster languages like C. Furthermore the people most probably wanting to do
programmatic PHP code analysis are incidentally PHP developers, not C developers.

What can it parse?
------------------

The parser uses a PHP 5.6 compliant grammar, which is backwards compatible with all PHP version from PHP 5.2
upwards (and maybe older).

As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4, 5.5 and 5.6 is provided.
This allows to parse PHP 5.6 source code running on PHP 5.3, for example. This emulation is very hacky and not
perfect, but it should work well on any sane code.

What output does it produce?
----------------------------

The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks like
can best be seen in an example. The program `<?php echo 'Hi', 'World';` will give you a node tree
roughly looking like this:

```
array(
    0: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hi
            )
            1: Scalar_String(
                value: World
            )
        )
    )
)
```

This matches the structure of the code: An echo statement, which takes two strings as expressions,
with the values `Hi` and `World!`.

You can also see that the AST does not contain any whitespace information (but most comments are saved).
So using it for formatting analysis is not possible.

What else can it do?
--------------------

Apart from the parser itself this package also bundles support for some other, related features:

 * Support for pretty printing, which is the act of converting an AST into PHP code. Please note
   that "pretty printing" does not imply that the output is especially pretty. It's just how it's
   called ;)
 * Support for serializing and unserializing the node tree to XML
 * Support for dumping the node tree in a human readable form (see the section above for an
   example of how the output looks like)
 * Infrastructure for traversing and changing the AST (node traverser and node visitors)
 * A node visitor for resolving namespaced names

 [0]: http://en.wikipedia.org/wiki/Static_program_analysis
 [1]: http://en.wikipedia.org/wiki/Abstract_syntax_tree
 [2]: http://php.net/token_get_all
Installation
============

There are multiple ways to include the PHP parser into your project:

Installing via Composer
-----------------------

Run the following command inside your project:

    php composer.phar require nikic/php-parser

If you haven't installed [Composer][1] yet, you can do so using:

    curl -s http://getcomposer.org/installer | php

Installing as a Git Submodule
-----------------------------

Run the following command to install the parser into the `vendor/PHP-Parser` folder:

    git submodule add git://github.com/nikic/PHP-Parser.git vendor/PHP-Parser

Installing from the Zip- or Tarball
-----------------------------------

Download the latest version from [the download page][2], unpack it and move the files somewhere into your project.


 [1]: https://getcomposer.org/
 [2]: https://github.com/nikic/PHP-Parser/tags
Usage of basic components
=========================

This document explains how to use the parser, the pretty printer and the node traverser.

Bootstrapping
-------------

The library needs to register a class autoloader. You can either use the `vendor/autoload.php` file generated by
Composer or by including the bundled `lib/bootstrap.php` file:

```php
<?php
require 'path/to/PHP-Parser/lib/bootstrap.php';
// Or, if you're using Composer:
require 'path/to/vendor/autoload.php';
```

Additionally you may want to set the `xdebug.max_nesting_level` ini option to a higher value:

```php
<?php
ini_set('xdebug.max_nesting_level', 3000);
```

This ensures that there will be no errors when traversing highly nested node trees.

Parsing
-------

In order to parse some source code you first have to create a `PhpParser\Parser` object, which
needs to be passed a `PhpParser\Lexer` instance:

```php
<?php

$parser = new PhpParser\Parser(new PhpParser\Lexer);
// or
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
```

Use of the emulative lexer is required if you want to parse PHP code from newer versions than the one
you're running on. For example it will allow you to parse PHP 5.6 code while running on PHP 5.3.

Subsequently you can pass PHP code (including the opening `<?php` tag) to the `parse` method in order to
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:

```php
<?php
$code = '<?php // some code';

$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);

try {
    $stmts = $parser->parse($code);
    // $stmts is an array of statement nodes
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

A parser instance can be reused to parse multiple files.

Node tree
---------

If you use the above code with `$code = "<?php echo 'Hi ', hi\\getTarget();"` the parser will
generate a node tree looking like this:

```
array(
    0: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hi
            )
            1: Expr_FuncCall(
                name: Name(
                    parts: array(
                        0: hi
                        1: getTarget
                    )
                )
                args: array(
                )
            )
        )
    )
)
```

Thus `$stmts` will contain an array with only one node, with this node being an instance of
`PhpParser\Node\Stmt\Echo_`.

As PHP is a large language there are approximately 140 different nodes. In order to make work
with them easier they are grouped into three categories:

 * `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
   a value and can not occur in an expression. For example a class definition is a statement.
   It doesn't return a value and you can't write something like `func(class A {});`.
 * `PhpParser\Node\Expr`s are expression nodes, i.e. language constructs that return a value
   and thus can occur in other expressions. Examples of expressions are `$var`
   (`PhpParser\Node\Expr\Variable`) and `func()` (`PhpParser\Node\Expr\FuncCall`).
 * `PhpParser\Node\Scalar`s are nodes representing scalar values, like `'string'`
   (`PhpParser\Node\Scalar\String_`), `0` (`PhpParser\Node\Scalar\LNumber`) or magic constants
   like `__FILE__` (`PhpParser\Node\Scalar\MagicConst\File`). All `PhpParser\Node\Scalar`s extend
   `PhpParser\Node\Expr`, as scalars are expressions, too.
 * There are some nodes not in either of these groups, for example names (`PhpParser\Node\Name`)
   and call arguments (`PhpParser\Node\Arg`).

Some node class names have a trailing `_`. This is used whenever the class name would otherwise clash
with a PHP keyword.

Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
in the above example you would write `$stmts[0]->exprs`. If you wanted to access the name of the function
call, you would write `$stmts[0]->exprs[1]->name`.

All nodes also define a `getType()` method that returns the node type. The type is the class name
without the `PhpParser\Node\` prefix and `\` replaced with `_`. It also does not contain a trailing
`_` for reserved-keyword class names.

It is possible to associate custom metadata with a node using the `setAttribute()` method. This data
can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttributes()`.

By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array
of `PhpParser\Comment[\Doc]` instances.

The start line can also be accessed using `getLine()`/`setLine()` (instead of `getAttribute('startLine')`).
The last doc comment from the `comments` attribute can be obtained using `getDocComment()`.

Pretty printer
--------------

The pretty printer component compiles the AST back to PHP code. As the parser does not retain formatting
information the formatting is done using a specified scheme. Currently there is only one scheme available,
namely `PhpParser\PrettyPrinter\Standard`.

```php
<?php
$code = "<?php echo 'Hi ', hi\\getTarget();";

$parser = new PhpParser\Parser(new PhpParser\Lexer);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;

try {
    // parse
    $stmts = $parser->parse($code);

    // change
    $stmts[0]         // the echo statement
          ->exprs     // sub expressions
          [0]         // the first of them (the string node)
          ->value     // it's value, i.e. 'Hi '
          = 'Hello '; // change to 'Hello '

    // pretty print
    $code = $prettyPrinter->prettyPrint($stmts);

    echo $code;
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

The above code will output:

    <?php echo 'Hello ', hi\getTarget();

As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.

The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
single expression using `prettyPrintExpr()`.

The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
and handle inline HTML as the first/last statement more gracefully.

Node traversation
-----------------

The above pretty printing example used the fact that the source code was known and thus it was easy to
write code that accesses a certain part of a node tree and changes it. Normally this is not the case.
Usually you want to change / analyze code in a generic way, where you don't know how the node tree is
going to look like.

For this purpose the parser provides a component for traversing and visiting the node tree. The basic
structure of a program using this `PhpParser\NodeTraverser` looks like this:

```php
<?php

$parser        = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser     = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;

// add your visitor
$traverser->addVisitor(new MyNodeVisitor);

try {
    $code = file_get_contents($fileName);

    // parse
    $stmts = $parser->parse($code);

    // traverse
    $stmts = $traverser->traverse($stmts);

    // pretty print
    $code = $prettyPrinter->prettyPrintFile($stmts);

    echo $code;
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

The corresponding node visitor might look like this:

```php
<?php
use PhpParser\Node;

class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
{
    public function leaveNode(Node $node) {
        if ($node instanceof Node\Scalar\String_) {
            $node->value = 'foo';
        }
    }
}
```

The above node visitor would change all string literals in the program to `'foo'`.

All visitors must implement the `PhpParser\NodeVisitor` interface, which defines the following four
methods:

    public function beforeTraverse(array $nodes);
    public function enterNode(PhpParser\Node $node);
    public function leaveNode(PhpParser\Node $node);
    public function afterTraverse(array $nodes);

The `beforeTraverse()` method is called once before the traversal begins and is passed the nodes the
traverser was called with. This method can be used for resetting values before traversation or
preparing the tree for traversal.

The `afterTraverse()` method is similar to the `beforeTraverse()` method, with the only difference that
it is called once after the traversal.

The `enterNode()` and `leaveNode()` methods are called on every node, the former when it is entered,
i.e. before its subnodes are traversed, the latter when it is left.

All four methods can either return the changed node or not return at all (i.e. `null`) in which
case the current node is not changed.

The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
which instructs the traverser to skip all children of the current node.

The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
case the current node will be removed from the parent array. Furthermove it is possible to return
an array of nodes, which will be merged into the parent array at the offset of the current node.
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
be `array(A, X, Y, Z, C)`.

Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
class, which will define empty default implementations for all the above methods.

The NameResolver node visitor
-----------------------------

One visitor is already bundled with the package: `PhpParser\NodeVisitor\NameResolver`. This visitor
helps you work with namespaced code by trying to resolve most names to fully qualified ones.

For example, consider the following code:

    use A as B;
    new B\C();

In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself.
The `NameResolver` takes care of that and resolves names as far as possible.

After running it most names will be fully qualified. The only names that will stay unqualified are
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
know which function they are referring to. In most cases this is a non-issue as the global functions
are meant.

Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
that contains the namespaced name instead of only the shortname that is available via `name`.

Example: Converting namespaced code to pseudo namespaces
--------------------------------------------------------

A small example to understand the concept: We want to convert namespaced code to pseudo namespaces
so it works on 5.2, i.e. names like `A\\B` should be converted to `A_B`. Note that such conversions
are fairly complicated if you take PHP's dynamic features into account, so our conversion will
assume that no dynamic features are used.

We start off with the following base code:

```php
<?php
$inDir  = '/some/path';
$outDir = '/some/other/path';

$parser        = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser     = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;

$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); // we will need resolved names
$traverser->addVisitor(new NodeVisitor\NamespaceConverter);     // our own node visitor

// iterate over all .php files in the directory
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($inDir));
$files = new RegexIterator($files, '/\.php$/');

foreach ($files as $file) {
    try {
        // read the file that should be converted
        $code = file_get_contents($file);

        // parse
        $stmts = $parser->parse($code);

        // traverse
        $stmts = $traverser->traverse($stmts);

        // pretty print
        $code = $prettyPrinter->prettyPrintFile($stmts);

        // write the converted file to the target directory
        file_put_contents(
            substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)),
            $code
        );
    } catch (PhpParser\Error $e) {
        echo 'Parse Error: ', $e->getMessage();
    }
}
```

Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do
is convert `A\\B` style names to `A_B` style ones.

```php
<?php
use PhpParser\Node;
class NodeVisitor_NamespaceConverter extends PhpParser\NodeVisitorAbstract
{
    public function leaveNode(Node $node) {
        if ($node instanceof Node\Name) {
            return new Node\Name($node->toString('_'));
        }
    }
}
```

The above code profits from the fact that the `NameResolver` already resolved all names as far as
possible, so we don't need to do that. We only need to create a string with the name parts separated
by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to
create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
a new name from the string and return it. Returning a new node replaces the old node.

Another thing we need to do is change the class/function/const declarations. Currently they contain
only the shortname (i.e. the last part of the name), but they need to contain the complete name inclduing
the namespace prefix:

```php
<?php
use PhpParser\Node;
use PhpParser\Node\Stmt;
class NodeVisitor_NamespaceConverter extends PhpParser\NodeVisitorAbstract
{
    public function leaveNode(Node $node) {
        if ($node instanceof Node\Name) {
            return new Node\Name($node->toString('_'));
        } elseif ($node instanceof Stmt\Class_
                  || $node instanceof Stmt\Interface_
                  || $node instanceof Stmt\Function_) {
            $node->name = $node->namespacedName->toString('_');
        } elseif ($node instanceof Stmt\Const_) {
            foreach ($node->consts as $const) {
                $const->name = $const->namespacedName->toString('_');
            }
        }
    }
}
```

There is not much more to it than converting the namespaced name to string with `_` as separator.

The last thing we need to do is remove the `namespace` and `use` statements:

```php
<?php
use PhpParser\Node;
use PhpParser\Node\Stmt;
class NodeVisitor_NamespaceConverter extends PhpParser\NodeVisitorAbstract
{
    public function leaveNode(Node $node) {
        if ($node instanceof Node\Name) {
            return new Node\Name($node->toString('_'));
        } elseif ($node instanceof Stmt\Class_
                  || $node instanceof Stmt\Interface_
                  || $node instanceof Stmt\Function_) {
            $node->name = $node->namespacedName->toString('_');
        } elseif ($node instanceof Stmt\Const_) {
            foreach ($node->consts as $const) {
                $const->name = $const->namespacedName->toString('_');
            }
        } elseif ($node instanceof Stmt\Namespace_) {
            // returning an array merges is into the parent array
            return $node->stmts;
        } elseif ($node instanceof Stmt\Use_) {
            // returning false removed the node altogether
            return false;
        }
    }
}
```

That's all.
Other node tree representations
===============================

It is possible to convert the AST into several textual representations, which serve different uses.

Simple serialization
--------------------

It is possible to serialize the node tree using `serialize()` and also unserialize it using
`unserialize()`. The output is not human readable and not easily processable from anything
but PHP, but it is compact and generates fast. The main application thus is in caching.

Human readable dumping
----------------------

Furthermore it is possible to dump nodes into a human readable format using the `dump` method of
`PhpParser\NodeDumper`. This can be used for debugging.

```php
<?php
$code = <<<'CODE'
<?php

function printLine($msg) {
    echo $msg, "\n";
}

printLine('Hello World!!!');
CODE;

$parser = new PhpParser\Parser(new PhpParser\Lexer);
$nodeDumper = new PhpParser\NodeDumper;

try {
    $stmts = $parser->parse($code);

    echo $nodeDumper->dump($stmts), "\n";
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

The above script will have an output looking roughly like this:

```
array(
    0: Stmt_Function(
        byRef: false
        params: array(
            0: Param(
                name: msg
                default: null
                type: null
                byRef: false
            )
        )
        stmts: array(
            0: Stmt_Echo(
                exprs: array(
                    0: Expr_Variable(
                        name: msg
                    )
                    1: Scalar_String(
                        value:

                    )
                )
            )
        )
        name: printLine
    )
    1: Expr_FuncCall(
        name: Name(
            parts: array(
                0: printLine
            )
        )
        args: array(
            0: Arg(
                value: Scalar_String(
                    value: Hello World!!!
                )
                byRef: false
            )
        )
    )
)
```

Serialization to XML
--------------------

It is also possible to serialize the node tree to XML using `PhpParser\Serializer\XML->serialize()`
and to unserialize it using `PhpParser\Unserializer\XML->unserialize()`. This is useful for
interfacing with other languages and applications or for doing transformation using XSLT.

```php
<?php
$code = <<<'CODE'
<?php

function printLine($msg) {
    echo $msg, "\n";
}

printLine('Hello World!!!');
CODE;

$parser = new PhpParser\Parser(new PhpParser\Lexer);
$serializer = new PhpParser\Serializer\XML;

try {
    $stmts = $parser->parse($code);

    echo $serializer->serialize($stmts);
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

Produces:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node" xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode" xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
 <scalar:array>
  <node:Stmt_Function line="2">
   <subNode:byRef>
    <scalar:false/>
   </subNode:byRef>
   <subNode:params>
    <scalar:array>
     <node:Param line="2">
      <subNode:name>
       <scalar:string>msg</scalar:string>
      </subNode:name>
      <subNode:default>
       <scalar:null/>
      </subNode:default>
      <subNode:type>
       <scalar:null/>
      </subNode:type>
      <subNode:byRef>
       <scalar:false/>
      </subNode:byRef>
     </node:Param>
    </scalar:array>
   </subNode:params>
   <subNode:stmts>
    <scalar:array>
     <node:Stmt_Echo line="3">
      <subNode:exprs>
       <scalar:array>
        <node:Expr_Variable line="3">
         <subNode:name>
          <scalar:string>msg</scalar:string>
         </subNode:name>
        </node:Expr_Variable>
        <node:Scalar_String line="3">
         <subNode:value>
          <scalar:string>
</scalar:string>
         </subNode:value>
        </node:Scalar_String>
       </scalar:array>
      </subNode:exprs>
     </node:Stmt_Echo>
    </scalar:array>
   </subNode:stmts>
   <subNode:name>
    <scalar:string>printLine</scalar:string>
   </subNode:name>
  </node:Stmt_Function>
  <node:Expr_FuncCall line="6">
   <subNode:name>
    <node:Name line="6">
     <subNode:parts>
      <scalar:array>
       <scalar:string>printLine</scalar:string>
      </scalar:array>
     </subNode:parts>
    </node:Name>
   </subNode:name>
   <subNode:args>
    <scalar:array>
     <node:Arg line="6">
      <subNode:value>
       <node:Scalar_String line="6">
        <subNode:value>
         <scalar:string>Hello World!!!</scalar:string>
        </subNode:value>
       </node:Scalar_String>
      </subNode:value>
      <subNode:byRef>
       <scalar:false/>
      </subNode:byRef>
     </node:Arg>
    </scalar:array>
   </subNode:args>
  </node:Expr_FuncCall>
 </scalar:array>
</AST>
```Code generation
===============

It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the
pretty printer to convert it to PHP code. To simplify code generation, the project comes with builders which allow
creating node trees using a fluid interface, instead of instantiating all nodes manually. Builders are available for
the following syntactic elements:

 * namespaces and use statements
 * classes, interfaces and traits
 * methods, functions and parameters
 * properties

Here is an example:

```php
<?php
$factory = new PhpParser\BuilderFactory;
$node = $factory->namespace('Name\Space')
    ->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass'))
    ->addStmt($factory->class('SomeClass')
        ->extend('SomeOtherClass')
        ->implement('A\Few', '\Interfaces')
        ->makeAbstract() // ->makeFinal()

        ->addStmt($factory->method('someMethod')
            ->makePublic()
            ->makeAbstract() // ->makeFinal()
            ->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
            ->setDocComment('/**
                              * This method does something.
                              *
                              * @param SomeClass And takes a parameter
                              */')
        )

        ->addStmt($factory->method('anotherMethod')
            ->makeProtected() // ->makePublic() [default], ->makePrivate()
            ->addParam($factory->param('someParam')->setDefault('test'))
            // it is possible to add manually created nodes
            ->addStmt(new PhpParser\Node\Expr\Print_(new PhpParser\Node\Expr\Variable('someParam')))
        )

        // properties will be correctly reordered above the methods
        ->addStmt($factory->property('someProperty')->makeProtected())
        ->addStmt($factory->property('anotherProperty')->makePrivate()->setDefault(array(1, 2, 3)))
    )

    ->getNode()
;

$stmts = array($node);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard();
echo $prettyPrinter->prettyPrintFile($stmts);
```

This will produce the following output with the standard pretty printer:

```php
<?php

namespace Name\Space;

use Some\Other\Thingy as SomeClass;
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
{
    protected $someProperty;
    private $anotherProperty = array(1, 2, 3);
    /**
     * This method does something.
     *
     * @param SomeClass And takes a parameter
     */
    public abstract function someMethod(SomeClass $someParam);
    protected function anotherMethod($someParam = 'test')
    {
        print $someParam;
    }
}
```
Error handling
==============

Errors during parsing or analysis are represented using the `PhpParser\Error` exception class. In addition to an error
message, an error can also store additional information about the location the error occurred at.

How much location information is available depends on the origin of the error and how many lexer attributes have been
enabled. At a minimum the start line of the error is usually available.

Column information
------------------

In order to receive information about not only the line, but also the column span an error occurred at, the file
position attributes in the lexer need to be enabled:

```php
$lexer = new PhpParser\Lexer(array(
    'usedAttributes' => array('comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'),
));
$parser = new PhpParser\Parser($lexer);

try {
    $stmts = $parser->parse($code);
    // ...
} catch (PhpParser\Error $e) {
    // ...
}
```

Before using column information its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
the source code of the parsed file. An example for printing an error:

```php
if ($e->hasColumnInfo()) {
    echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
        . ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
} else {
    echo $e->getMessage();
}
```

Both line numbers and column numbers are 1-based. EOF errors will be located at the position one past the end of the
file.

Error recovery
--------------

> **EXPERIMENTAL**

By default the parser will throw an exception upon encountering the first error during parsing. An alternative mode is
also supported, in which the parser will remember the error, but try to continue parsing the rest of the source code.

To enable this mode the `throwOnError` parser option needs to be disabled. Any errors that occurred during parsing can
then be retrieved using `$parser->getErrors()`. The `$parser->parse()` method will either return a partial syntax tree
or `null` if recovery fails.

A usage example:

```php
$parser = new PhpParser\Parser(new PhpParser\Lexer, array(
    'throwOnError' => false,
));

$stmts = $parser->parse($code);
$errors = $parser->getErrors();

foreach ($errors as $error) {
    // $error is an ordinary PhpParser\Error
}

if (null !== $stmts) {
    // $stmts is a best-effort partial AST
}
```

The error recovery implementation is experimental -- it currently won't be able to recover from many types of errors.
Lexer component documentation
=============================

The lexer is responsible for providing tokens to the parser. The project comes with two lexers: `PhpParser\Lexer` and
`PhpParser\Lexer\Emulative`. The latter is an extension of the former, which adds the ability to emulate tokens of
newer PHP versions and thus allows parsing of new code on older versions.

This documentation discusses options available for the default lexers and explains how lexers can be extended.

Lexer options
-------------

The two default lexers accept an `$options` array in the constructor. Currently only the `'usedAttributes'` option is
supported, which allows you to specify which attributes will be added to the AST nodes. The attributes can then be
accessed using `$node->getAttribute()`, `$node->setAttribute()`, `$node->hasAttribute()` and `$node->getAttributes()`
methods. A sample options array:

```php
$lexer = new PhpParser\Lexer(array(
    'usedAttributes' => array(
        'comments', 'startLine', 'endLine'
    )
));
```

The attributes used in this example match the default behavior of the lexer. The following attributes are supported:

 * `comments`: Array of `PhpParser\Comment` or `PhpParser\Comment\Doc` instances, representing all comments that occurred
   between the previous non-discarded token and the current one. Use of this attribute is required for the
   `$node->getDocComment()` method to work. The attribute is also needed if you wish the pretty printer to retain
   comments present in the original code.
 * `startLine`: Line in which the node starts. This attribute is required for the `$node->getLine()` to work. It is also
   required if syntax errors should contain line number information.
 * `endLine`: Line in which the node ends.
 * `startTokenPos`: Offset into the token array of the first token in the node.
 * `endTokenPos`: Offset into the token array of the last token in the node.
 * `startFilePos`: Offset into the code string of the first character that is part of the node.
 * `endFilePos`: Offset into the code string of the last character that is part of the node.

### Using token positions

The token offset information is useful if you wish to examine the exact formatting used for a node. For example the AST
does not distinguish whether a property was declared using `public` or using `var`, but you can retrieve this
information based on the token position:

```php
function isDeclaredUsingVar(array $tokens, PhpParser\Node\Stmt\Property $prop) {
    $i = $prop->getAttribute('startTokenPos');
    return $tokens[$i][0] === T_VAR;
}
```

In order to make use of this function, you will have to provide the tokens from the lexer to your node visitor using
code similar to the following:

```php
class MyNodeVisitor extends PhpParser\NodeVisitorAbstract {
    private $tokens;
    public function setTokens(array $tokens) {
        $this->tokens = $tokens;
    }

    public function leaveNode(PhpParser\Node $node) {
        if ($node instanceof PhpParser\Node\Stmt\Property) {
            var_dump(isDeclaredUsingVar($this->tokens, $node));
        }
    }
}

$lexer = new PhpParser\Lexer(array(
    'usedAttributes' => array(
        'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'
    )
));
$parser = new PhpParser\Parser($lexer);

$visitor = new MyNodeVisitor();
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor($visitor);

try {
    $stmts = $parser->parse($code);
    $visitor->setTokens($lexer->getTokens());
    $stmts = $traverser->traverse($stmts);
} catch (PhpParser\Error $e) {
    echo 'Parse Error: ', $e->getMessage();
}
```

The same approach can also be used to perform specific modifications in the code, without changing the formatting in
other places (which is the case when using the pretty printer).

Lexer extension
---------------

A lexer has to define the following public interface:

    void startLexing(string $code);
    array getTokens();
    string handleHaltCompiler();
    int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null);

The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens.

The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.

The `handleHaltCompiler()` method is called whenever a `T_HALT_COMPILER` token is encountered. It has to return the
remaining string after the construct (not including `();`).

The `getNextToken()` method returns the ID of the next token (as defined by the `Parser::T_*` constants). If no more
tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore the string content of the
token should be written into the by-reference `$value` parameter (which will then be available as `$n` in the parser).

### Attribute handling

The other two by-ref variables `$startAttributes` and `$endAttributes` define which attributes will eventually be
assigned to the generated nodes: The parser will take the `$startAttributes` from the first token which is part of the
node and the `$endAttributes` from the last token that is part of the node.

E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the
`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token.

An application of custom attributes is storing the original formatting of literals: The parser does not retain
information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type or used
escape sequences). This can be remedied by storing the original value in an attribute:

```php
class KeepOriginalValueLexer extends PHPParser\Lexer // or PHPParser\Lexer\Emulative
{
    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);

        if ($tokenId == PHPParser\Parser::T_CONSTANT_ENCAPSED_STRING // non-interpolated string
            || $tokenId == PHPParser\Parser::T_LNUMBER               // integer
            || $tokenId == PHPParser\Parser::T_DNUMBER               // floating point number
        ) {
            // could also use $startAttributes, doesn't really matter here
            $endAttributes['originalValue'] = $value;
        }

        return $tokenId;
    }
}
```
What do all those files mean?
=============================

 * `zend_language_parser.phpy`: PHP grammer written in a pseudo language
 * `analyze.php`:               Analyzes the `.phpy`-grammer and outputs some info about it
 * `rebuildParser.php`:         Preprocesses the `.phpy`-grammar and builds the parser using `kmyacc`
 * `kmyacc.php.parser`:         A `kmyacc` parser prototype file for PHP

.phpy pseudo language
=====================

The `.phpy` file is a normal grammer in `kmyacc` (`yacc`) style, with some transformations
applied to it:

 * Nodes are created using the syntax `Name[..., ...]`. This is transformed into
   `new Name(..., ..., attributes())`
 * Some function-like constructs are resolved (see `rebuildParser.php` for a list)
 * Associative arrays are written as `[key: value, ...]`, which is transformed to
   `array('key' => value, ...)`

Building the parser
===================

In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked).
After you compiled/installed it, run the `rebuildParser.php` script.

By default only the `Parser.php` is built. If you want to additionally emit debug symbols and create `y.output`, run the
script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`.
<?php

const GRAMMAR_FILE = './zend_language_parser.phpy';

const LIB = '(?(DEFINE)
    (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    (?<string>(?&singleQuotedString)|(?&doubleQuotedString))
    (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';

const RULE_BLOCK = '(?<name>[a-z_]++):(?<rules>[^\'"/{};]*+(?:(?:(?&string)|(?&comment)|(?&code)|/|})[^\'"/{};]*+)*+);';

$usedTerminals = array_flip(array(
    'T_VARIABLE', 'T_STRING', 'T_INLINE_HTML', 'T_ENCAPSED_AND_WHITESPACE',
    'T_LNUMBER', 'T_DNUMBER', 'T_CONSTANT_ENCAPSED_STRING', 'T_STRING_VARNAME', 'T_NUM_STRING'
));
$unusedNonterminals = array_flip(array(
    'case_separator', 'optional_comma'
));

function regex($regex) {
    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
}

function magicSplit($regex, $string) {
    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);

    foreach ($pieces as &$piece) {
        $piece = trim($piece);
    }

    return array_filter($pieces);
}

echo '<pre>';

////////////////////
////////////////////
////////////////////

list($defs, $ruleBlocks) = magicSplit('%%', file_get_contents(GRAMMAR_FILE));

if ('' !== trim(preg_replace(regex(RULE_BLOCK), '', $ruleBlocks))) {
    die('Not all rule blocks were properly recognized!');
}

preg_match_all(regex(RULE_BLOCK), $ruleBlocks, $ruleBlocksMatches, PREG_SET_ORDER);
foreach ($ruleBlocksMatches as $match) {
    $ruleBlockName = $match['name'];
    $rules = magicSplit('\|', $match['rules']);

    foreach ($rules as &$rule) {
        $parts = magicSplit('\s+', $rule);
        $usedParts = array();

        foreach ($parts as $part) {
            if ('{' === $part[0]) {
                preg_match_all('~\$([0-9]+)~', $part, $backReferencesMatches, PREG_SET_ORDER);
                foreach ($backReferencesMatches as $match) {
                    $usedParts[$match[1]] = true;
                }
            }
        }

        $i = 1;
        foreach ($parts as &$part) {
            if ('/' === $part[0]) {
                continue;
            }

            if (isset($usedParts[$i])) {
                if ('\'' === $part[0] || '{' === $part[0]
                    || (ctype_upper($part[0]) && !isset($usedTerminals[$part]))
                    || (ctype_lower($part[0]) && isset($unusedNonterminals[$part]))
                ) {
                    $part = '<span style="background-color: red; color: white;">' . $part . '</span>';
                } else {
                    $part = '<strong><em>' . $part . '</em></strong>';
                }
            } elseif ((ctype_upper($part[0]) && isset($usedTerminals[$part]))
                      || (ctype_lower($part[0]) && !isset($unusedNonterminals[$part]))

            ) {
                $part = '<span style="background-color: blue; color: white;">' . $part . '</span>';
            }

            ++$i;
        }

        $rule = implode(' ', $parts);
    }

    echo $ruleBlockName, ':', "\n", '      ', implode("\n" . '    | ', $rules), "\n", ';', "\n\n";
}<?php
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $this->stackPos-(%l-%n)
#semval(%n,%t) $this->stackPos-(%l-%n)

namespace PhpParser;
#include;

/* This is an automatically GENERATED file, which should not be manually edited.
 * Instead edit one of the following:
 *  * the grammar file grammar/zend_language_parser.phpy
 *  * the skeleton file grammar/kymacc.php.parser
 *  * the preprocessing script grammar/rebuildParser.php
 */
class Parser extends ParserAbstract
{
    protected $tokenToSymbolMapSize = #(YYMAXLEX);
    protected $actionTableSize = #(YYLAST);
    protected $gotoTableSize = #(YYGLAST);

    protected $invalidSymbol = #(YYBADCH);
    protected $errorSymbol = #(YYINTERRTOK);
    protected $defaultAction = #(YYDEFAULT);
    protected $unexpectedTokenRule = #(YYUNEXPECTED);

    protected $YY2TBLSTATE  = #(YY2TBLSTATE);
    protected $YYNLSTATES   = #(YYNLSTATES);

#tokenval
    const %s = %n;
#endtokenval

    protected $symbolToName = array(
        #listvar terminals
    );

    protected $tokenToSymbol = array(
        #listvar yytranslate
    );

    protected $action = array(
        #listvar yyaction
    );

    protected $actionCheck = array(
        #listvar yycheck
    );

    protected $actionBase = array(
        #listvar yybase
    );

    protected $actionDefault = array(
        #listvar yydefault
    );

    protected $goto = array(
        #listvar yygoto
    );

    protected $gotoCheck = array(
        #listvar yygcheck
    );

    protected $gotoBase = array(
        #listvar yygbase
    );

    protected $gotoDefault = array(
        #listvar yygdefault
    );

    protected $ruleToNonTerminal = array(
        #listvar yylhs
    );

    protected $ruleToLength = array(
        #listvar yylen
    );
#if -t

    protected $productions = array(
        #production-strings;
    );
#endif
#reduce

    protected function reduceRule%n() {
        %b
    }
#noact

    protected function reduceRule%n() {
        $this->semValue = $this->semStack[$this->stackPos];
    }
#endreduce
}
#tailcode;
<?php

$grammarFile           = __DIR__ . '/zend_language_parser.phpy';
$skeletonFile          = __DIR__ . '/kmyacc.php.parser';
$tmpGrammarFile        = __DIR__ . '/tmp_parser.phpy';
$tmpResultFile         = __DIR__ . '/tmp_parser.php';
$parserResultFile      = __DIR__ . '/../lib/PhpParser/Parser.php';

// check for kmyacc.exe binary in this directory, otherwise fall back to global name
$kmyacc = __DIR__ . '/kmyacc.exe';
if (!file_exists($kmyacc)) {
    $kmyacc = 'kmyacc';
}

$options = array_flip($argv);
$optionDebug = isset($options['--debug']);
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);

///////////////////////////////
/// Utility regex constants ///
///////////////////////////////

const LIB = '(?(DEFINE)
    (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    (?<string>(?&singleQuotedString)|(?&doubleQuotedString))
    (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';

const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
const ARGS   = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';

///////////////////
/// Main script ///
///////////////////

echo 'Building temporary preproprocessed grammar file.', "\n";

$grammarCode = file_get_contents($grammarFile);

$grammarCode = resolveNodes($grammarCode);
$grammarCode = resolveMacros($grammarCode);
$grammarCode = resolveArrays($grammarCode);
$grammarCode = resolveStackAccess($grammarCode);

file_put_contents($tmpGrammarFile, $grammarCode);

$additionalArgs = $optionDebug ? '-t -v' : '';

echo "Building parser.\n";
$output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile $tmpGrammarFile 2>&1"));
echo "Output: \"$output\"\n";

$resultCode = file_get_contents($tmpResultFile);
$resultCode = removeTrailingWhitespace($resultCode);

ensureDirExists(dirname($parserResultFile));
file_put_contents($parserResultFile, $resultCode);
unlink($tmpResultFile);

if (!$optionKeepTmpGrammar) {
    unlink($tmpGrammarFile);
}

///////////////////////////////
/// Preprocessing functions ///
///////////////////////////////

function resolveNodes($code) {
    return preg_replace_callback(
        '~(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
        function($matches) {
            // recurse
            $matches['params'] = resolveNodes($matches['params']);

            $params = magicSplit(
                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
                $matches['params']
            );

            $paramCode = '';
            foreach ($params as $param) {
                $paramCode .= $param . ', ';
            }

            return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
        },
        $code
    );
}

function resolveMacros($code) {
    return preg_replace_callback(
        '~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
        function($matches) {
            // recurse
            $matches['args'] = resolveMacros($matches['args']);

            $name = $matches['name'];
            $args = magicSplit(
                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
                $matches['args']
            );

            if ('attributes' == $name) {
                assertArgs(0, $args, $name);
                return '$this->startAttributeStack[#1] + $this->endAttributes';
            }

            if ('init' == $name) {
                return '$$ = array(' . implode(', ', $args) . ')';
            }

            if ('push' == $name) {
                assertArgs(2, $args, $name);

                return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
            }

            if ('pushNormalizing' == $name) {
                assertArgs(2, $args, $name);

                return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); } else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
            }

            if ('toArray' == $name) {
                assertArgs(1, $args, $name);

                return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
            }

            if ('parseVar' == $name) {
                assertArgs(1, $args, $name);

                return 'substr(' . $args[0] . ', 1)';
            }

            if ('parseEncapsed' == $name) {
                assertArgs(2, $args, $name);

                return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, ' . $args[1] . '); } }';
            }

            if ('parseEncapsedDoc' == $name) {
                assertArgs(1, $args, $name);

                return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, null); } } $s = preg_replace(\'~(\r\n|\n|\r)$~\', \'\', $s); if (\'\' === $s) array_pop(' . $args[0] . ');';
            }

            return $matches[0];
        },
        $code
    );
}

function assertArgs($num, $args, $name) {
    if ($num != count($args)) {
        die('Wrong argument count for ' . $name . '().');
    }
}

function resolveArrays($code) {
    return preg_replace_callback(
        '~' . PARAMS . '~',
        function ($matches) {
            $elements = magicSplit(
                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
                $matches['params']
            );

            // don't convert [] to array, it might have different meaning
            if (empty($elements)) {
                return $matches[0];
            }

            $elementCodes = array();
            foreach ($elements as $element) {
                // convert only arrays where all elements have keys
                if (false === strpos($element, ':')) {
                    return $matches[0];
                }

                list($key, $value) = explode(':', $element, 2);
                $elementCodes[] = "'" . $key . "' =>" . $value;
            }

            return 'array(' . implode(', ', $elementCodes) . ')';
        },
        $code
    );
}

function resolveStackAccess($code) {
    $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
    $code = preg_replace('/#(\d+)/', '$$1', $code);
    return $code;
}

function removeTrailingWhitespace($code) {
    $lines = explode("\n", $code);
    $lines = array_map('rtrim', $lines);
    return implode("\n", $lines);
}

function ensureDirExists($dir) {
    if (!is_dir($dir)) {
        mkdir($dir, 0777, true);
    }
}

//////////////////////////////
/// Regex helper functions ///
//////////////////////////////

function regex($regex) {
    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
}

function magicSplit($regex, $string) {
    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);

    foreach ($pieces as &$piece) {
        $piece = trim($piece);
    }

    return array_filter($pieces);
}
%pure_parser
%expect 2

%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
%left ','
%left T_LOGICAL_OR
%left T_LOGICAL_XOR
%left T_LOGICAL_AND
%right T_PRINT
%right T_YIELD
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left '&'
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
%left '+' '-' '.'
%left '*' '/' '%'
%right '!'
%nonassoc T_INSTANCEOF
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
%right T_POW
%right '['
%nonassoc T_NEW T_CLONE
%token T_EXIT
%token T_IF
%left T_ELSEIF
%left T_ELSE
%left T_ENDIF
%token T_LNUMBER
%token T_DNUMBER
%token T_STRING
%token T_STRING_VARNAME
%token T_VARIABLE
%token T_NUM_STRING
%token T_INLINE_HTML
%token T_CHARACTER
%token T_BAD_CHARACTER
%token T_ENCAPSED_AND_WHITESPACE
%token T_CONSTANT_ENCAPSED_STRING
%token T_ECHO
%token T_DO
%token T_WHILE
%token T_ENDWHILE
%token T_FOR
%token T_ENDFOR
%token T_FOREACH
%token T_ENDFOREACH
%token T_DECLARE
%token T_ENDDECLARE
%token T_AS
%token T_SWITCH
%token T_ENDSWITCH
%token T_CASE
%token T_DEFAULT
%token T_BREAK
%token T_CONTINUE
%token T_GOTO
%token T_FUNCTION
%token T_CONST
%token T_RETURN
%token T_TRY
%token T_CATCH
%token T_FINALLY
%token T_THROW
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
%token T_VAR
%token T_UNSET
%token T_ISSET
%token T_EMPTY
%token T_HALT_COMPILER
%token T_CLASS
%token T_TRAIT
%token T_INTERFACE
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
%token T_CALLABLE
%token T_CLASS_C
%token T_TRAIT_C
%token T_METHOD_C
%token T_FUNC_C
%token T_LINE
%token T_FILE
%token T_COMMENT
%token T_DOC_COMMENT
%token T_OPEN_TAG
%token T_OPEN_TAG_WITH_ECHO
%token T_CLOSE_TAG
%token T_WHITESPACE
%token T_START_HEREDOC
%token T_END_HEREDOC
%token T_DOLLAR_OPEN_CURLY_BRACES
%token T_CURLY_OPEN
%token T_PAAMAYIM_NEKUDOTAYIM
%token T_NAMESPACE
%token T_NS_C
%token T_DIR
%token T_NS_SEPARATOR
%token T_ELLIPSIS

%{
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
%}

%%

start:
    top_statement_list                                      { $$ = $this->handleNamespaces($1); }
;

top_statement_list:
      top_statement_list top_statement                      { pushNormalizing($1, $2); }
    | /* empty */                                           { init(); }
;

namespace_name_parts:
      T_STRING                                              { init($1); }
    | namespace_name_parts T_NS_SEPARATOR T_STRING          { push($1, $3); }
;

namespace_name:
      namespace_name_parts                                  { $$ = Name[$1]; }
;

top_statement:
      statement                                             { $$ = $1; }
    | function_declaration_statement                        { $$ = $1; }
    | class_declaration_statement                           { $$ = $1; }
    | T_HALT_COMPILER
          { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
    | T_NAMESPACE namespace_name ';'                        { $$ = Stmt\Namespace_[$2, null]; }
    | T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
    | T_NAMESPACE '{' top_statement_list '}'                { $$ = Stmt\Namespace_[null,     $3]; }
    | T_USE use_declarations ';'                            { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
    | T_USE T_FUNCTION use_declarations ';'                 { $$ = Stmt\Use_[$3, Stmt\Use_::TYPE_FUNCTION]; }
    | T_USE T_CONST use_declarations ';'                    { $$ = Stmt\Use_[$3, Stmt\Use_::TYPE_CONSTANT]; }
    | T_CONST constant_declaration_list ';'                 { $$ = Stmt\Const_[$2]; }
;

use_declarations:
      use_declarations ',' use_declaration                  { push($1, $3); }
    | use_declaration                                       { init($1); }
;

use_declaration:
      namespace_name                                        { $$ = Stmt\UseUse[$1, null]; }
    | namespace_name T_AS T_STRING                          { $$ = Stmt\UseUse[$1, $3]; }
    | T_NS_SEPARATOR namespace_name                         { $$ = Stmt\UseUse[$2, null]; }
    | T_NS_SEPARATOR namespace_name T_AS T_STRING           { $$ = Stmt\UseUse[$2, $4]; }
;

constant_declaration_list:
      constant_declaration_list ',' constant_declaration    { push($1, $3); }
    | constant_declaration                                  { init($1); }
;

constant_declaration:
    T_STRING '=' static_scalar                              { $$ = Node\Const_[$1, $3]; }
;

inner_statement_list:
      inner_statement_list inner_statement                  { pushNormalizing($1, $2); }
    | /* empty */                                           { init(); }
;

inner_statement:
      statement                                             { $$ = $1; }
    | function_declaration_statement                        { $$ = $1; }
    | class_declaration_statement                           { $$ = $1; }
    | T_HALT_COMPILER
          { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
;

statement:
      '{' inner_statement_list '}'                          { $$ = $2; }
    | T_IF parentheses_expr statement elseif_list else_single
          { $$ = Stmt\If_[$2, [stmts: toArray($3), elseifs: $4, else: $5]]; }
    | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
          { $$ = Stmt\If_[$2, [stmts: $4, elseifs: $5, else: $6]]; }
    | T_WHILE parentheses_expr while_statement              { $$ = Stmt\While_[$2, $3]; }
    | T_DO statement T_WHILE parentheses_expr ';'           { $$ = Stmt\Do_   [$4, toArray($2)]; }
    | T_FOR '(' for_expr ';'  for_expr ';' for_expr ')' for_statement
          { $$ = Stmt\For_[[init: $3, cond: $5, loop: $7, stmts: $9]]; }
    | T_SWITCH parentheses_expr switch_case_list            { $$ = Stmt\Switch_[$2, $3]; }
    | T_BREAK ';'                                           { $$ = Stmt\Break_[null]; }
    | T_BREAK expr ';'                                      { $$ = Stmt\Break_[$2]; }
    | T_CONTINUE ';'                                        { $$ = Stmt\Continue_[null]; }
    | T_CONTINUE expr ';'                                   { $$ = Stmt\Continue_[$2]; }
    | T_RETURN ';'                                          { $$ = Stmt\Return_[null]; }
    | T_RETURN expr ';'                                     { $$ = Stmt\Return_[$2]; }
    | yield_expr ';'                                        { $$ = $1; }
    | T_GLOBAL global_var_list ';'                          { $$ = Stmt\Global_[$2]; }
    | T_STATIC static_var_list ';'                          { $$ = Stmt\Static_[$2]; }
    | T_ECHO expr_list ';'                                  { $$ = Stmt\Echo_[$2]; }
    | T_INLINE_HTML                                         { $$ = Stmt\InlineHTML[$1]; }
    | expr ';'                                              { $$ = $1; }
    | T_UNSET '(' variables_list ')' ';'                    { $$ = Stmt\Unset_[$3]; }
    | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
          { $$ = Stmt\Foreach_[$3, $5[0], [keyVar: null, byRef: $5[1], stmts: $7]]; }
    | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
          { $$ = Stmt\Foreach_[$3, $7[0], [keyVar: $5, byRef: $7[1], stmts: $9]]; }
    | T_DECLARE '(' declare_list ')' declare_statement      { $$ = Stmt\Declare_[$3, $5]; }
    | ';'                                                   { $$ = array(); /* means: no statement */ }
    | T_TRY '{' inner_statement_list '}' catches optional_finally
          { $$ = Stmt\TryCatch[$3, $5, $6]; }
    | T_THROW expr ';'                                      { $$ = Stmt\Throw_[$2]; }
    | T_GOTO T_STRING ';'                                   { $$ = Stmt\Goto_[$2]; }
    | T_STRING ':'                                          { $$ = Stmt\Label[$1]; }
    | error                                                 { $$ = array(); /* means: no statement */ }
;

catches:
      /* empty */                                           { init(); }
    | catches catch                                         { push($1, $2); }
;

catch:
    T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
        { $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
;

optional_finally:
      /* empty */                                           { $$ = null; }
    | T_FINALLY '{' inner_statement_list '}'                { $$ = $3; }
;

variables_list:
      variable                                              { init($1); }
    | variables_list ',' variable                           { push($1, $3); }
;

optional_ref:
      /* empty */                                           { $$ = false; }
    | '&'                                                   { $$ = true; }
;

optional_ellipsis:
      /* empty */                                           { $$ = false; }
    | T_ELLIPSIS                                            { $$ = true; }
;

function_declaration_statement:
    T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
        { $$ = Stmt\Function_[$3, [byRef: $2, params: $5, returnType: $7, stmts: $9]]; }
;

class_declaration_statement:
      class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
          { $$ = Stmt\Class_[$2, [type: $1, extends: $3, implements: $4, stmts: $6]]; }
    | T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
          { $$ = Stmt\Interface_[$2, [extends: $3, stmts: $5]]; }
    | T_TRAIT T_STRING '{' class_statement_list '}'
          { $$ = Stmt\Trait_[$2, $4]; }
;

class_entry_type:
      T_CLASS                                               { $$ = 0; }
    | T_ABSTRACT T_CLASS                                    { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    | T_FINAL T_CLASS                                       { $$ = Stmt\Class_::MODIFIER_FINAL; }
;

extends_from:
      /* empty */                                           { $$ = null; }
    | T_EXTENDS name                                        { $$ = $2; }
;

interface_extends_list:
      /* empty */                                           { $$ = array(); }
    | T_EXTENDS name_list                                   { $$ = $2; }
;

implements_list:
      /* empty */                                           { $$ = array(); }
    | T_IMPLEMENTS name_list                                { $$ = $2; }
;

name_list:
      name                                                  { init($1); }
    | name_list ',' name                                    { push($1, $3); }
;

for_statement:
      statement                                             { $$ = toArray($1); }
    | ':' inner_statement_list T_ENDFOR ';'                 { $$ = $2; }
;

foreach_statement:
      statement                                             { $$ = toArray($1); }
    | ':' inner_statement_list T_ENDFOREACH ';'             { $$ = $2; }
;

declare_statement:
      statement                                             { $$ = toArray($1); }
    | ':' inner_statement_list T_ENDDECLARE ';'             { $$ = $2; }
;

declare_list:
      declare_list_element                                  { init($1); }
    | declare_list ',' declare_list_element                 { push($1, $3); }
;

declare_list_element:
      T_STRING '=' static_scalar                            { $$ = Stmt\DeclareDeclare[$1, $3]; }
;

switch_case_list:
      '{' case_list '}'                                     { $$ = $2; }
    | '{' ';' case_list '}'                                 { $$ = $3; }
    | ':' case_list T_ENDSWITCH ';'                         { $$ = $2; }
    | ':' ';' case_list T_ENDSWITCH ';'                     { $$ = $3; }
;

case_list:
      /* empty */                                           { init(); }
    | case_list case                                        { push($1, $2); }
;

case:
      T_CASE expr case_separator inner_statement_list       { $$ = Stmt\Case_[$2, $4]; }
    | T_DEFAULT case_separator inner_statement_list         { $$ = Stmt\Case_[null, $3]; }
;

case_separator:
      ':'
    | ';'
;

while_statement:
      statement                                             { $$ = toArray($1); }
    | ':' inner_statement_list T_ENDWHILE ';'               { $$ = $2; }
;

elseif_list:
      /* empty */                                           { init(); }
    | elseif_list elseif                                    { push($1, $2); }
;

elseif:
      T_ELSEIF parentheses_expr statement                   { $$ = Stmt\ElseIf_[$2, toArray($3)]; }
;

new_elseif_list:
      /* empty */                                           { init(); }
    | new_elseif_list new_elseif                            { push($1, $2); }
;

new_elseif:
     T_ELSEIF parentheses_expr ':' inner_statement_list     { $$ = Stmt\ElseIf_[$2, $4]; }
;

else_single:
      /* empty */                                           { $$ = null; }
    | T_ELSE statement                                      { $$ = Stmt\Else_[toArray($2)]; }
;

new_else_single:
      /* empty */                                           { $$ = null; }
    | T_ELSE ':' inner_statement_list                       { $$ = Stmt\Else_[$3]; }
;

foreach_variable:
      variable                                              { $$ = array($1, false); }
    | '&' variable                                          { $$ = array($2, true); }
    | list_expr                                             { $$ = array($1, false); }
;

parameter_list:
      non_empty_parameter_list                              { $$ = $1; }
    | /* empty */                                           { $$ = array(); }
;

non_empty_parameter_list:
      parameter                                             { init($1); }
    | non_empty_parameter_list ',' parameter                { push($1, $3); }
;

parameter:
      optional_param_type optional_ref optional_ellipsis T_VARIABLE
          { $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
    | optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
          { $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
;

type:
      name                                                  { $$ = $1; }
    | T_ARRAY                                               { $$ = 'array'; }
    | T_CALLABLE                                            { $$ = 'callable'; }
;

optional_param_type:
      /* empty */                                           { $$ = null; }
    | type                                                  { $$ = $1; }
;

optional_return_type:
      /* empty */                                           { $$ = null; }
    | ':' type                                              { $$ = $2; }
;

argument_list:
      '(' ')'                                               { $$ = array(); }
    | '(' non_empty_argument_list ')'                       { $$ = $2; }
    | '(' yield_expr ')'                                    { $$ = array(Node\Arg[$2, false, false]); }
;

non_empty_argument_list:
      argument                                              { init($1); }
    | non_empty_argument_list ',' argument                  { push($1, $3); }
;

argument:
      expr                                                  { $$ = Node\Arg[$1, false, false]; }
    | '&' variable                                          { $$ = Node\Arg[$2, true, false]; }
    | T_ELLIPSIS expr                                       { $$ = Node\Arg[$2, false, true]; }
;

global_var_list:
      global_var_list ',' global_var                        { push($1, $3); }
    | global_var                                            { init($1); }
;

global_var:
      T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    | '$' variable                                          { $$ = Expr\Variable[$2]; }
    | '$' '{' expr '}'                                      { $$ = Expr\Variable[$3]; }
;

static_var_list:
      static_var_list ',' static_var                        { push($1, $3); }
    | static_var                                            { init($1); }
;

static_var:
      T_VARIABLE                                            { $$ = Stmt\StaticVar[parseVar($1), null]; }
    | T_VARIABLE '=' static_scalar                          { $$ = Stmt\StaticVar[parseVar($1), $3]; }
;

class_statement_list:
      class_statement_list class_statement                  { push($1, $2); }
    | /* empty */                                           { init(); }
;

class_statement:
      variable_modifiers property_declaration_list ';'      { $$ = Stmt\Property[$1, $2]; }
    | T_CONST constant_declaration_list ';'                 { $$ = Stmt\ClassConst[$2]; }
    | method_modifiers T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type method_body
          { $$ = Stmt\ClassMethod[$4, [type: $1, byRef: $3, params: $6, returnType: $8, stmts: $9]]; }
    | T_USE name_list trait_adaptations                     { $$ = Stmt\TraitUse[$2, $3]; }
;

trait_adaptations:
      ';'                                                   { $$ = array(); }
    | '{' trait_adaptation_list '}'                         { $$ = $2; }
;

trait_adaptation_list:
      /* empty */                                           { init(); }
    | trait_adaptation_list trait_adaptation                { push($1, $2); }
;

trait_adaptation:
      trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
          { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
    | trait_method_reference T_AS member_modifier T_STRING ';'
          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
    | trait_method_reference T_AS member_modifier ';'
          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
    | trait_method_reference T_AS T_STRING ';'
          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;

trait_method_reference_fully_qualified:
      name T_PAAMAYIM_NEKUDOTAYIM T_STRING                  { $$ = array($1, $3); }
;
trait_method_reference:
      trait_method_reference_fully_qualified                { $$ = $1; }
    | T_STRING                                              { $$ = array(null, $1); }
;

method_body:
      ';' /* abstract method */                             { $$ = null; }
    | '{' inner_statement_list '}'                          { $$ = $2; }
;

variable_modifiers:
      non_empty_member_modifiers                            { $$ = $1; }
    | T_VAR                                                 { $$ = 0; }
;

method_modifiers:
      /* empty */                                           { $$ = 0; }
    | non_empty_member_modifiers                            { $$ = $1; }
;

non_empty_member_modifiers:
      member_modifier                                       { $$ = $1; }
    | non_empty_member_modifiers member_modifier            { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
;

member_modifier:
      T_PUBLIC                                              { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
    | T_PROTECTED                                           { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
    | T_PRIVATE                                             { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
    | T_STATIC                                              { $$ = Stmt\Class_::MODIFIER_STATIC; }
    | T_ABSTRACT                                            { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    | T_FINAL                                               { $$ = Stmt\Class_::MODIFIER_FINAL; }
;

property_declaration_list:
      property_declaration                                  { init($1); }
    | property_declaration_list ',' property_declaration    { push($1, $3); }
;

property_declaration:
      T_VARIABLE                                            { $$ = Stmt\PropertyProperty[parseVar($1), null]; }
    | T_VARIABLE '=' static_scalar                          { $$ = Stmt\PropertyProperty[parseVar($1), $3]; }
;

expr_list:
      expr_list ',' expr                                    { push($1, $3); }
    | expr                                                  { init($1); }
;

for_expr:
      /* empty */                                           { $$ = array(); }
    | expr_list                                             { $$ = $1; }
;

expr:
      variable                                              { $$ = $1; }
    | list_expr '=' expr                                    { $$ = Expr\Assign[$1, $3]; }
    | variable '=' expr                                     { $$ = Expr\Assign[$1, $3]; }
    | variable '=' '&' variable                             { $$ = Expr\AssignRef[$1, $4]; }
    | variable '=' '&' new_expr                             { $$ = Expr\AssignRef[$1, $4]; }
    | new_expr                                              { $$ = $1; }
    | T_CLONE expr                                          { $$ = Expr\Clone_[$2]; }
    | variable T_PLUS_EQUAL expr                            { $$ = Expr\AssignOp\Plus      [$1, $3]; }
    | variable T_MINUS_EQUAL expr                           { $$ = Expr\AssignOp\Minus     [$1, $3]; }
    | variable T_MUL_EQUAL expr                             { $$ = Expr\AssignOp\Mul       [$1, $3]; }
    | variable T_DIV_EQUAL expr                             { $$ = Expr\AssignOp\Div       [$1, $3]; }
    | variable T_CONCAT_EQUAL expr                          { $$ = Expr\AssignOp\Concat    [$1, $3]; }
    | variable T_MOD_EQUAL expr                             { $$ = Expr\AssignOp\Mod       [$1, $3]; }
    | variable T_AND_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; }
    | variable T_OR_EQUAL expr                              { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; }
    | variable T_XOR_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; }
    | variable T_SL_EQUAL expr                              { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
    | variable T_SR_EQUAL expr                              { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
    | variable T_POW_EQUAL expr                             { $$ = Expr\AssignOp\Pow       [$1, $3]; }
    | variable T_INC                                        { $$ = Expr\PostInc[$1]; }
    | T_INC variable                                        { $$ = Expr\PreInc [$2]; }
    | variable T_DEC                                        { $$ = Expr\PostDec[$1]; }
    | T_DEC variable                                        { $$ = Expr\PreDec [$2]; }
    | expr T_BOOLEAN_OR expr                                { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
    | expr T_BOOLEAN_AND expr                               { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
    | expr T_LOGICAL_OR expr                                { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
    | expr T_LOGICAL_AND expr                               { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
    | expr T_LOGICAL_XOR expr                               { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
    | expr '|' expr                                         { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
    | expr '&' expr                                         { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
    | expr '^' expr                                         { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
    | expr '.' expr                                         { $$ = Expr\BinaryOp\Concat    [$1, $3]; }
    | expr '+' expr                                         { $$ = Expr\BinaryOp\Plus      [$1, $3]; }
    | expr '-' expr                                         { $$ = Expr\BinaryOp\Minus     [$1, $3]; }
    | expr '*' expr                                         { $$ = Expr\BinaryOp\Mul       [$1, $3]; }
    | expr '/' expr                                         { $$ = Expr\BinaryOp\Div       [$1, $3]; }
    | expr '%' expr                                         { $$ = Expr\BinaryOp\Mod       [$1, $3]; }
    | expr T_SL expr                                        { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
    | expr T_SR expr                                        { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
    | expr T_POW expr                                       { $$ = Expr\BinaryOp\Pow       [$1, $3]; }
    | '+' expr %prec T_INC                                  { $$ = Expr\UnaryPlus [$2]; }
    | '-' expr %prec T_INC                                  { $$ = Expr\UnaryMinus[$2]; }
    | '!' expr                                              { $$ = Expr\BooleanNot[$2]; }
    | '~' expr                                              { $$ = Expr\BitwiseNot[$2]; }
    | expr T_IS_IDENTICAL expr                              { $$ = Expr\BinaryOp\Identical     [$1, $3]; }
    | expr T_IS_NOT_IDENTICAL expr                          { $$ = Expr\BinaryOp\NotIdentical  [$1, $3]; }
    | expr T_IS_EQUAL expr                                  { $$ = Expr\BinaryOp\Equal         [$1, $3]; }
    | expr T_IS_NOT_EQUAL expr                              { $$ = Expr\BinaryOp\NotEqual      [$1, $3]; }
    | expr T_SPACESHIP expr                                 { $$ = Expr\BinaryOp\Spaceship     [$1, $3]; }
    | expr '<' expr                                         { $$ = Expr\BinaryOp\Smaller       [$1, $3]; }
    | expr T_IS_SMALLER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
    | expr '>' expr                                         { $$ = Expr\BinaryOp\Greater       [$1, $3]; }
    | expr T_IS_GREATER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
    | expr T_INSTANCEOF class_name_reference                { $$ = Expr\Instanceof_[$1, $3]; }
    | parentheses_expr                                      { $$ = $1; }
    /* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */
    | '(' new_expr ')'                                      { $$ = $2; }
    | expr '?' expr ':' expr                                { $$ = Expr\Ternary[$1, $3,   $5]; }
    | expr '?' ':' expr                                     { $$ = Expr\Ternary[$1, null, $4]; }
    | expr T_COALESCE expr                                  { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
    | T_ISSET '(' variables_list ')'                        { $$ = Expr\Isset_[$3]; }
    | T_EMPTY '(' expr ')'                                  { $$ = Expr\Empty_[$3]; }
    | T_INCLUDE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
    | T_INCLUDE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
    | T_EVAL parentheses_expr                               { $$ = Expr\Eval_[$2]; }
    | T_REQUIRE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
    | T_REQUIRE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
    | T_INT_CAST expr                                       { $$ = Expr\Cast\Int_    [$2]; }
    | T_DOUBLE_CAST expr                                    { $$ = Expr\Cast\Double  [$2]; }
    | T_STRING_CAST expr                                    { $$ = Expr\Cast\String_ [$2]; }
    | T_ARRAY_CAST expr                                     { $$ = Expr\Cast\Array_  [$2]; }
    | T_OBJECT_CAST expr                                    { $$ = Expr\Cast\Object_ [$2]; }
    | T_BOOL_CAST expr                                      { $$ = Expr\Cast\Bool_   [$2]; }
    | T_UNSET_CAST expr                                     { $$ = Expr\Cast\Unset_  [$2]; }
    | T_EXIT exit_expr                                      { $$ = Expr\Exit_        [$2]; }
    | '@' expr                                              { $$ = Expr\ErrorSuppress[$2]; }
    | scalar                                                { $$ = $1; }
    | array_expr                                            { $$ = $1; }
    | scalar_dereference                                    { $$ = $1; }
    | '`' backticks_expr '`'                                { $$ = Expr\ShellExec[$2]; }
    | T_PRINT expr                                          { $$ = Expr\Print_[$2]; }
    | T_YIELD                                               { $$ = Expr\Yield_[null, null]; }
    | T_YIELD_FROM expr                                     { $$ = Expr\YieldFrom[$2]; }
    | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
      '{' inner_statement_list '}'
          { $$ = Expr\Closure[[static: false, byRef: $2, params: $4, uses: $6, returnType: $7, stmts: $9]]; }
    | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
      '{' inner_statement_list '}'
          { $$ = Expr\Closure[[static: true, byRef: $3, params: $5, uses: $7, returnType: $8, stmts: $10]]; }
;

parentheses_expr:
      '(' expr ')'                                          { $$ = $2; }
    | '(' yield_expr ')'                                    { $$ = $2; }
;

yield_expr:
      T_YIELD expr                                          { $$ = Expr\Yield_[$2, null]; }
    | T_YIELD expr T_DOUBLE_ARROW expr                      { $$ = Expr\Yield_[$4, $2]; }
;

array_expr:
      T_ARRAY '(' array_pair_list ')'                       { $$ = Expr\Array_[$3]; }
    | '[' array_pair_list ']'                               { $$ = Expr\Array_[$2]; }
;

scalar_dereference:
      array_expr '[' dim_offset ']'                         { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
          { $$ = Expr\ArrayDimFetch[Scalar\String_[Scalar\String_::parse($1)], $3]; }
    | constant '[' dim_offset ']'                           { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | scalar_dereference '[' dim_offset ']'                 { $$ = Expr\ArrayDimFetch[$1, $3]; }
    /* alternative array syntax missing intentionally */
;

anonymous_class:
      T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
          { $$ = array(Stmt\Class_[null, [type: 0, extends: $3, implements: $4, stmts: $6]], $2); }

new_expr:
      T_NEW class_name_reference ctor_arguments             { $$ = Expr\New_[$2, $3]; }
    | T_NEW anonymous_class
          { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
;

lexical_vars:
      /* empty */                                           { $$ = array(); }
    | T_USE '(' lexical_var_list ')'                        { $$ = $3; }
;

lexical_var_list:
      lexical_var                                           { init($1); }
    | lexical_var_list ',' lexical_var                      { push($1, $3); }
;

lexical_var:
      optional_ref T_VARIABLE                               { $$ = Expr\ClosureUse[parseVar($2), $1]; }
;

function_call:
      name argument_list                                    { $$ = Expr\FuncCall[$1, $2]; }
    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_STRING argument_list
          { $$ = Expr\StaticCall[$1, $3, $4]; }
    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
          { $$ = Expr\StaticCall[$1, $4, $6]; }
    | static_property argument_list {
            if ($1 instanceof Node\Expr\StaticPropertyFetch) {
                $$ = Expr\StaticCall[$1->class, Expr\Variable[$1->name], $2];
            } elseif ($1 instanceof Node\Expr\ArrayDimFetch) {
                $tmp = $1;
                while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
                    $tmp = $tmp->var;
                }

                $$ = Expr\StaticCall[$tmp->var->class, $1, $2];
                $tmp->var = Expr\Variable[$tmp->var->name];
            } else {
                throw new \Exception;
            }
          }
    | variable_without_objects argument_list
          { $$ = Expr\FuncCall[$1, $2]; }
    | function_call '[' dim_offset ']'                      { $$ = Expr\ArrayDimFetch[$1, $3]; }
      /* alternative array syntax missing intentionally */
;

class_name:
      T_STATIC                                              { $$ = Name[$1]; }
    | name                                                  { $$ = $1; }
;

name:
      namespace_name_parts                                  { $$ = Name[$1]; }
    | T_NS_SEPARATOR namespace_name_parts                   { $$ = Name\FullyQualified[$2]; }
    | T_NAMESPACE T_NS_SEPARATOR namespace_name_parts       { $$ = Name\Relative[$3]; }
;

class_name_reference:
      class_name                                            { $$ = $1; }
    | dynamic_class_name_reference                          { $$ = $1; }
;

dynamic_class_name_reference:
      object_access_for_dcnr                                { $$ = $1; }
    | base_variable                                         { $$ = $1; }
;

class_name_or_var:
      class_name                                            { $$ = $1; }
    | reference_variable                                    { $$ = $1; }
;

object_access_for_dcnr:
      base_variable T_OBJECT_OPERATOR object_property
          { $$ = Expr\PropertyFetch[$1, $3]; }
    | object_access_for_dcnr T_OBJECT_OPERATOR object_property
          { $$ = Expr\PropertyFetch[$1, $3]; }
    | object_access_for_dcnr '[' dim_offset ']'             { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | object_access_for_dcnr '{' expr '}'                   { $$ = Expr\ArrayDimFetch[$1, $3]; }
;

exit_expr:
      /* empty */                                           { $$ = null; }
    | '(' ')'                                               { $$ = null; }
    | parentheses_expr                                      { $$ = $1; }
;

backticks_expr:
      /* empty */                                           { $$ = array(); }
    | T_ENCAPSED_AND_WHITESPACE                             { $$ = array(Scalar\String_::parseEscapeSequences($1, '`')); }
    | encaps_list                                           { parseEncapsed($1, '`'); $$ = $1; }
;

ctor_arguments:
      /* empty */                                           { $$ = array(); }
    | argument_list                                         { $$ = $1; }
;

common_scalar:
      T_LNUMBER                                             { $$ = Scalar\LNumber[Scalar\LNumber::parse($1)]; }
    | T_DNUMBER                                             { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
    | T_CONSTANT_ENCAPSED_STRING                            { $$ = Scalar\String_[Scalar\String_::parse($1)]; }
    | T_LINE                                                { $$ = Scalar\MagicConst\Line[]; }
    | T_FILE                                                { $$ = Scalar\MagicConst\File[]; }
    | T_DIR                                                 { $$ = Scalar\MagicConst\Dir[]; }
    | T_CLASS_C                                             { $$ = Scalar\MagicConst\Class_[]; }
    | T_TRAIT_C                                             { $$ = Scalar\MagicConst\Trait_[]; }
    | T_METHOD_C                                            { $$ = Scalar\MagicConst\Method[]; }
    | T_FUNC_C                                              { $$ = Scalar\MagicConst\Function_[]; }
    | T_NS_C                                                { $$ = Scalar\MagicConst\Namespace_[]; }
    | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
          { $$ = Scalar\String_[Scalar\String_::parseDocString($1, $2)]; }
    | T_START_HEREDOC T_END_HEREDOC
          { $$ = Scalar\String_['']; }
;

static_scalar:
      common_scalar                                         { $$ = $1; }
    | class_name T_PAAMAYIM_NEKUDOTAYIM class_const_name    { $$ = Expr\ClassConstFetch[$1, $3]; }
    | name                                                  { $$ = Expr\ConstFetch[$1]; }
    | T_ARRAY '(' static_array_pair_list ')'                { $$ = Expr\Array_[$3]; }
    | '[' static_array_pair_list ']'                        { $$ = Expr\Array_[$2]; }
    | static_operation                                      { $$ = $1; }
;

static_operation:
      static_scalar T_BOOLEAN_OR static_scalar              { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
    | static_scalar T_BOOLEAN_AND static_scalar             { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
    | static_scalar T_LOGICAL_OR static_scalar              { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
    | static_scalar T_LOGICAL_AND static_scalar             { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
    | static_scalar T_LOGICAL_XOR static_scalar             { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
    | static_scalar '|' static_scalar                       { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
    | static_scalar '&' static_scalar                       { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
    | static_scalar '^' static_scalar                       { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
    | static_scalar '.' static_scalar                       { $$ = Expr\BinaryOp\Concat    [$1, $3]; }
    | static_scalar '+' static_scalar                       { $$ = Expr\BinaryOp\Plus      [$1, $3]; }
    | static_scalar '-' static_scalar                       { $$ = Expr\BinaryOp\Minus     [$1, $3]; }
    | static_scalar '*' static_scalar                       { $$ = Expr\BinaryOp\Mul       [$1, $3]; }
    | static_scalar '/' static_scalar                       { $$ = Expr\BinaryOp\Div       [$1, $3]; }
    | static_scalar '%' static_scalar                       { $$ = Expr\BinaryOp\Mod       [$1, $3]; }
    | static_scalar T_SL static_scalar                      { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
    | static_scalar T_SR static_scalar                      { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
    | static_scalar T_POW static_scalar                     { $$ = Expr\BinaryOp\Pow       [$1, $3]; }
    | '+' static_scalar %prec T_INC                         { $$ = Expr\UnaryPlus [$2]; }
    | '-' static_scalar %prec T_INC                         { $$ = Expr\UnaryMinus[$2]; }
    | '!' static_scalar                                     { $$ = Expr\BooleanNot[$2]; }
    | '~' static_scalar                                     { $$ = Expr\BitwiseNot[$2]; }
    | static_scalar T_IS_IDENTICAL static_scalar            { $$ = Expr\BinaryOp\Identical     [$1, $3]; }
    | static_scalar T_IS_NOT_IDENTICAL static_scalar        { $$ = Expr\BinaryOp\NotIdentical  [$1, $3]; }
    | static_scalar T_IS_EQUAL static_scalar                { $$ = Expr\BinaryOp\Equal         [$1, $3]; }
    | static_scalar T_IS_NOT_EQUAL static_scalar            { $$ = Expr\BinaryOp\NotEqual      [$1, $3]; }
    | static_scalar '<' static_scalar                       { $$ = Expr\BinaryOp\Smaller       [$1, $3]; }
    | static_scalar T_IS_SMALLER_OR_EQUAL static_scalar     { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
    | static_scalar '>' static_scalar                       { $$ = Expr\BinaryOp\Greater       [$1, $3]; }
    | static_scalar T_IS_GREATER_OR_EQUAL static_scalar     { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
    | static_scalar '?' static_scalar ':' static_scalar     { $$ = Expr\Ternary[$1, $3,   $5]; }
    | static_scalar '?' ':' static_scalar                   { $$ = Expr\Ternary[$1, null, $4]; }
    | static_scalar '[' static_scalar ']'                   { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | '(' static_scalar ')'                                 { $$ = $2; }
;

constant:
      name                                                  { $$ = Expr\ConstFetch[$1]; }
    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
          { $$ = Expr\ClassConstFetch[$1, $3]; }
;

scalar:
      common_scalar                                         { $$ = $1; }
    | constant                                              { $$ = $1; }
    | '"' encaps_list '"'
          { parseEncapsed($2, '"'); $$ = Scalar\Encapsed[$2]; }
    | T_START_HEREDOC encaps_list T_END_HEREDOC
          { parseEncapsedDoc($2); $$ = Scalar\Encapsed[$2]; }
;

class_const_name:
      T_STRING                                              { $$ = $1; }
    | T_CLASS                                               { $$ = 'class'; }
;

static_array_pair_list:
      /* empty */                                           { $$ = array(); }
    | non_empty_static_array_pair_list optional_comma       { $$ = $1; }
;

optional_comma:
      /* empty */
    | ','
;

non_empty_static_array_pair_list:
      non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); }
    | static_array_pair                                      { init($1); }
;

static_array_pair:
      static_scalar T_DOUBLE_ARROW static_scalar            { $$ = Expr\ArrayItem[$3, $1,   false]; }
    | static_scalar                                         { $$ = Expr\ArrayItem[$1, null, false]; }
;

variable:
      object_access                                         { $$ = $1; }
    | base_variable                                         { $$ = $1; }
    | function_call                                         { $$ = $1; }
    | new_expr_array_deref                                  { $$ = $1; }
;

new_expr_array_deref:
      '(' new_expr ')' '[' dim_offset ']'                   { $$ = Expr\ArrayDimFetch[$2, $5]; }
    | new_expr_array_deref '[' dim_offset ']'               { $$ = Expr\ArrayDimFetch[$1, $3]; }
      /* alternative array syntax missing intentionally */
;

object_access:
      variable_or_new_expr T_OBJECT_OPERATOR object_property
          { $$ = Expr\PropertyFetch[$1, $3]; }
    | variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list
          { $$ = Expr\MethodCall[$1, $3, $4]; }
    | object_access argument_list                           { $$ = Expr\FuncCall[$1, $2]; }
    | object_access '[' dim_offset ']'                      { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | object_access '{' expr '}'                            { $$ = Expr\ArrayDimFetch[$1, $3]; }
;

variable_or_new_expr:
      variable                                              { $$ = $1; }
    | '(' new_expr ')'                                      { $$ = $2; }
;

variable_without_objects:
      reference_variable                                    { $$ = $1; }
    | '$' variable_without_objects                          { $$ = Expr\Variable[$2]; }
;

base_variable:
      variable_without_objects                              { $$ = $1; }
    | static_property                                       { $$ = $1; }
;

static_property:
      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable
          { $$ = Expr\StaticPropertyFetch[$1, $4]; }
    | static_property_with_arrays                           { $$ = $1; }
;

static_property_with_arrays:
      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_VARIABLE
          { $$ = Expr\StaticPropertyFetch[$1, parseVar($3)]; }
    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}'
          { $$ = Expr\StaticPropertyFetch[$1, $5]; }
    | static_property_with_arrays '[' dim_offset ']'        { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | static_property_with_arrays '{' expr '}'              { $$ = Expr\ArrayDimFetch[$1, $3]; }
;

reference_variable:
      reference_variable '[' dim_offset ']'                 { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | reference_variable '{' expr '}'                       { $$ = Expr\ArrayDimFetch[$1, $3]; }
    | T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    | '$' '{' expr '}'                                      { $$ = Expr\Variable[$3]; }
;

dim_offset:
      /* empty */                                           { $$ = null; }
    | expr                                                  { $$ = $1; }
;

object_property:
      T_STRING                                              { $$ = $1; }
    | '{' expr '}'                                          { $$ = $2; }
    | variable_without_objects                              { $$ = $1; }
;

list_expr:
      T_LIST '(' list_expr_elements ')'                     { $$ = Expr\List_[$3]; }
;

list_expr_elements:
      list_expr_elements ',' list_expr_element              { push($1, $3); }
    | list_expr_element                                     { init($1); }
;

list_expr_element:
      variable                                              { $$ = $1; }
    | list_expr                                             { $$ = $1; }
    | /* empty */                                           { $$ = null; }
;

array_pair_list:
      /* empty */                                           { $$ = array(); }
    | non_empty_array_pair_list optional_comma              { $$ = $1; }
;

non_empty_array_pair_list:
      non_empty_array_pair_list ',' array_pair              { push($1, $3); }
    | array_pair                                            { init($1); }
;

array_pair:
      expr T_DOUBLE_ARROW expr                              { $$ = Expr\ArrayItem[$3, $1,   false]; }
    | expr                                                  { $$ = Expr\ArrayItem[$1, null, false]; }
    | expr T_DOUBLE_ARROW '&' variable                      { $$ = Expr\ArrayItem[$4, $1,   true]; }
    | '&' variable                                          { $$ = Expr\ArrayItem[$2, null, true]; }
;

encaps_list:
      encaps_list encaps_var                                { push($1, $2); }
    | encaps_list T_ENCAPSED_AND_WHITESPACE                 { push($1, $2); }
    | encaps_var                                            { init($1); }
    | T_ENCAPSED_AND_WHITESPACE encaps_var                  { init($1, $2); }
;

encaps_var:
      T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    | T_VARIABLE '[' encaps_var_offset ']'                  { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
    | T_VARIABLE T_OBJECT_OPERATOR T_STRING                 { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
    | T_DOLLAR_OPEN_CURLY_BRACES expr '}'                   { $$ = Expr\Variable[$2]; }
    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'       { $$ = Expr\Variable[$2]; }
    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
          { $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
    | T_CURLY_OPEN variable '}'                             { $$ = $2; }
;

encaps_var_offset:
      T_STRING                                              { $$ = Scalar\String_[$1]; }
    | T_NUM_STRING                                          { $$ = Scalar\String_[$1]; }
    | T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
;

%%
<?php

namespace PhpParser;

/**
 * @codeCoverageIgnore
 */
class Autoloader
{
    /** @var bool Whether the autoloader has been registered. */
    private static $registered = false;

    /** @var bool Whether we're running on PHP 7. */
    private static $runningOnPhp7;

    /**
     * Registers PhpParser\Autoloader as an SPL autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader instead of appending
     */
    static public function register($prepend = false) {
        if (self::$registered === true) {
            return;
        }

        spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend);
        self::$registered = true;
        self::$runningOnPhp7 = version_compare(PHP_VERSION, '7.0-dev', '>=');
    }

    /**
     * Handles autoloading of classes.
     *
     * @param string $class A class name.
     */
    static public function autoload($class) {
        if (0 === strpos($class, 'PhpParser\\')) {
            if (isset(self::$php7AliasesOldToNew[$class])) {
                if (self::$runningOnPhp7) {
                    return;
                }

                // Load the new class, alias will be registered afterwards
                $class = self::$php7AliasesOldToNew[$class];
            }

            $fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
            if (file_exists($fileName)) {
                require $fileName;
            }

            if (isset(self::$php7AliasesNewToOld[$class])) {
                // New class name was used, register alias for old one, otherwise
                // it won't be usable in "instanceof" and other non-autoloading places.
                if (!self::$runningOnPhp7) {
                    class_alias($class, self::$php7AliasesNewToOld[$class]);
                }
            }
        } else if (0 === strpos($class, 'PHPParser_')) {
            if (isset(self::$nonNamespacedAliases[$class])) {
                // Register all aliases at once to avoid dependency issues
                self::registerNonNamespacedAliases();
            }
        }
    }

    private static function registerNonNamespacedAliases() {
        foreach (self::$nonNamespacedAliases as $old => $new) {
            class_alias($new, $old);
        }
    }

    private static $php7AliasesOldToNew = array(
        'PhpParser\Node\Expr\Cast\Bool' => 'PhpParser\Node\Expr\Cast\Bool_',
        'PhpParser\Node\Expr\Cast\Int' => 'PhpParser\Node\Expr\Cast\Int_',
        'PhpParser\Node\Expr\Cast\Object' => 'PhpParser\Node\Expr\Cast\Object_',
        'PhpParser\Node\Expr\Cast\String' => 'PhpParser\Node\Expr\Cast\String_',
        'PhpParser\Node\Scalar\String' => 'PhpParser\Node\Scalar\String_',
    );

    private static $php7AliasesNewToOld = array(
        'PhpParser\Node\Expr\Cast\Bool_' => 'PhpParser\Node\Expr\Cast\Bool',
        'PhpParser\Node\Expr\Cast\Int_' => 'PhpParser\Node\Expr\Cast\Int',
        'PhpParser\Node\Expr\Cast\Object_' => 'PhpParser\Node\Expr\Cast\Object',
        'PhpParser\Node\Expr\Cast\String_' => 'PhpParser\Node\Expr\Cast\String',
        'PhpParser\Node\Scalar\String_' => 'PhpParser\Node\Scalar\String',
    );

    private static $nonNamespacedAliases = array(
        'PHPParser_Builder' => 'PhpParser\Builder',
        'PHPParser_BuilderAbstract' => 'PhpParser\BuilderAbstract',
        'PHPParser_BuilderFactory' => 'PhpParser\BuilderFactory',
        'PHPParser_Comment' => 'PhpParser\Comment',
        'PHPParser_Comment_Doc' => 'PhpParser\Comment\Doc',
        'PHPParser_Error' => 'PhpParser\Error',
        'PHPParser_Lexer' => 'PhpParser\Lexer',
        'PHPParser_Lexer_Emulative' => 'PhpParser\Lexer\Emulative',
        'PHPParser_Node' => 'PhpParser\Node',
        'PHPParser_NodeAbstract' => 'PhpParser\NodeAbstract',
        'PHPParser_NodeDumper' => 'PhpParser\NodeDumper',
        'PHPParser_NodeTraverser' => 'PhpParser\NodeTraverser',
        'PHPParser_NodeTraverserInterface' => 'PhpParser\NodeTraverserInterface',
        'PHPParser_NodeVisitor' => 'PhpParser\NodeVisitor',
        'PHPParser_NodeVisitor_NameResolver' => 'PhpParser\NodeVisitor\NameResolver',
        'PHPParser_NodeVisitorAbstract' => 'PhpParser\NodeVisitorAbstract',
        'PHPParser_Parser' => 'PhpParser\Parser',
        'PHPParser_PrettyPrinterAbstract' => 'PhpParser\PrettyPrinterAbstract',
        'PHPParser_PrettyPrinter_Default' => 'PhpParser\PrettyPrinter\Standard',
        'PHPParser_PrettyPrinter_Zend' => 'PhpParser\PrettyPrinter\Standard',
        'PHPParser_Serializer' => 'PhpParser\Serializer',
        'PHPParser_Serializer_XML' => 'PhpParser\Serializer\XML',
        'PHPParser_Unserializer' => 'PhpParser\Unserializer',
        'PHPParser_Unserializer_XML' => 'PhpParser\Unserializer\XML',

        'PHPParser_Builder_Class' => 'PhpParser\Builder\Class_',
        'PHPParser_Builder_Function' => 'PhpParser\Builder\Function_',
        'PHPParser_Builder_Interface' => 'PhpParser\Builder\Interface_',
        'PHPParser_Builder_Method' => 'PhpParser\Builder\Method',
        'PHPParser_Builder_Param' => 'PhpParser\Builder\Param',
        'PHPParser_Builder_Property' => 'PhpParser\Builder\Property',

        'PHPParser_Node_Arg' => 'PhpParser\Node\Arg',
        'PHPParser_Node_Const' => 'PhpParser\Node\Const_',
        'PHPParser_Node_Expr' => 'PhpParser\Node\Expr',
        'PHPParser_Node_Name' => 'PhpParser\Node\Name',
        'PHPParser_Node_Name_FullyQualified' => 'PhpParser\Node\Name\FullyQualified',
        'PHPParser_Node_Name_Relative' => 'PhpParser\Node\Name\Relative',
        'PHPParser_Node_Param' => 'PhpParser\Node\Param',
        'PHPParser_Node_Scalar' => 'PhpParser\Node\Scalar',
        'PHPParser_Node_Stmt' => 'PhpParser\Node\Stmt',

        'PHPParser_Node_Stmt_Break' => 'PhpParser\Node\Stmt\Break_',
        'PHPParser_Node_Stmt_Case' => 'PhpParser\Node\Stmt\Case_',
        'PHPParser_Node_Stmt_Catch' => 'PhpParser\Node\Stmt\Catch_',
        'PHPParser_Node_Stmt_Class' => 'PhpParser\Node\Stmt\Class_',
        'PHPParser_Node_Stmt_ClassConst' => 'PhpParser\Node\Stmt\ClassConst',
        'PHPParser_Node_Stmt_ClassMethod' => 'PhpParser\Node\Stmt\ClassMethod',
        'PHPParser_Node_Stmt_Const' => 'PhpParser\Node\Stmt\Const_',
        'PHPParser_Node_Stmt_Continue' => 'PhpParser\Node\Stmt\Continue_',
        'PHPParser_Node_Stmt_Declare' => 'PhpParser\Node\Stmt\Declare_',
        'PHPParser_Node_Stmt_DeclareDeclare' => 'PhpParser\Node\Stmt\DeclareDeclare',
        'PHPParser_Node_Stmt_Do' => 'PhpParser\Node\Stmt\Do_',
        'PHPParser_Node_Stmt_Echo' => 'PhpParser\Node\Stmt\Echo_',
        'PHPParser_Node_Stmt_Else' => 'PhpParser\Node\Stmt\Else_',
        'PHPParser_Node_Stmt_ElseIf' => 'PhpParser\Node\Stmt\ElseIf_',
        'PHPParser_Node_Stmt_For' => 'PhpParser\Node\Stmt\For_',
        'PHPParser_Node_Stmt_Foreach' => 'PhpParser\Node\Stmt\Foreach_',
        'PHPParser_Node_Stmt_Function' => 'PhpParser\Node\Stmt\Function_',
        'PHPParser_Node_Stmt_Global' => 'PhpParser\Node\Stmt\Global_',
        'PHPParser_Node_Stmt_Goto' => 'PhpParser\Node\Stmt\Goto_',
        'PHPParser_Node_Stmt_HaltCompiler' => 'PhpParser\Node\Stmt\HaltCompiler',
        'PHPParser_Node_Stmt_If' => 'PhpParser\Node\Stmt\If_',
        'PHPParser_Node_Stmt_InlineHTML' => 'PhpParser\Node\Stmt\InlineHTML',
        'PHPParser_Node_Stmt_Interface' => 'PhpParser\Node\Stmt\Interface_',
        'PHPParser_Node_Stmt_Label' => 'PhpParser\Node\Stmt\Label',
        'PHPParser_Node_Stmt_Namespace' => 'PhpParser\Node\Stmt\Namespace_',
        'PHPParser_Node_Stmt_Property' => 'PhpParser\Node\Stmt\Property',
        'PHPParser_Node_Stmt_PropertyProperty' => 'PhpParser\Node\Stmt\PropertyProperty',
        'PHPParser_Node_Stmt_Return' => 'PhpParser\Node\Stmt\Return_',
        'PHPParser_Node_Stmt_Static' => 'PhpParser\Node\Stmt\Static_',
        'PHPParser_Node_Stmt_StaticVar' => 'PhpParser\Node\Stmt\StaticVar',
        'PHPParser_Node_Stmt_Switch' => 'PhpParser\Node\Stmt\Switch_',
        'PHPParser_Node_Stmt_Throw' => 'PhpParser\Node\Stmt\Throw_',
        'PHPParser_Node_Stmt_Trait' => 'PhpParser\Node\Stmt\Trait_',
        'PHPParser_Node_Stmt_TraitUse' => 'PhpParser\Node\Stmt\TraitUse',
        'PHPParser_Node_Stmt_TraitUseAdaptation' => 'PhpParser\Node\Stmt\TraitUseAdaptation',
        'PHPParser_Node_Stmt_TraitUseAdaptation_Alias' => 'PhpParser\Node\Stmt\TraitUseAdaptation\Alias',
        'PHPParser_Node_Stmt_TraitUseAdaptation_Precedence' => 'PhpParser\Node\Stmt\TraitUseAdaptation\Precedence',
        'PHPParser_Node_Stmt_TryCatch' => 'PhpParser\Node\Stmt\TryCatch',
        'PHPParser_Node_Stmt_Unset' => 'PhpParser\Node\Stmt\Unset_',
        'PHPParser_Node_Stmt_UseUse' => 'PhpParser\Node\Stmt\UseUse',
        'PHPParser_Node_Stmt_Use' => 'PhpParser\Node\Stmt\Use_',
        'PHPParser_Node_Stmt_While' => 'PhpParser\Node\Stmt\While_',

        'PHPParser_Node_Expr_AssignBitwiseAnd' => 'PhpParser\Node\Expr\AssignOp\BitwiseAnd',
        'PHPParser_Node_Expr_AssignBitwiseOr' => 'PhpParser\Node\Expr\AssignOp\BitwiseOr',
        'PHPParser_Node_Expr_AssignBitwiseXor' => 'PhpParser\Node\Expr\AssignOp\BitwiseXor',
        'PHPParser_Node_Expr_AssignConcat' => 'PhpParser\Node\Expr\AssignOp\Concat',
        'PHPParser_Node_Expr_AssignDiv' => 'PhpParser\Node\Expr\AssignOp\Div',
        'PHPParser_Node_Expr_AssignMinus' => 'PhpParser\Node\Expr\AssignOp\Minus',
        'PHPParser_Node_Expr_AssignMod' => 'PhpParser\Node\Expr\AssignOp\Mod',
        'PHPParser_Node_Expr_AssignMul' => 'PhpParser\Node\Expr\AssignOp\Mul',
        'PHPParser_Node_Expr_AssignPlus' => 'PhpParser\Node\Expr\AssignOp\Plus',
        'PHPParser_Node_Expr_AssignShiftLeft' => 'PhpParser\Node\Expr\AssignOp\ShiftLeft',
        'PHPParser_Node_Expr_AssignShiftRight' => 'PhpParser\Node\Expr\AssignOp\ShiftRight',

        'PHPParser_Node_Expr_Cast' => 'PhpParser\Node\Expr\Cast',
        'PHPParser_Node_Expr_Cast_Array' => 'PhpParser\Node\Expr\Cast\Array_',
        'PHPParser_Node_Expr_Cast_Bool' => 'PhpParser\Node\Expr\Cast\Bool_',
        'PHPParser_Node_Expr_Cast_Double' => 'PhpParser\Node\Expr\Cast\Double',
        'PHPParser_Node_Expr_Cast_Int' => 'PhpParser\Node\Expr\Cast\Int_',
        'PHPParser_Node_Expr_Cast_Object' => 'PhpParser\Node\Expr\Cast\Object_',
        'PHPParser_Node_Expr_Cast_String' => 'PhpParser\Node\Expr\Cast\String_',
        'PHPParser_Node_Expr_Cast_Unset' => 'PhpParser\Node\Expr\Cast\Unset_',

        'PHPParser_Node_Expr_BitwiseAnd' => 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd',
        'PHPParser_Node_Expr_BitwiseOr' => 'PhpParser\Node\Expr\BinaryOp\BitwiseOr',
        'PHPParser_Node_Expr_BitwiseXor' => 'PhpParser\Node\Expr\BinaryOp\BitwiseXor',
        'PHPParser_Node_Expr_BooleanAnd' => 'PhpParser\Node\Expr\BinaryOp\BooleanAnd',
        'PHPParser_Node_Expr_BooleanOr' => 'PhpParser\Node\Expr\BinaryOp\BooleanOr',
        'PHPParser_Node_Expr_Concat' => 'PhpParser\Node\Expr\BinaryOp\Concat',
        'PHPParser_Node_Expr_Div' => 'PhpParser\Node\Expr\BinaryOp\Div',
        'PHPParser_Node_Expr_Equal' => 'PhpParser\Node\Expr\BinaryOp\Equal',
        'PHPParser_Node_Expr_Greater' => 'PhpParser\Node\Expr\BinaryOp\Greater',
        'PHPParser_Node_Expr_GreaterOrEqual' => 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual',
        'PHPParser_Node_Expr_Identical' => 'PhpParser\Node\Expr\BinaryOp\Identical',
        'PHPParser_Node_Expr_LogicalAnd' => 'PhpParser\Node\Expr\BinaryOp\LogicalAnd',
        'PHPParser_Node_Expr_LogicalOr' => 'PhpParser\Node\Expr\BinaryOp\LogicalOr',
        'PHPParser_Node_Expr_LogicalXor' => 'PhpParser\Node\Expr\BinaryOp\LogicalXor',
        'PHPParser_Node_Expr_Minus' => 'PhpParser\Node\Expr\BinaryOp\Minus',
        'PHPParser_Node_Expr_Mod' => 'PhpParser\Node\Expr\BinaryOp\Mod',
        'PHPParser_Node_Expr_Mul' => 'PhpParser\Node\Expr\BinaryOp\Mul',
        'PHPParser_Node_Expr_NotEqual' => 'PhpParser\Node\Expr\BinaryOp\NotEqual',
        'PHPParser_Node_Expr_NotIdentical' => 'PhpParser\Node\Expr\BinaryOp\NotIdentical',
        'PHPParser_Node_Expr_Plus' => 'PhpParser\Node\Expr\BinaryOp\Plus',
        'PHPParser_Node_Expr_ShiftLeft' => 'PhpParser\Node\Expr\BinaryOp\ShiftLeft',
        'PHPParser_Node_Expr_ShiftRight' => 'PhpParser\Node\Expr\BinaryOp\ShiftRight',
        'PHPParser_Node_Expr_Smaller' => 'PhpParser\Node\Expr\BinaryOp\Smaller',
        'PHPParser_Node_Expr_SmallerOrEqual' => 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual',

        'PHPParser_Node_Expr_Array' => 'PhpParser\Node\Expr\Array_',
        'PHPParser_Node_Expr_ArrayDimFetch' => 'PhpParser\Node\Expr\ArrayDimFetch',
        'PHPParser_Node_Expr_ArrayItem' => 'PhpParser\Node\Expr\ArrayItem',
        'PHPParser_Node_Expr_Assign' => 'PhpParser\Node\Expr\Assign',
        'PHPParser_Node_Expr_AssignRef' => 'PhpParser\Node\Expr\AssignRef',
        'PHPParser_Node_Expr_BitwiseNot' => 'PhpParser\Node\Expr\BitwiseNot',
        'PHPParser_Node_Expr_BooleanNot' => 'PhpParser\Node\Expr\BooleanNot',
        'PHPParser_Node_Expr_ClassConstFetch' => 'PhpParser\Node\Expr\ClassConstFetch',
        'PHPParser_Node_Expr_Clone' => 'PhpParser\Node\Expr\Clone_',
        'PHPParser_Node_Expr_Closure' => 'PhpParser\Node\Expr\Closure',
        'PHPParser_Node_Expr_ClosureUse' => 'PhpParser\Node\Expr\ClosureUse',
        'PHPParser_Node_Expr_ConstFetch' => 'PhpParser\Node\Expr\ConstFetch',
        'PHPParser_Node_Expr_Empty' => 'PhpParser\Node\Expr\Empty_',
        'PHPParser_Node_Expr_ErrorSuppress' => 'PhpParser\Node\Expr\ErrorSuppress',
        'PHPParser_Node_Expr_Eval' => 'PhpParser\Node\Expr\Eval_',
        'PHPParser_Node_Expr_Exit' => 'PhpParser\Node\Expr\Exit_',
        'PHPParser_Node_Expr_FuncCall' => 'PhpParser\Node\Expr\FuncCall',
        'PHPParser_Node_Expr_Include' => 'PhpParser\Node\Expr\Include_',
        'PHPParser_Node_Expr_Instanceof' => 'PhpParser\Node\Expr\Instanceof_',
        'PHPParser_Node_Expr_Isset' => 'PhpParser\Node\Expr\Isset_',
        'PHPParser_Node_Expr_List' => 'PhpParser\Node\Expr\List_',
        'PHPParser_Node_Expr_MethodCall' => 'PhpParser\Node\Expr\MethodCall',
        'PHPParser_Node_Expr_New' => 'PhpParser\Node\Expr\New_',
        'PHPParser_Node_Expr_PostDec' => 'PhpParser\Node\Expr\PostDec',
        'PHPParser_Node_Expr_PostInc' => 'PhpParser\Node\Expr\PostInc',
        'PHPParser_Node_Expr_PreDec' => 'PhpParser\Node\Expr\PreDec',
        'PHPParser_Node_Expr_PreInc' => 'PhpParser\Node\Expr\PreInc',
        'PHPParser_Node_Expr_Print' => 'PhpParser\Node\Expr\Print_',
        'PHPParser_Node_Expr_PropertyFetch' => 'PhpParser\Node\Expr\PropertyFetch',
        'PHPParser_Node_Expr_ShellExec' => 'PhpParser\Node\Expr\ShellExec',
        'PHPParser_Node_Expr_StaticCall' => 'PhpParser\Node\Expr\StaticCall',
        'PHPParser_Node_Expr_StaticPropertyFetch' => 'PhpParser\Node\Expr\StaticPropertyFetch',
        'PHPParser_Node_Expr_Ternary' => 'PhpParser\Node\Expr\Ternary',
        'PHPParser_Node_Expr_UnaryMinus' => 'PhpParser\Node\Expr\UnaryMinus',
        'PHPParser_Node_Expr_UnaryPlus' => 'PhpParser\Node\Expr\UnaryPlus',
        'PHPParser_Node_Expr_Variable' => 'PhpParser\Node\Expr\Variable',
        'PHPParser_Node_Expr_Yield' => 'PhpParser\Node\Expr\Yield_',

        'PHPParser_Node_Scalar_ClassConst' => 'PhpParser\Node\Scalar\MagicConst\Class_',
        'PHPParser_Node_Scalar_DirConst' => 'PhpParser\Node\Scalar\MagicConst\Dir',
        'PHPParser_Node_Scalar_FileConst' => 'PhpParser\Node\Scalar\MagicConst\File',
        'PHPParser_Node_Scalar_FuncConst' => 'PhpParser\Node\Scalar\MagicConst\Function_',
        'PHPParser_Node_Scalar_LineConst' => 'PhpParser\Node\Scalar\MagicConst\Line',
        'PHPParser_Node_Scalar_MethodConst' => 'PhpParser\Node\Scalar\MagicConst\Method',
        'PHPParser_Node_Scalar_NSConst' => 'PhpParser\Node\Scalar\MagicConst\Namespace_',
        'PHPParser_Node_Scalar_TraitConst' => 'PhpParser\Node\Scalar\MagicConst\Trait_',

        'PHPParser_Node_Scalar_DNumber' => 'PhpParser\Node\Scalar\DNumber',
        'PHPParser_Node_Scalar_Encapsed' => 'PhpParser\Node\Scalar\Encapsed',
        'PHPParser_Node_Scalar_LNumber' => 'PhpParser\Node\Scalar\LNumber',
        'PHPParser_Node_Scalar_String' => 'PhpParser\Node\Scalar\String_',
    );
}

class_alias('PhpParser\Autoloader', 'PHPParser_Autoloader');
<?php

namespace PhpParser;

interface Builder
{
    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode();
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Class_ extends Declaration
{
    protected $name;

    protected $extends = null;
    protected $implements = array();
    protected $type = 0;

    protected $uses = array();
    protected $constants = array();
    protected $properties = array();
    protected $methods = array();

    /**
     * Creates a class builder.
     *
     * @param string $name Name of the class
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Extends a class.
     *
     * @param Name|string $class Name of class to extend
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function extend($class) {
        $this->extends = $this->normalizeName($class);

        return $this;
    }

    /**
     * Implements one or more interfaces.
     *
     * @param Name|string $interface Name of interface to implement
     * @param Name|string $...       More interfaces to implement
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function implement() {
        foreach (func_get_args() as $interface) {
            $this->implements[] = $this->normalizeName($interface);
        }

        return $this;
    }

    /**
     * Makes the class abstract.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeAbstract() {
        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);

        return $this;
    }

    /**
     * Makes the class final.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeFinal() {
        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        $targets = array(
            'Stmt_TraitUse'    => &$this->uses,
            'Stmt_ClassConst'  => &$this->constants,
            'Stmt_Property'    => &$this->properties,
            'Stmt_ClassMethod' => &$this->methods,
        );

        $type = $stmt->getType();
        if (!isset($targets[$type])) {
            throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
        }

        $targets[$type][] = $stmt;

        return $this;
    }

    /**
     * Returns the built class node.
     *
     * @return Stmt\Class_ The built class node
     */
    public function getNode() {
        return new Stmt\Class_($this->name, array(
            'type' => $this->type,
            'extends' => $this->extends,
            'implements' => $this->implements,
            'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
        ), $this->attributes);
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

abstract class Declaration extends PhpParser\BuilderAbstract
{
    protected $attributes = array();

    abstract public function addStmt($stmt);

    /**
     * Adds multiple statements.
     *
     * @param array $stmts The statements to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmts(array $stmts) {
        foreach ($stmts as $stmt) {
            $this->addStmt($stmt);
        }

        return $this;
    }

    /**
     * Sets doc comment for the declaration.
     *
     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDocComment($docComment) {
        $this->attributes['comments'] = array(
            $this->normalizeDocComment($docComment)
        );

        return $this;
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

abstract class FunctionLike extends Declaration
{
    protected $returnByRef = false;
    protected $params = array();

    /**
     * Make the function return by reference.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeReturnByRef() {
        $this->returnByRef = true;

        return $this;
    }

    /**
     * Adds a parameter.
     *
     * @param Node\Param|Param $param The parameter to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addParam($param) {
        $param = $this->normalizeNode($param);

        if (!$param instanceof Node\Param) {
            throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
        }

        $this->params[] = $param;

        return $this;
    }

    /**
     * Adds multiple parameters.
     *
     * @param array $params The parameters to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addParams(array $params) {
        foreach ($params as $param) {
            $this->addParam($param);
        }

        return $this;
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Function_ extends FunctionLike
{
    protected $name;
    protected $stmts = array();

    /**
     * Creates a function builder.
     *
     * @param string $name Name of the function
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Returns the built function node.
     *
     * @return Stmt\Function_ The built function node
     */
    public function getNode() {
        return new Stmt\Function_($this->name, array(
            'byRef'  => $this->returnByRef,
            'params' => $this->params,
            'stmts'  => $this->stmts,
        ), $this->attributes);
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Interface_ extends Declaration
{
    protected $name;
    protected $extends = array();
    protected $constants = array();
    protected $methods = array();

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Extends one or more interfaces.
     *
     * @param Name|string $interface Name of interface to extend
     * @param Name|string $...       More interfaces to extend
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function extend() {
        foreach (func_get_args() as $interface) {
            $this->extends[] = $this->normalizeName($interface);
        }

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        $type = $stmt->getType();
        switch ($type) {
            case 'Stmt_ClassConst':
                $this->constants[] = $stmt;
                break;

            case 'Stmt_ClassMethod':
                // we erase all statements in the body of an interface method
                $stmt->stmts = null;
                $this->methods[] = $stmt;
                break;

            default:
                throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
        }

        return $this;
    }

    /**
     * Returns the built interface node.
     *
     * @return Stmt\Interface_ The built interface node
     */
    public function getNode() {
        return new Stmt\Interface_($this->name, array(
            'extends' => $this->extends,
            'stmts' => array_merge($this->constants, $this->methods),
        ), $this->attributes);
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Method extends FunctionLike
{
    protected $name;
    protected $type = 0;
    protected $stmts = array();

    /**
     * Creates a method builder.
     *
     * @param string $name Name of the method
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Makes the method public.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePublic() {
        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);

        return $this;
    }

    /**
     * Makes the method protected.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeProtected() {
        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);

        return $this;
    }

    /**
     * Makes the method private.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePrivate() {
        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);

        return $this;
    }

    /**
     * Makes the method static.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeStatic() {
        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);

        return $this;
    }

    /**
     * Makes the method abstract.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeAbstract() {
        if (!empty($this->stmts)) {
            throw new \LogicException('Cannot make method with statements abstract');
        }

        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
        $this->stmts = null; // abstract methods don't have statements

        return $this;
    }

    /**
     * Makes the method final.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeFinal() {
        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        if (null === $this->stmts) {
            throw new \LogicException('Cannot add statements to an abstract method');
        }

        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Returns the built method node.
     *
     * @return Stmt\ClassMethod The built method node
     */
    public function getNode() {
        return new Stmt\ClassMethod($this->name, array(
            'type'   => $this->type,
            'byRef'  => $this->returnByRef,
            'params' => $this->params,
            'stmts'  => $this->stmts,
        ), $this->attributes);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Namespace_ extends PhpParser\BuilderAbstract
{
    private $name;
    private $stmts = array();

    /**
     * Creates a namespace builder.
     *
     * @param Node\Name|string|null $name Name of the namespace
     */
    public function __construct($name) {
        $this->name = null !== $name ? $this->normalizeName($name) : null;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Adds multiple statements.
     *
     * @param array $stmts The statements to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmts(array $stmts) {
        foreach ($stmts as $stmt) {
            $this->addStmt($stmt);
        }

        return $this;
    }

    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode() {
        return new Stmt\Namespace_($this->name, $this->stmts);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;

class Param extends PhpParser\BuilderAbstract
{
    protected $name;

    protected $default = null;
    protected $type = null;
    protected $byRef = false;

    /**
     * Creates a parameter builder.
     *
     * @param string $name Name of the parameter
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Sets default value for the parameter.
     *
     * @param mixed $value Default value to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDefault($value) {
        $this->default = $this->normalizeValue($value);

        return $this;
    }

    /**
     * Sets type hint for the parameter.
     *
     * @param string|Node\Name $type Type hint to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setTypeHint($type) {
        if ($type === 'array' || $type === 'callable') {
            $this->type = $type;
        } else {
            $this->type = $this->normalizeName($type);
        }

        return $this;
    }

    /**
     * Make the parameter accept the value by reference.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeByRef() {
        $this->byRef = true;

        return $this;
    }

    /**
     * Returns the built parameter node.
     *
     * @return Node\Param The built parameter node
     */
    public function getNode() {
        return new Node\Param(
            $this->name, $this->default, $this->type, $this->byRef
        );
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Stmt;

class Property extends PhpParser\BuilderAbstract
{
    protected $name;

    protected $type = 0;
    protected $default = null;
    protected $attributes = array();

    /**
     * Creates a property builder.
     *
     * @param string $name Name of the property
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Makes the property public.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePublic() {
        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);

        return $this;
    }

    /**
     * Makes the property protected.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeProtected() {
        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);

        return $this;
    }

    /**
     * Makes the property private.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePrivate() {
        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);

        return $this;
    }

    /**
     * Makes the property static.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeStatic() {
        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);

        return $this;
    }

    /**
     * Sets default value for the property.
     *
     * @param mixed $value Default value to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDefault($value) {
        $this->default = $this->normalizeValue($value);

        return $this;
    }

    /**
     * Sets doc comment for the property.
     *
     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDocComment($docComment) {
        $this->attributes = array(
            'comments' => array($this->normalizeDocComment($docComment))
        );

        return $this;
    }

    /**
     * Returns the built class node.
     *
     * @return Stmt\Property The built property node
     */
    public function getNode() {
        return new Stmt\Property(
            $this->type !== 0 ? $this->type : Stmt\Class_::MODIFIER_PUBLIC,
            array(
                new Stmt\PropertyProperty($this->name, $this->default)
            ),
            $this->attributes
        );
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Trait_ extends Declaration
{
    protected $name;
    protected $properties = array();
    protected $methods = array();

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        if ($stmt instanceof Stmt\Property) {
            $this->properties[] = $stmt;
        } else if ($stmt instanceof Stmt\ClassMethod) {
            $this->methods[] = $stmt;
        } else {
            throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
        }

        return $this;
    }

    /**
     * Returns the built trait node.
     *
     * @return Stmt\Trait_ The built interface node
     */
    public function getNode() {
        return new Stmt\Trait_(
            $this->name, array_merge($this->properties, $this->methods), $this->attributes
        );
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\BuilderAbstract;
use PhpParser\Node;
use PhpParser\Node\Stmt;

/**
 * @method $this as(string $alias) Sets alias for used name.
 */
class Use_ extends BuilderAbstract {
    protected $name;
    protected $type;
    protected $alias = null;

    /**
     * Creates a name use (alias) builder.
     *
     * @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
     * @param int              $type One of the Stmt\Use_::TYPE_* constants
     */
    public function __construct($name, $type) {
        $this->name = $this->normalizeName($name);
        $this->type = $type;
    }

    /**
     * Sets alias for used name.
     *
     * @param string $alias Alias to use (last component of full name by default)
     *
     * @return $this The builder instance (for fluid interface)
     */
    protected function as_($alias) {
        $this->alias = $alias;
        return $this;
    }
    public function __call($name, $args) {
        if (method_exists($this, $name . '_')) {
            return call_user_func_array(array($this, $name . '_'), $args);
        }

        throw new \LogicException(sprintf('Method "%s" does not exist', $name));
    }

    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode() {
        $alias = null !== $this->alias ? $this->alias : $this->name->getLast();
        return new Stmt\Use_(array(
            new Stmt\UseUse($this->name, $alias)
        ), $this->type);
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar;
use PhpParser\Comment;

abstract class BuilderAbstract implements Builder {
    /**
     * Normalizes a node: Converts builder objects to nodes.
     *
     * @param Node|Builder $node The node to normalize
     *
     * @return Node The normalized node
     */
    protected function normalizeNode($node) {
        if ($node instanceof Builder) {
            return $node->getNode();
        } elseif ($node instanceof Node) {
            return $node;
        }

        throw new \LogicException('Expected node or builder object');
    }

    /**
     * Normalizes a name: Converts plain string names to PhpParser\Node\Name.
     *
     * @param Name|string $name The name to normalize
     *
     * @return Name The normalized name
     */
    protected function normalizeName($name) {
        if ($name instanceof Name) {
            return $name;
        } elseif (is_string($name)) {
            if (!$name) {
                throw new \LogicException('Name cannot be empty');
            }

            if ($name[0] == '\\') {
                return new Name\FullyQualified(substr($name, 1));
            } elseif (0 === strpos($name, 'namespace\\')) {
                return new Name\Relative(substr($name, strlen('namespace\\')));
            } else {
                return new Name($name);
            }
        }

        throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
    }

    /**
     * Normalizes a value: Converts nulls, booleans, integers,
     * floats, strings and arrays into their respective nodes
     *
     * @param mixed $value The value to normalize
     *
     * @return Expr The normalized value
     */
    protected function normalizeValue($value) {
        if ($value instanceof Node) {
            return $value;
        } elseif (is_null($value)) {
            return new Expr\ConstFetch(
                new Name('null')
            );
        } elseif (is_bool($value)) {
            return new Expr\ConstFetch(
                new Name($value ? 'true' : 'false')
            );
        } elseif (is_int($value)) {
            return new Scalar\LNumber($value);
        } elseif (is_float($value)) {
            return new Scalar\DNumber($value);
        } elseif (is_string($value)) {
            return new Scalar\String_($value);
        } elseif (is_array($value)) {
            $items = array();
            $lastKey = -1;
            foreach ($value as $itemKey => $itemValue) {
                // for consecutive, numeric keys don't generate keys
                if (null !== $lastKey && ++$lastKey === $itemKey) {
                    $items[] = new Expr\ArrayItem(
                        $this->normalizeValue($itemValue)
                    );
                } else {
                    $lastKey = null;
                    $items[] = new Expr\ArrayItem(
                        $this->normalizeValue($itemValue),
                        $this->normalizeValue($itemKey)
                    );
                }
            }

            return new Expr\Array_($items);
        } else {
            throw new \LogicException('Invalid value');
        }
    }

    /**
     * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
     *
     * @param Comment\Doc|string $docComment The doc comment to normalize
     *
     * @return Comment\Doc The normalized doc comment
     */
    protected function normalizeDocComment($docComment) {
        if ($docComment instanceof Comment\Doc) {
            return $docComment;
        } else if (is_string($docComment)) {
            return new Comment\Doc($docComment);
        } else {
            throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
        }
    }

    /**
     * Sets a modifier in the $this->type property.
     *
     * @param int $modifier Modifier to set
     */
    protected function setModifier($modifier) {
        Stmt\Class_::verifyModifier($this->type, $modifier);
        $this->type |= $modifier;
    }
}
<?php

namespace PhpParser;

use PhpParser\Builder;
use PhpParser\Node\Stmt\Use_;

/**
 * The following methods use reserved keywords, so their implementation is defined with an underscore and made available
 * with the reserved name through __call() magic.
 *
 * @method Builder\Namespace_ namespace(string $name) Creates a namespace builder.
 * @method Builder\Class_     class(string $name)     Creates a class builder.
 * @method Builder\Interface_ interface(string $name) Creates an interface builder.
 * @method Builder\Trait_     trait(string $name)     Creates a trait builder.
 * @method Builder\Function_  function(string $name)  Creates a function builder.
 * @method Builder\Use_       use(string $name)       Creates a namespace/class use builder.
 */
class BuilderFactory
{
    /**
     * Creates a namespace builder.
     * 
     * @param null|string|Node\Name $name Name of the namespace
     *
     * @return Builder\Namespace_ The created namespace builder
     */
    protected function _namespace($name) {
        return new Builder\Namespace_($name);
    }

    /**
     * Creates a class builder.
     *
     * @param string $name Name of the class
     *
     * @return Builder\Class_ The created class builder
     */
    protected function _class($name) {
        return new Builder\Class_($name);
    }

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     *
     * @return Builder\Interface_ The created interface builder
     */
    protected function _interface($name) {
        return new Builder\Interface_($name);
    }

    /**
     * Creates a trait builder.
     *
     * @param string $name Name of the trait
     *
     * @return Builder\Trait_ The created trait builder
     */
    protected function _trait($name) {
        return new Builder\Trait_($name);
    }

    /**
     * Creates a method builder.
     *
     * @param string $name Name of the method
     *
     * @return Builder\Method The created method builder
     */
    public function method($name) {
        return new Builder\Method($name);
    }

    /**
     * Creates a parameter builder.
     *
     * @param string $name Name of the parameter
     *
     * @return Builder\Param The created parameter builder
     */
    public function param($name) {
        return new Builder\Param($name);
    }

    /**
     * Creates a property builder.
     *
     * @param string $name Name of the property
     *
     * @return Builder\Property The created property builder
     */
    public function property($name) {
        return new Builder\Property($name);
    }

    /**
     * Creates a function builder.
     *
     * @param string $name Name of the function
     *
     * @return Builder\Function_ The created function builder
     */
    protected function _function($name) {
        return new Builder\Function_($name);
    }

    /**
     * Creates a namespace/class use builder.
     *
     * @param string|Node\Name Name to alias
     *
     * @return Builder\Use_ The create use builder
     */
    protected function _use($name) {
        return new Builder\Use_($name, Use_::TYPE_NORMAL);
    }

    public function __call($name, array $args) {
        if (method_exists($this, '_' . $name)) {
            return call_user_func_array(array($this, '_' . $name), $args);
        }

        throw new \LogicException(sprintf('Method "%s" does not exist', $name));
    }
}
<?php

namespace PhpParser;

class Comment
{
    protected $text;
    protected $line;

    /**
     * Constructs a comment node.
     *
     * @param string $text Comment text (including comment delimiters like /*)
     * @param int    $line Line number the comment started on
     */
    public function __construct($text, $line = -1) {
        $this->text = $text;
        $this->line = $line;
    }

    /**
     * Gets the comment text.
     *
     * @return string The comment text (including comment delimiters like /*)
     */
    public function getText() {
        return $this->text;
    }

    /**
     * Sets the comment text.
     *
     * @param string $text The comment text (including comment delimiters like /*)
     */
    public function setText($text) {
        $this->text = $text;
    }

    /**
     * Gets the line number the comment started on.
     *
     * @return int Line number
     */
    public function getLine() {
        return $this->line;
    }

    /**
     * Sets the line number the comment started on.
     *
     * @param int $line Line number
     */
    public function setLine($line) {
        $this->line = $line;
    }

    /**
     * Gets the comment text.
     *
     * @return string The comment text (including comment delimiters like /*)
     */
    public function __toString() {
        return $this->text;
    }

    /**
     * Gets the reformatted comment text.
     *
     * "Reformatted" here means that we try to clean up the whitespace at the
     * starts of the lines. This is necessary because we receive the comments
     * without trailing whitespace on the first line, but with trailing whitespace
     * on all subsequent lines.
     *
     * @return mixed|string
     */
    public function getReformattedText() {
        $text = trim($this->text);
        if (false === strpos($text, "\n")) {
            // Single line comments don't need further processing
            return $text;
        } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
            // Multi line comment of the type
            //
            //     /*
            //      * Some text.
            //      * Some more text.
            //      */
            //
            // is handled by replacing the whitespace sequences before the * by a single space
            return preg_replace('(^\s+\*)m', ' *', $this->text);
        } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
            // Multi line comment of the type
            //
            //    /*
            //        Some text.
            //        Some more text.
            //    */
            //
            // is handled by removing the whitespace sequence on the line before the closing
            // */ on all lines. So if the last line is "    */", then "    " is removed at the
            // start of all lines.
            return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
        } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
            // Multi line comment of the type
            //
            //     /* Some text.
            //        Some more text.
            //        Even more text. */
            //
            // is handled by taking the length of the "/* " segment and leaving only that
            // many space characters before the lines. Thus in the above example only three
            // space characters are left at the start of every line.
            return preg_replace('(^\s*(?= {' . strlen($matches[0]) . '}(?!\s)))m', '', $text);
        }

        // No idea how to format this comment, so simply return as is
        return $text;
    }
}<?php

namespace PhpParser\Comment;

class Doc extends \PhpParser\Comment
{
}<?php

namespace PhpParser;

class Error extends \RuntimeException
{
    protected $rawMessage;
    protected $attributes;

    /**
     * Creates an Exception signifying a parse error.
     *
     * @param string    $message    Error message
     * @param array|int $attributes Attributes of node/token where error occurred
     *                              (or start line of error -- deprecated)
     */
    public function __construct($message, $attributes = array()) {
        $this->rawMessage = (string) $message;
        if (is_array($attributes)) {
            $this->attributes = $attributes;
        } else {
            $this->attributes = array('startLine' => $attributes);
        }
        $this->updateMessage();
    }

    /**
     * Gets the error message
     *
     * @return string Error message
     */
    public function getRawMessage() {
        return $this->rawMessage;
    }

    /**
     * Gets the line the error starts in.
     *
     * @return int Error start line
     */
    public function getStartLine() {
        return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1;
    }

    /**
     * Gets the line the error ends in.
     *
     * @return int Error end line
     */
    public function getEndLine() {
        return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1;
    }


    /**
     * Gets the attributes of the node/token the error occurred at.
     *
     * @return array
     */
    public function getAttributes() {
        return $this->attributes;
    }

    /**
     * Sets the line of the PHP file the error occurred in.
     *
     * @param string $message Error message
     */
    public function setRawMessage($message) {
        $this->rawMessage = (string) $message;
        $this->updateMessage();
    }

    /**
     * Sets the line the error starts in.
     *
     * @param int $line Error start line
     */
    public function setStartLine($line) {
        $this->attributes['startLine'] = (int) $line;
        $this->updateMessage();
    }

    /**
     * Returns whether the error has start and end column information.
     *
     * For column information enable the startFilePos and endFilePos in the lexer options.
     *
     * @return bool
     */
    public function hasColumnInfo() {
        return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']);
    }

    /**
     * Gets the start column (1-based) into the line where the error started.
     *
     * @param string $code Source code of the file
     * @return int
     */
    public function getStartColumn($code) {
        if (!$this->hasColumnInfo()) {
            throw new \RuntimeException('Error does not have column information');
        }

        return $this->toColumn($code, $this->attributes['startFilePos']);
    }

    /**
     * Gets the end column (1-based) into the line where the error ended.
     *
     * @param string $code Source code of the file
     * @return int
     */
    public function getEndColumn($code) {
        if (!$this->hasColumnInfo()) {
            throw new \RuntimeException('Error does not have column information');
        }

        return $this->toColumn($code, $this->attributes['endFilePos']);
    }

    private function toColumn($code, $pos) {
        if ($pos > strlen($code)) {
            throw new \RuntimeException('Invalid position information');
        }

        $lineStartPos = strrpos($code, "\n", $pos - strlen($code));
        if (false === $lineStartPos) {
            $lineStartPos = -1;
        }

        return $pos - $lineStartPos;
    }

    /**
     * Updates the exception message after a change to rawMessage or rawLine.
     */
    protected function updateMessage() {
        $this->message = $this->rawMessage;

        if (-1 === $this->getStartLine()) {
            $this->message .= ' on unknown line';
        } else {
            $this->message .= ' on line ' . $this->getStartLine();
        }
    }

    /** @deprecated Use getStartLine() instead */
    public function getRawLine() {
        return $this->getStartLine();
    }

    /** @deprecated Use setStartLine() instead */
    public function setRawLine($line) {
        $this->setStartLine($line);
    }
}
<?php

namespace PhpParser;

class Lexer
{
    protected $code;
    protected $tokens;
    protected $pos;
    protected $line;
    protected $filePos;

    protected $tokenMap;
    protected $dropTokens;

    protected $usedAttributes;

    /**
     * Creates a Lexer.
     *
     * @param array $options Options array. Currently only the 'usedAttributes' option is supported,
     *                       which is an array of attributes to add to the AST nodes. Possible attributes
     *                       are: 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos',
     *                       'startFilePos', 'endFilePos'. The option defaults to the first three.
     *                       For more info see getNextToken() docs.
     */
    public function __construct(array $options = array()) {
        // map from internal tokens to PhpParser tokens
        $this->tokenMap = $this->createTokenMap();

        // map of tokens to drop while lexing (the map is only used for isset lookup,
        // that's why the value is simply set to 1; the value is never actually used.)
        $this->dropTokens = array_fill_keys(array(T_WHITESPACE, T_OPEN_TAG), 1);

        // the usedAttributes member is a map of the used attribute names to a dummy
        // value (here "true")
        $options += array(
            'usedAttributes' => array('comments', 'startLine', 'endLine'),
        );
        $this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
    }

    /**
     * Initializes the lexer for lexing the provided source code.
     *
     * @param string $code The source code to lex
     *
     * @throws Error on lexing errors (unterminated comment or unexpected character)
     */
    public function startLexing($code) {
        $scream = ini_set('xdebug.scream', '0');

        $this->resetErrors();
        $this->tokens = @token_get_all($code);
        $this->handleErrors();

        if (false !== $scream) {
            ini_set('xdebug.scream', $scream);
        }

        $this->code = $code; // keep the code around for __halt_compiler() handling
        $this->pos  = -1;
        $this->line =  1;
        $this->filePos = 0;
    }

    protected function resetErrors() {
        // set error_get_last() to defined state by forcing an undefined variable error
        set_error_handler(function() { return false; }, 0);
        @$undefinedVariable;
        restore_error_handler();
    }

    protected function handleErrors() {
        $error = error_get_last();

        if (preg_match(
            '~^Unterminated comment starting line ([0-9]+)$~',
            $error['message'], $matches
        )) {
            throw new Error('Unterminated comment', (int) $matches[1]);
        }

        if (preg_match(
            '~^Unexpected character in input:  \'(.)\' \(ASCII=([0-9]+)\)~s',
            $error['message'], $matches
        )) {
            throw new Error(sprintf(
                'Unexpected character "%s" (ASCII %d)',
                $matches[1], $matches[2]
            ));
        }

        // PHP cuts error message after null byte, so need special case
        if (preg_match('~^Unexpected character in input:  \'$~', $error['message'])) {
            throw new Error('Unexpected null byte');
        }
    }

    /**
     * Fetches the next token.
     *
     * The available attributes are determined by the 'usedAttributes' option, which can
     * be specified in the constructor. The following attributes are supported:
     *
     *  * 'comments'      => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
     *                       representing all comments that occurred between the previous
     *                       non-discarded token and the current one.
     *  * 'startLine'     => Line in which the node starts.
     *  * 'endLine'       => Line in which the node ends.
     *  * 'startTokenPos' => Offset into the token array of the first token in the node.
     *  * 'endTokenPos'   => Offset into the token array of the last token in the node.
     *  * 'startFilePos'  => Offset into the code string of the first character that is part of the node.
     *  * 'endFilePos'    => Offset into the code string of the last character that is part of the node
     *
     * @param mixed $value           Variable to store token content in
     * @param mixed $startAttributes Variable to store start attributes in
     * @param mixed $endAttributes   Variable to store end attributes in
     *
     * @return int Token id
     */
    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $startAttributes = array();
        $endAttributes   = array();

        while (1) {
            if (isset($this->tokens[++$this->pos])) {
                $token = $this->tokens[$this->pos];
            } else {
                // EOF token with ID 0
                $token = "\0";
            }

            if (isset($this->usedAttributes['startTokenPos'])) {
                $startAttributes['startTokenPos'] = $this->pos;
            }
            if (isset($this->usedAttributes['startFilePos'])) {
                $startAttributes['startFilePos'] = $this->filePos;
            }

            if (is_string($token)) {
                // bug in token_get_all
                if ('b"' === $token) {
                    $value = 'b"';
                    $this->filePos += 2;
                    $id = ord('"');
                } else {
                    $value = $token;
                    $this->filePos += 1;
                    $id = ord($token);
                }

                if (isset($this->usedAttributes['startLine'])) {
                    $startAttributes['startLine'] = $this->line;
                }
                if (isset($this->usedAttributes['endLine'])) {
                    $endAttributes['endLine'] = $this->line;
                }
                if (isset($this->usedAttributes['endTokenPos'])) {
                    $endAttributes['endTokenPos'] = $this->pos;
                }
                if (isset($this->usedAttributes['endFilePos'])) {
                    $endAttributes['endFilePos'] = $this->filePos - 1;
                }

                return $id;
            } else {
                $this->line += substr_count($token[1], "\n");
                $this->filePos += strlen($token[1]);

                if (T_COMMENT === $token[0]) {
                    if (isset($this->usedAttributes['comments'])) {
                        $startAttributes['comments'][] = new Comment($token[1], $token[2]);
                    }
                } elseif (T_DOC_COMMENT === $token[0]) {
                    if (isset($this->usedAttributes['comments'])) {
                        $startAttributes['comments'][] = new Comment\Doc($token[1], $token[2]);
                    }
                } elseif (!isset($this->dropTokens[$token[0]])) {
                    $value = $token[1];

                    if (isset($this->usedAttributes['startLine'])) {
                        $startAttributes['startLine'] = $token[2];
                    }
                    if (isset($this->usedAttributes['endLine'])) {
                        $endAttributes['endLine'] = $this->line;
                    }
                    if (isset($this->usedAttributes['endTokenPos'])) {
                        $endAttributes['endTokenPos'] = $this->pos;
                    }
                    if (isset($this->usedAttributes['endFilePos'])) {
                        $endAttributes['endFilePos'] = $this->filePos - 1;
                    }

                    return $this->tokenMap[$token[0]];
                }
            }
        }

        throw new \RuntimeException('Reached end of lexer loop');
    }

    /**
     * Returns the token array for current code.
     *
     * The token array is in the same format as provided by the
     * token_get_all() function and does not discard tokens (i.e.
     * whitespace and comments are included). The token position
     * attributes are against this token array.
     *
     * @return array Array of tokens in token_get_all() format
     */
    public function getTokens() {
        return $this->tokens;
    }

    /**
     * Handles __halt_compiler() by returning the text after it.
     *
     * @return string Remaining text
     */
    public function handleHaltCompiler() {
        // text after T_HALT_COMPILER, still including ();
        $textAfter = substr($this->code, $this->filePos);

        // ensure that it is followed by ();
        // this simplifies the situation, by not allowing any comments
        // in between of the tokens.
        if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
            throw new Error('__HALT_COMPILER must be followed by "();"');
        }

        // prevent the lexer from returning any further tokens
        $this->pos = count($this->tokens);

        // return with (); removed
        return (string) substr($textAfter, strlen($matches[0])); // (string) converts false to ''
    }

    /**
     * Creates the token map.
     *
     * The token map maps the PHP internal token identifiers
     * to the identifiers used by the Parser. Additionally it
     * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
     *
     * @return array The token map
     */
    protected function createTokenMap() {
        $tokenMap = array();

        // 256 is the minimum possible token number, as everything below
        // it is an ASCII value
        for ($i = 256; $i < 1000; ++$i) {
            if (T_DOUBLE_COLON === $i) {
                // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
                $tokenMap[$i] = Parser::T_PAAMAYIM_NEKUDOTAYIM;
            } elseif(T_OPEN_TAG_WITH_ECHO === $i) {
                // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
                $tokenMap[$i] = Parser::T_ECHO;
            } elseif(T_CLOSE_TAG === $i) {
                // T_CLOSE_TAG is equivalent to ';'
                $tokenMap[$i] = ord(';');
            } elseif ('UNKNOWN' !== $name = token_name($i)) {
                if ('T_HASHBANG' === $name) {
                    // HHVM uses a special token for #! hashbang lines
                    $tokenMap[$i] = Parser::T_INLINE_HTML;
                } else if (defined($name = 'PhpParser\Parser::' . $name)) {
                    // Other tokens can be mapped directly
                    $tokenMap[$i] = constant($name);
                }
            }
        }

        // HHVM uses a special token for numbers that overflow to double
        if (defined('T_ONUMBER')) {
            $tokenMap[T_ONUMBER] = Parser::T_DNUMBER;
        }
        // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
        if (defined('T_COMPILER_HALT_OFFSET')) {
            $tokenMap[T_COMPILER_HALT_OFFSET] = Parser::T_STRING;
        }

        return $tokenMap;
    }
}
<?php

namespace PhpParser\Lexer;

use PhpParser\Parser;

/**
 * ATTENTION: This code is WRITE-ONLY. Do not try to read it.
 */
class Emulative extends \PhpParser\Lexer
{
    protected $newKeywords;
    protected $inObjectAccess;

    const T_ELLIPSIS   = 1001;
    const T_POW        = 1002;
    const T_POW_EQUAL  = 1003;
    const T_COALESCE   = 1004;
    const T_SPACESHIP  = 1005;
    const T_YIELD_FROM = 1006;

    const PHP_7_0 = '7.0.0dev';
    const PHP_5_6 = '5.6.0rc1';
    const PHP_5_5 = '5.5.0beta1';
    const PHP_5_4 = '5.4.0beta1';

    public function __construct(array $options = array()) {
        parent::__construct($options);

        $newKeywordsPerVersion = array(
            self::PHP_5_5 => array(
                'finally'       => Parser::T_FINALLY,
                'yield'         => Parser::T_YIELD,
            ),
            self::PHP_5_4 => array(
                'callable'      => Parser::T_CALLABLE,
                'insteadof'     => Parser::T_INSTEADOF,
                'trait'         => Parser::T_TRAIT,
                '__trait__'     => Parser::T_TRAIT_C,
            ),
        );

        $this->newKeywords = array();
        foreach ($newKeywordsPerVersion as $version => $newKeywords) {
            if (version_compare(PHP_VERSION, $version, '>=')) {
                break;
            }

            $this->newKeywords += $newKeywords;
        }

        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
            return;
        }
        $this->tokenMap[self::T_COALESCE] = Parser::T_COALESCE;
        $this->tokenMap[self::T_SPACESHIP] = Parser::T_SPACESHIP;
        $this->tokenMap[self::T_YIELD_FROM] = Parser::T_YIELD_FROM;

        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
            return;
        }
        $this->tokenMap[self::T_ELLIPSIS]  = Parser::T_ELLIPSIS;
        $this->tokenMap[self::T_POW]       = Parser::T_POW;
        $this->tokenMap[self::T_POW_EQUAL] = Parser::T_POW_EQUAL;
    }

    public function startLexing($code) {
        $this->inObjectAccess = false;

        $preprocessedCode = $this->preprocessCode($code);
        parent::startLexing($preprocessedCode);
        if ($preprocessedCode !== $code) {
            $this->postprocessTokens();
        }

        // Set code property back to the original code, so __halt_compiler()
        // handling and (start|end)FilePos attributes use the correct offsets
        $this->code = $code;
    }

    /*
     * Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
     * ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
     * use it here.
     * Later when preprocessing the tokens these sequences will either be replaced
     * by real tokens or replaced with their original content (e.g. if they occurred
     * inside a string, i.e. a place where they don't have a special meaning).
     */
    protected function preprocessCode($code) {
        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
            return $code;
        }

        $code = str_replace('??', '~__EMU__COALESCE__~', $code);
        $code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
        $code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
            // Encoding $0 in order to preserve exact whitespace
            return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
        }, $code);

        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
            return $code;
        }

        $code = str_replace('...', '~__EMU__ELLIPSIS__~', $code);
        $code = preg_replace('((?<!/)\*\*=)', '~__EMU__POWEQUAL__~', $code);
        $code = preg_replace('((?<!/)\*\*(?!/))', '~__EMU__POW__~', $code);

        if (version_compare(PHP_VERSION, self::PHP_5_4, '>=')) {
            return $code;
        }

        // binary notation (0b010101101001...)
        return preg_replace('(\b0b[01]+\b)', '~__EMU__BINARY__$0__~', $code);
    }

    /*
     * Replaces the ~__EMU__...~ sequences with real tokens or their original
     * value.
     */
    protected function postprocessTokens() {
        // we need to manually iterate and manage a count because we'll change
        // the tokens array on the way
        for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
            // first check that the following tokens are of form ~LABEL~,
            // then match the __EMU__... sequence.
            if ('~' === $this->tokens[$i]
                && isset($this->tokens[$i + 2])
                && '~' === $this->tokens[$i + 2]
                && T_STRING === $this->tokens[$i + 1][0]
                && preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
            ) {
                if ('BINARY' === $matches[1]) {
                    // the binary number can either be an integer or a double, so return a LNUMBER
                    // or DNUMBER respectively
                    $isInt = is_int(bindec($matches[2]));
                    $replace = array(
                        array($isInt ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
                    );
                } else if ('ELLIPSIS' === $matches[1]) {
                    $replace = array(
                        array(self::T_ELLIPSIS, '...', $this->tokens[$i + 1][2])
                    );
                } else if ('POW' === $matches[1]) {
                    $replace = array(
                        array(self::T_POW, '**', $this->tokens[$i + 1][2])
                    );
                } else if ('POWEQUAL' === $matches[1]) {
                    $replace = array(
                        array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
                    );
                } else if ('COALESCE' === $matches[1]) {
                    $replace = array(
                        array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
                    );
                } else if ('SPACESHIP' === $matches[1]) {
                    $replace = array(
                        array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
                    );
                } else if ('YIELDFROM' === $matches[1]) {
                    $content = $this->hex2bin($matches[2]);
                    $replace = array(
                        array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
                    );
                } else {
                    throw new \RuntimeException('Invalid __EMU__ sequence');
                }

                array_splice($this->tokens, $i, 3, $replace);
                $c -= 3 - count($replace);
            // for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
            // in their content with the original character sequence
            } elseif (is_array($this->tokens[$i])
                      && 0 !== strpos($this->tokens[$i][1], '__EMU__')
            ) {
                $this->tokens[$i][1] = preg_replace_callback(
                    '(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
                    array($this, 'restoreContentCallback'),
                    $this->tokens[$i][1]
                );
            }
        }
    }

    /*
     * This method is a callback for restoring EMU sequences in
     * multichar tokens (like strings) to their original value.
     */
    public function restoreContentCallback(array $matches) {
        if ('BINARY' === $matches[1]) {
            return $matches[2];
        } else if ('ELLIPSIS' === $matches[1]) {
            return '...';
        } else if ('POW' === $matches[1]) {
            return '**';
        } else if ('POWEQUAL' === $matches[1]) {
            return '**=';
        } else if ('COALESCE' === $matches[1]) {
            return '??';
        } else if ('SPACESHIP' === $matches[1]) {
            return '<=>';
        } else if ('YIELDFROM' === $matches[1]) {
            return $this->hex2bin($matches[2]);
        } else {
            return $matches[0];
        }
    }

    private function hex2bin($str) {
        // TODO Drop when removing support for PHP 5.3
        return pack('H*', $str);
    }

    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $token = parent::getNextToken($value, $startAttributes, $endAttributes);

        // replace new keywords by their respective tokens. This is not done
        // if we currently are in an object access (e.g. in $obj->namespace
        // "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
        if (Parser::T_STRING === $token && !$this->inObjectAccess) {
            if (isset($this->newKeywords[strtolower($value)])) {
                return $this->newKeywords[strtolower($value)];
            }
        } else {
            // keep track of whether we currently are in an object access (after ->)
            $this->inObjectAccess = Parser::T_OBJECT_OPERATOR === $token;
        }

        return $token;
    }
}
<?php

namespace PhpParser;

interface Node
{
    /**
     * Gets the type of the node.
     *
     * @return string Type of the node
     */
    public function getType();

    /**
     * Gets the names of the sub nodes.
     *
     * @return array Names of sub nodes
     */
    public function getSubNodeNames();

    /**
     * Gets line the node started in.
     *
     * @return int Line
     */
    public function getLine();

    /**
     * Sets line the node started in.
     *
     * @param int $line Line
     */
    public function setLine($line);

    /**
     * Gets the doc comment of the node.
     *
     * The doc comment has to be the last comment associated with the node.
     *
     * @return null|Comment\Doc Doc comment object or null
     */
    public function getDocComment();

    /**
     * Sets an attribute on a node.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function setAttribute($key, $value);

    /**
     * Returns whether an attribute exists.
     *
     * @param string $key
     *
     * @return bool
     */
    public function hasAttribute($key);

    /**
     * Returns the value of an attribute.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public function &getAttribute($key, $default = null);

    /**
     * Returns all attributes for the given node.
     *
     * @return array
     */
    public function getAttributes();
}<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Arg extends NodeAbstract
{
    /** @var Expr Value to pass */
    public $value;
    /** @var bool Whether to pass by ref */
    public $byRef;
    /** @var bool Whether to unpack the argument */
    public $unpack;

    /**
     * Constructs a function call argument node.
     *
     * @param Expr  $value      Value to pass
     * @param bool  $byRef      Whether to pass by ref
     * @param bool  $unpack     Whether to unpack the argument
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $value, $byRef = false, $unpack = false, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->value = $value;
        $this->byRef = $byRef;
        $this->unpack = $unpack;
    }

    public function getSubNodeNames() {
        return array('value', 'byRef', 'unpack');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Const_ extends NodeAbstract
{
    /** @var string Name */
    public $name;
    /** @var Expr Value */
    public $value;

    /**
     * Constructs a const node for use in class const and const statements.
     *
     * @param string  $name       Name
     * @param Expr    $value      Value
     * @param array   $attributes Additional attributes
     */
    public function __construct($name, Expr $value, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('name', 'value');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

abstract class Expr extends NodeAbstract
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ArrayDimFetch extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var null|Expr Array index / dim */
    public $dim;

    /**
     * Constructs an array index fetch node.
     *
     * @param Expr      $var        Variable
     * @param null|Expr $dim        Array index / dim
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $dim = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->dim = $dim;
    }

    public function getSubnodeNames() {
        return array('var', 'dim');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ArrayItem extends Expr
{
    /** @var null|Expr Key */
    public $key;
    /** @var Expr Value */
    public $value;
    /** @var bool Whether to assign by reference */
    public $byRef;

    /**
     * Constructs an array item node.
     *
     * @param Expr      $value      Value
     * @param null|Expr $key        Key
     * @param bool      $byRef      Whether to assign by reference
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $value, Expr $key = null, $byRef = false, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->key = $key;
        $this->value = $value;
        $this->byRef = $byRef;
    }

    public function getSubNodeNames() {
        return array('key', 'value', 'byRef');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Array_ extends Expr
{
    /** @var ArrayItem[] Items */
    public $items;

    /**
     * Constructs an array node.
     *
     * @param ArrayItem[] $items      Items of the array
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $items = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->items = $items;
    }

    public function getSubNodeNames() {
        return array('items');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Assign extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an assignment node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

/**
 * @property Expr $var  Variable
 * @property Expr $expr Expression
 */
abstract class AssignOp extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a compound assignment operation node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseAnd extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseOr extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseXor extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Concat extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Div extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Minus extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Mod extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Mul extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Plus extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Pow extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class ShiftLeft extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class ShiftRight extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

/**
 * @property Expr $var  Variable reference is assigned to
 * @property Expr $expr Variable which is referenced
 */
class AssignRef extends Expr
{
    /** @var Expr Variable reference is assigned to */
    public $var;
    /** @var Expr Variable which is referenced */
    public $expr;

    /**
     * Constructs an assignment node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

abstract class BinaryOp extends Expr
{
    /** @var Expr The left hand side expression */
    public $left;
    /** @var Expr The right hand side expression */
    public $right;

    /**
     * Constructs a bitwise and node.
     *
     * @param Expr  $left       The left hand side expression
     * @param Expr  $right      The right hand side expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $left, Expr $right, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->left = $left;
        $this->right = $right;
    }

    public function getSubNodeNames() {
        return array('left', 'right');
    }
}
<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseXor extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BooleanAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BooleanOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Coalesce extends BinaryOp
{
}
<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Concat extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Div extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Equal extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Greater extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class GreaterOrEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Identical extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalXor extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Minus extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Mod extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Mul extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class NotEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class NotIdentical extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Plus extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Pow extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class ShiftLeft extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class ShiftRight extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Smaller extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class SmallerOrEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Spaceship extends BinaryOp
{
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class BitwiseNot extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a bitwise not node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class BooleanNot extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a boolean not node.
     *
     * @param Expr $expr       Expression
     * @param array               $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

abstract class Cast extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a cast node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Array_ extends Cast
{
}<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Bool_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Double extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Int_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Object_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class String_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Unset_ extends Cast
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class ClassConstFetch extends Expr
{
    /** @var Name|Expr Class name */
    public $class;
    /** @var string Constant name */
    public $name;

    /**
     * Constructs a class const fetch node.
     *
     * @param Name|Expr $class      Class name
     * @param string    $name       Constant name
     * @param array     $attributes Additional attributes
     */
    public function __construct($class, $name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->class = $class;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('class', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Clone_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a clone node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;

class Closure extends Expr implements FunctionLike
{
    /** @var bool Whether the closure is static */
    public $static;
    /** @var bool Whether to return by reference */
    public $byRef;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var ClosureUse[] use()s */
    public $uses;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a lambda function node.
     *
     * @param array $subNodes   Array of the following optional subnodes:
     *                          'static'     => false  : Whether the closure is static
     *                          'byRef'      => false  : Whether to return by reference
     *                          'params'     => array(): Parameters
     *                          'uses'       => array(): use()s
     *                          'returnType' => null   : Return type
     *                          'stmts'      => array(): Statements
     * @param array $attributes Additional attributes
     */
    public function __construct(array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->static = isset($subNodes['static']) ? $subNodes['static'] : false;
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->uses = isset($subNodes['uses']) ? $subNodes['uses'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('static', 'byRef', 'params', 'uses', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ClosureUse extends Expr
{
    /** @var string Name of variable */
    public $var;
    /** @var bool Whether to use by reference */
    public $byRef;

    /**
     * Constructs a closure use node.
     *
     * @param string      $var        Name of variable
     * @param bool        $byRef      Whether to use by reference
     * @param array       $attributes Additional attributes
     */
    public function __construct($var, $byRef = false, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->byRef = $byRef;
    }

    public function getSubNodeNames() {
        return array('var', 'byRef');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class ConstFetch extends Expr
{
    /** @var Name Constant name */
    public $name;

    /**
     * Constructs a const fetch node.
     *
     * @param Name  $name       Constant name
     * @param array $attributes Additional attributes
     */
    public function __construct(Name $name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Empty_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an empty() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ErrorSuppress extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an error suppress node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Eval_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an eval() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Exit_ extends Expr
{
    /** @var null|Expr Expression */
    public $expr;

    /**
     * Constructs an exit() node.
     *
     * @param null|Expr $expr       Expression
     * @param array                    $attributes Additional attributes
     */
    public function __construct(Expr $expr = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class FuncCall extends Expr
{
    /** @var Node\Name|Expr Function name */
    public $name;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Node\Name|Expr $name       Function name
     * @param Node\Arg[]                    $args       Arguments
     * @param array                                   $attributes Additional attributes
     */
    public function __construct($name, array $args = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Include_ extends Expr
{
    const TYPE_INCLUDE      = 1;
    const TYPE_INCLUDE_ONCE = 2;
    const TYPE_REQUIRE      = 3;
    const TYPE_REQUIRE_ONCE = 4;

    /** @var Expr Expression */
    public $expr;
    /** @var int Type of include */
    public $type;

    /**
     * Constructs an include node.
     *
     * @param Expr  $expr       Expression
     * @param int   $type       Type of include
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, $type, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
        $this->type = $type;
    }

    public function getSubNodeNames() {
        return array('expr', 'type');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class Instanceof_ extends Expr
{
    /** @var Expr Expression */
    public $expr;
    /** @var Name|Expr Class name */
    public $class;

    /**
     * Constructs an instanceof check node.
     *
     * @param Expr      $expr       Expression
     * @param Name|Expr $class      Class name
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $expr, $class, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
        $this->class = $class;
    }

    public function getSubNodeNames() {
        return array('expr', 'class');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Isset_ extends Expr
{
    /** @var Expr[] Variables */
    public $vars;

    /**
     * Constructs an array node.
     *
     * @param Expr[] $vars       Variables
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class List_ extends Expr
{
    /** @var Expr[] List of variables to assign to */
    public $vars;

    /**
     * Constructs a list() destructuring node.
     *
     * @param Expr[] $vars       List of variables to assign to
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr;

class MethodCall extends Expr
{
    /** @var Expr Variable holding object */
    public $var;
    /** @var string|Expr Method name */
    public $name;
    /** @var Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Expr        $var        Variable holding object
     * @param string|Expr $name       Method name
     * @param Arg[]       $args       Arguments
     * @param array       $attributes Additional attributes
     */
    public function __construct(Expr $var, $name, array $args = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('var', 'name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class New_ extends Expr
{
    /** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
    public $class;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Node\Name|Expr|Node\Stmt\Class_ $class      Class name (or class node for anonymous classes)
     * @param Node\Arg[]                      $args       Arguments
     * @param array                           $attributes Additional attributes
     */
    public function __construct($class, array $args = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->class = $class;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('class', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PostDec extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a post decrement node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PostInc extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a post increment node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PreDec extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a pre decrement node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PreInc extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a pre increment node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Print_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an print() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PropertyFetch extends Expr
{
    /** @var Expr Variable holding object */
    public $var;
    /** @var string|Expr Property name */
    public $name;

    /**
     * Constructs a function call node.
     *
     * @param Expr        $var        Variable holding object
     * @param string|Expr $name       Property name
     * @param array       $attributes Additional attributes
     */
    public function __construct(Expr $var, $name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->var = $var;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('var', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ShellExec extends Expr
{
    /** @var array Encapsed string array */
    public $parts;

    /**
     * Constructs a shell exec (backtick) node.
     *
     * @param array $parts      Encapsed string array
     * @param array $attributes Additional attributes
     */
    public function __construct(array $parts, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class StaticCall extends Expr
{
    /** @var Node\Name|Expr Class name */
    public $class;
    /** @var string|Expr Method name */
    public $name;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a static method call node.
     *
     * @param Node\Name|Expr $class      Class name
     * @param string|Expr    $name       Method name
     * @param Node\Arg[]     $args       Arguments
     * @param array          $attributes Additional attributes
     */
    public function __construct($class, $name, array $args = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->class = $class;
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('class', 'name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class StaticPropertyFetch extends Expr
{
    /** @var Name|Expr Class name */
    public $class;
    /** @var string|Expr Property name */
    public $name;

    /**
     * Constructs a static property fetch node.
     *
     * @param Name|Expr   $class      Class name
     * @param string|Expr $name       Property name
     * @param array       $attributes Additional attributes
     */
    public function __construct($class, $name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->class = $class;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('class', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Ternary extends Expr
{
    /** @var Expr Condition */
    public $cond;
    /** @var null|Expr Expression for true */
    public $if;
    /** @var Expr Expression for false */
    public $else;

    /**
     * Constructs a ternary operator node.
     *
     * @param Expr      $cond       Condition
     * @param null|Expr $if         Expression for true
     * @param Expr      $else       Expression for false
     * @param array                    $attributes Additional attributes
     */
    public function __construct(Expr $cond, $if, Expr $else, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->if = $if;
        $this->else = $else;
    }

    public function getSubNodeNames() {
        return array('cond', 'if', 'else');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class UnaryMinus extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a unary minus node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class UnaryPlus extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a unary plus node.
     *
     * @param Expr $expr       Expression
     * @param array               $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Variable extends Expr
{
    /** @var string|Expr Name */
    public $name;

    /**
     * Constructs a variable node.
     *
     * @param string|Expr $name       Name
     * @param array                      $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class YieldFrom extends Expr
{
    /** @var Expr Expression to yield from */
    public $expr;

    /**
     * Constructs an "yield from" node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Yield_ extends Expr
{
    /** @var null|Expr Key expression */
    public $key;
    /** @var null|Expr Value expression */
    public $value;

    /**
     * Constructs a yield expression node.
     *
     * @param null|Expr $value      Value expression
     * @param null|Expr $key        Key expression
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $value = null, Expr $key = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->key = $key;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('key', 'value');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\Node;

interface FunctionLike extends Node
{
    /**
     * Whether to return by reference
     *
     * @return bool
     */
    public function returnsByRef();

    /**
     * List of parameters
     *
     * @return Node\Param[]
     */
    public function getParams();

    /**
     * Get the declared return type or null
     * 
     * @return null|string|Node\Name
     */
    public function getReturnType();

    /**
     * The function body
     *
     * @return Node\Stmt[]
     */
    public function getStmts();
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Name extends NodeAbstract
{
    /** @var string[] Parts of the name */
    public $parts;

    /**
     * Constructs a name node.
     *
     * @param string|array $parts      Parts of the name (or name as string)
     * @param array        $attributes Additional attributes
     */
    public function __construct($parts, array $attributes = array()) {
        if (!is_array($parts)) {
            $parts = explode('\\', $parts);
        }

        parent::__construct(null, $attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }

    /**
     * Gets the first part of the name, i.e. everything before the first namespace separator.
     *
     * @return string First part of the name
     */
    public function getFirst() {
        return $this->parts[0];
    }

    /**
     * Gets the last part of the name, i.e. everything after the last namespace separator.
     *
     * @return string Last part of the name
     */
    public function getLast() {
        return $this->parts[count($this->parts) - 1];
    }

    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return 1 == count($this->parts);
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return 1 < count($this->parts);
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return false;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return false;
    }

    /**
     * Returns a string representation of the name by imploding the namespace parts with a separator.
     *
     * @param string $separator The separator to use (defaults to the namespace separator \)
     *
     * @return string String representation
     */
    public function toString($separator = '\\') {
        return implode($separator, $this->parts);
    }

    /**
     * Returns a string representation of the name by imploding the namespace parts with the
     * namespace separator.
     *
     * @return string String representation
     */
    public function __toString() {
        return implode('\\', $this->parts);
    }

    /**
     * Sets the whole name.
     *
     * @param string|array|self $name The name to set the whole name to
     */
    public function set($name) {
        $this->parts = $this->prepareName($name);
    }

    /**
     * Prepends a name to this name.
     *
     * @param string|array|self $name Name to prepend
     */
    public function prepend($name) {
        $this->parts = array_merge($this->prepareName($name), $this->parts);
    }

    /**
     * Appends a name to this name.
     *
     * @param string|array|self $name Name to append
     */
    public function append($name) {
        $this->parts = array_merge($this->parts, $this->prepareName($name));
    }

    /**
     * Sets the first part of the name.
     *
     * @param string|array|self $name The name to set the first part to
     */
    public function setFirst($name) {
        array_splice($this->parts, 0, 1, $this->prepareName($name));
    }

    /**
     * Sets the last part of the name.
     *
     * @param string|array|self $name The name to set the last part to
     */
    public function setLast($name) {
        array_splice($this->parts, -1, 1, $this->prepareName($name));
    }

    /**
     * Prepares a (string, array or Name node) name for use in name changing methods by converting
     * it to an array.
     *
     * @param string|array|self $name Name to prepare
     *
     * @return array Prepared name
     */
    protected function prepareName($name) {
        if (is_string($name)) {
            return explode('\\', $name);
        } elseif (is_array($name)) {
            return $name;
        } elseif ($name instanceof self) {
            return $name->parts;
        }

        throw new \InvalidArgumentException(
            'When changing a name you need to pass either a string, an array or a Name node'
        );
    }
}
<?php

namespace PhpParser\Node\Name;

class FullyQualified extends \PhpParser\Node\Name
{
    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return false;
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return false;
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return true;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return false;
    }
}<?php

namespace PhpParser\Node\Name;

class Relative extends \PhpParser\Node\Name
{
    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return false;
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return false;
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return false;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return true;
    }
}<?php

namespace PhpParser\Node;

use PhpParser\Error;
use PhpParser\NodeAbstract;

class Param extends NodeAbstract
{
    /** @var null|string|Name Typehint */
    public $type;
    /** @var bool Whether parameter is passed by reference */
    public $byRef;
    /** @var bool Whether this is a variadic argument */
    public $variadic;
    /** @var string Name */
    public $name;
    /** @var null|Expr Default value */
    public $default;

    /**
     * Constructs a parameter node.
     *
     * @param string           $name       Name
     * @param null|Expr        $default    Default value
     * @param null|string|Name $type       Typehint
     * @param bool             $byRef      Whether is passed by reference
     * @param bool             $variadic   Whether this is a variadic argument
     * @param array            $attributes Additional attributes
     */
    public function __construct($name, Expr $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->type = $type;
        $this->byRef = $byRef;
        $this->variadic = $variadic;
        $this->name = $name;
        $this->default = $default;

        if ($variadic && null !== $default) {
            throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
        }
    }

    public function getSubNodeNames() {
        return array('type', 'byRef', 'variadic', 'name', 'default');
    }
}
<?php

namespace PhpParser\Node;

abstract class Scalar extends Expr
{
}<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class DNumber extends Scalar
{
    /** @var float Number value */
    public $value;

    /**
     * Constructs a float number scalar node.
     *
     * @param float $value      Value of the number
     * @param array $attributes Additional attributes
     */
    public function __construct($value = 0.0, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * @internal
     *
     * Parses a DNUMBER token like PHP would.
     *
     * @param string $str A string number
     *
     * @return float The parsed number
     */
    public static function parse($str) {
        // if string contains any of .eE just cast it to float
        if (false !== strpbrk($str, '.eE')) {
            return (float) $str;
        }

        // otherwise it's an integer notation that overflowed into a float
        // if it starts with 0 it's one of the special integer notations
        if ('0' === $str[0]) {
            // hex
            if ('x' === $str[1] || 'X' === $str[1]) {
                return hexdec($str);
            }

            // bin
            if ('b' === $str[1] || 'B' === $str[1]) {
                return bindec($str);
            }

            // oct
            // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
            // so that only the digits before that are used
            return octdec(substr($str, 0, strcspn($str, '89')));
        }

        // dec
        return (float) $str;
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class Encapsed extends Scalar
{
    /** @var array Encaps list */
    public $parts;

    /**
     * Constructs an encapsed string node.
     *
     * @param array $parts      Encaps list
     * @param array $attributes Additional attributes
     */
    public function __construct(array $parts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class LNumber extends Scalar
{
    /** @var int Number value */
    public $value;

    /**
     * Constructs an integer number scalar node.
     *
     * @param int   $value      Value of the number
     * @param array $attributes Additional attributes
     */
    public function __construct($value = 0, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * @internal
     *
     * Parses an LNUMBER token (dec, hex, oct and bin notations) like PHP would.
     *
     * @param string $str A string number
     *
     * @return int The parsed number
     */
    public static function parse($str) {
        // handle plain 0 specially
        if ('0' === $str) {
            return 0;
        }

        // if first char is 0 (and number isn't 0) it's a special syntax
        if ('0' === $str[0]) {
            // hex
            if ('x' === $str[1] || 'X' === $str[1]) {
                return hexdec($str);
            }

            // bin
            if ('b' === $str[1] || 'B' === $str[1]) {
                return bindec($str);
            }

            // oct (intval instead of octdec to get proper cutting behavior with malformed numbers)
            return intval($str, 8);
        }

        // dec
        return (int) $str;
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

abstract class MagicConst extends Scalar
{
    /**
     * Constructs a magic constant node.
     *
     * @param array $attributes Additional attributes
     */
    public function __construct(array $attributes = array()) {
        parent::__construct(null, $attributes);
    }

    public function getSubNodeNames() {
        return array();
    }

    /**
     * Get name of magic constant.
     *
     * @return string Name of magic constant
     */
    abstract public function getName();
}
<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Class_ extends MagicConst
{
    public function getName() {
        return '__CLASS__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Dir extends MagicConst
{
    public function getName() {
        return '__DIR__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class File extends MagicConst
{
    public function getName() {
        return '__FILE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Function_ extends MagicConst
{
    public function getName() {
        return '__FUNCTION__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Line extends MagicConst
{
    public function getName() {
        return '__LINE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Method extends MagicConst
{
    public function getName() {
        return '__METHOD__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Namespace_ extends MagicConst
{
    public function getName() {
        return '__NAMESPACE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Trait_ extends MagicConst
{
    public function getName() {
        return '__TRAIT__';
    }
}<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class String_ extends Scalar
{
    /** @var string String value */
    public $value;

    protected static $replacements = array(
        '\\' => '\\',
        '$'  =>  '$',
        'n'  => "\n",
        'r'  => "\r",
        't'  => "\t",
        'f'  => "\f",
        'v'  => "\v",
        'e'  => "\x1B",
    );

    /**
     * Constructs a string scalar node.
     *
     * @param string $value      Value of the string
     * @param array  $attributes Additional attributes
     */
    public function __construct($value = '', array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * @internal
     *
     * Parses a string token.
     *
     * @param string $str String token content
     *
     * @return string The parsed string
     */
    public static function parse($str) {
        $bLength = 0;
        if ('b' === $str[0]) {
            $bLength = 1;
        }

        if ('\'' === $str[$bLength]) {
            return str_replace(
                array('\\\\', '\\\''),
                array(  '\\',   '\''),
                substr($str, $bLength + 1, -1)
            );
        } else {
            return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
        }
    }

    /**
     * @internal
     *
     * Parses escape sequences in strings (all string types apart from single quoted).
     *
     * @param string      $str   String without quotes
     * @param null|string $quote Quote type
     *
     * @return string String with escape sequences parsed
     */
    public static function parseEscapeSequences($str, $quote) {
        if (null !== $quote) {
            $str = str_replace('\\' . $quote, $quote, $str);
        }

        return preg_replace_callback(
            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
            array(__CLASS__, 'parseCallback'),
            $str
        );
    }

    private static function parseCallback($matches) {
        $str = $matches[1];

        if (isset(self::$replacements[$str])) {
            return self::$replacements[$str];
        } elseif ('x' === $str[0] || 'X' === $str[0]) {
            return chr(hexdec($str));
        } else {
            return chr(octdec($str));
        }
    }

    /**
     * @internal
     *
     * Parses a constant doc string.
     *
     * @param string $startToken Doc string start token content (<<<SMTHG)
     * @param string $str        String token content
     *
     * @return string Parsed string
     */
    public static function parseDocString($startToken, $str) {
        // strip last newline (thanks tokenizer for sticking it into the string!)
        $str = preg_replace('~(\r\n|\n|\r)$~', '', $str);

        // nowdoc string
        if (false !== strpos($startToken, '\'')) {
            return $str;
        }

        return self::parseEscapeSequences($str, null);
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

abstract class Stmt extends NodeAbstract
{
}<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Break_ extends Node\Stmt
{
    /** @var null|Node\Expr Number of loops to break */
    public $num;

    /**
     * Constructs a break node.
     *
     * @param null|Node\Expr $num        Number of loops to break
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $num = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->num = $num;
    }

    public function getSubNodeNames() {
        return array('num');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Case_ extends Node\Stmt
{
    /** @var null|Node\Expr $cond Condition (null for default) */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a case node.
     *
     * @param null|Node\Expr $cond       Condition (null for default)
     * @param Node[]         $stmts      Statements
     * @param array          $attributes Additional attributes
     */
    public function __construct($cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Catch_ extends Node\Stmt
{
    /** @var Node\Name Class of exception */
    public $type;
    /** @var string Variable for exception */
    public $var;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a catch node.
     *
     * @param Node\Name $type       Class of exception
     * @param string    $var        Variable for exception
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Name $type, $var, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->type = $type;
        $this->var = $var;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('type', 'var', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class ClassConst extends Node\Stmt
{
    /** @var Node\Const_[] Constant declarations */
    public $consts;

    /**
     * Constructs a class const list node.
     *
     * @param Node\Const_[] $consts     Constant declarations
     * @param array         $attributes Additional attributes
     */
    public function __construct(array $consts, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->consts = $consts;
    }

    public function getSubNodeNames() {
        return array('consts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

abstract class ClassLike extends Node\Stmt {
    /** @var string Name */
    public $name;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Gets all methods defined directly in this class/interface/trait
     *
     * @return ClassMethod[]
     */
    public function getMethods() {
        $methods = array();
        foreach ($this->stmts as $stmt) {
            if ($stmt instanceof ClassMethod) {
                $methods[] = $stmt;
            }
        }
        return $methods;
    }

    /**
     * Gets method with the given name defined directly in this class/interface/trait.
     *
     * @param string $name Name of the method (compared case-insensitively)
     *
     * @return ClassMethod|null Method node or null if the method does not exist
     */
    public function getMethod($name) {
        $lowerName = strtolower($name);
        foreach ($this->stmts as $stmt) {
            if ($stmt instanceof ClassMethod && $lowerName === strtolower($stmt->name)) {
                return $stmt;
            }
        }
        return null;
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Error;

class ClassMethod extends Node\Stmt implements FunctionLike
{
    /** @var int Type */
    public $type;
    /** @var bool Whether to return by reference */
    public $byRef;
    /** @var string Name */
    public $name;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a class method node.
     *
     * @param string      $name       Name
     * @param array       $subNodes   Array of the following optional subnodes:
     *                                'type'       => MODIFIER_PUBLIC: Type
     *                                'byRef'      => false          : Whether to return by reference
     *                                'params'     => array()        : Parameters
     *                                'returnType' => null           : Return type
     *                                'stmts'      => array()        : Statements
     * @param array       $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
        $this->byRef = isset($subNodes['byRef'])  ? $subNodes['byRef']  : false;
        $this->name = $name;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : array();

        if ($this->type & Class_::MODIFIER_STATIC) {
            switch (strtolower($this->name)) {
                case '__construct':
                    throw new Error(sprintf('Constructor %s() cannot be static', $this->name));
                case '__destruct':
                    throw new Error(sprintf('Destructor %s() cannot be static', $this->name));
                case '__clone':
                    throw new Error(sprintf('Clone method %s() cannot be static', $this->name));
            }
        }
    }

    public function getSubNodeNames() {
        return array('type', 'byRef', 'name', 'params', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }

    public function isPublic() {
        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    }

    public function isProtected() {
        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    }

    public function isPrivate() {
        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    }

    public function isAbstract() {
        return (bool) ($this->type & Class_::MODIFIER_ABSTRACT);
    }

    public function isFinal() {
        return (bool) ($this->type & Class_::MODIFIER_FINAL);
    }

    public function isStatic() {
        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Class_ extends ClassLike
{
    const MODIFIER_PUBLIC    =  1;
    const MODIFIER_PROTECTED =  2;
    const MODIFIER_PRIVATE   =  4;
    const MODIFIER_STATIC    =  8;
    const MODIFIER_ABSTRACT  = 16;
    const MODIFIER_FINAL     = 32;

    const VISIBILITY_MODIFER_MASK = 7; // 1 | 2 | 4

    /** @var int Type */
    public $type;
    /** @var null|Node\Name Name of extended class */
    public $extends;
    /** @var Node\Name[] Names of implemented interfaces */
    public $implements;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a class node.
     *
     * @param string|null $name       Name
     * @param array       $subNodes   Array of the following optional subnodes:
     *                                'type'       => 0      : Type
     *                                'extends'    => null   : Name of extended class
     *                                'implements' => array(): Names of implemented interfaces
     *                                'stmts'      => array(): Statements
     * @param array       $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
        $this->name = $name;
        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : null;
        $this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();

        if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
        }

        if (isset(self::$specialNames[strtolower($this->extends)])) {
            throw new Error(
                sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
                $this->extends->getAttributes()
            );
        }

        foreach ($this->implements as $interface) {
            if (isset(self::$specialNames[strtolower($interface)])) {
                throw new Error(
                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
                    $interface->getAttributes()
                );
            }
        }
    }

    public function getSubNodeNames() {
        return array('type', 'name', 'extends', 'implements', 'stmts');
    }

    public function isAbstract() {
        return (bool) ($this->type & self::MODIFIER_ABSTRACT);
    }

    public function isFinal() {
        return (bool) ($this->type & self::MODIFIER_FINAL);
    }

    public function isAnonymous() {
        return null === $this->name;
    }

    /**
     * @internal
     */
    public static function verifyModifier($a, $b) {
        if ($a & self::VISIBILITY_MODIFER_MASK && $b & self::VISIBILITY_MODIFER_MASK) {
            throw new Error('Multiple access type modifiers are not allowed');
        }

        if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
            throw new Error('Multiple abstract modifiers are not allowed');
        }

        if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) {
            throw new Error('Multiple static modifiers are not allowed');
        }

        if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
            throw new Error('Multiple final modifiers are not allowed');
        }

        if ($a & 48 && $b & 48) {
            throw new Error('Cannot use the final modifier on an abstract class member');
        }
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Const_ extends Node\Stmt
{
    /** @var Node\Const_[] Constant declarations */
    public $consts;

    /**
     * Constructs a const list node.
     *
     * @param Node\Const_[] $consts     Constant declarations
     * @param array         $attributes Additional attributes
     */
    public function __construct(array $consts, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->consts = $consts;
    }

    public function getSubNodeNames() {
        return array('consts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Continue_ extends Node\Stmt
{
    /** @var null|Node\Expr Number of loops to continue */
    public $num;

    /**
     * Constructs a continue node.
     *
     * @param null|Node\Expr $num        Number of loops to continue
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $num = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->num = $num;
    }

    public function getSubNodeNames() {
        return array('num');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class DeclareDeclare extends Node\Stmt
{
    /** @var string Key */
    public $key;
    /** @var Node\Expr Value */
    public $value;

    /**
     * Constructs a declare key=>value pair node.
     *
     * @param string    $key        Key
     * @param Node\Expr $value      Value
     * @param array     $attributes Additional attributes
     */
    public function __construct($key, Node\Expr $value, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->key = $key;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('key', 'value');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
class Declare_ extends Node\Stmt
{
    /** @var DeclareDeclare[] List of declares */
    public $declares;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a declare node.
     *
     * @param DeclareDeclare[] $declares   List of declares
     * @param Node[]           $stmts      Statements
     * @param array            $attributes Additional attributes
     */
    public function __construct(array $declares, array $stmts, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->declares = $declares;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('declares', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Do_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a do while node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Echo_ extends Node\Stmt
{
    /** @var Node\Expr[] Expressions */
    public $exprs;

    /**
     * Constructs an echo node.
     *
     * @param Node\Expr[] $exprs      Expressions
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $exprs, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->exprs = $exprs;
    }

    public function getSubNodeNames() {
        return array('exprs');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class ElseIf_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs an elseif node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Else_ extends Node\Stmt
{
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs an else node.
     *
     * @param Node[] $stmts      Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class For_ extends Node\Stmt
{
    /** @var Node\Expr[] Init expressions */
    public $init;
    /** @var Node\Expr[] Loop conditions */
    public $cond;
    /** @var Node\Expr[] Loop expressions */
    public $loop;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a for loop node.
     *
     * @param array $subNodes   Array of the following optional subnodes:
     *                          'init'  => array(): Init expressions
     *                          'cond'  => array(): Loop conditions
     *                          'loop'  => array(): Loop expressions
     *                          'stmts' => array(): Statements
     * @param array $attributes Additional attributes
     */
    public function __construct(array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->init = isset($subNodes['init']) ? $subNodes['init'] : array();
        $this->cond = isset($subNodes['cond']) ? $subNodes['cond'] : array();
        $this->loop = isset($subNodes['loop']) ? $subNodes['loop'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('init', 'cond', 'loop', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Foreach_ extends Node\Stmt
{
    /** @var Node\Expr Expression to iterate */
    public $expr;
    /** @var null|Node\Expr Variable to assign key to */
    public $keyVar;
    /** @var bool Whether to assign value by reference */
    public $byRef;
    /** @var Node\Expr Variable to assign value to */
    public $valueVar;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a foreach node.
     *
     * @param Node\Expr $expr       Expression to iterate
     * @param Node\Expr $valueVar   Variable to assign value to
     * @param array     $subNodes   Array of the following optional subnodes:
     *                              'keyVar' => null   : Variable to assign key to
     *                              'byRef'  => false  : Whether to assign value by reference
     *                              'stmts'  => array(): Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
        $this->keyVar = isset($subNodes['keyVar']) ? $subNodes['keyVar'] : null;
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->valueVar = $valueVar;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('expr', 'keyVar', 'byRef', 'valueVar', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Node\FunctionLike;

class Function_ extends Node\Stmt implements FunctionLike
{
    /** @var bool Whether function returns by reference */
    public $byRef;
    /** @var string Name */
    public $name;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a function node.
     *
     * @param string $name       Name
     * @param array  $subNodes   Array of the following optional subnodes:
     *                           'byRef'      => false  : Whether to return by reference
     *                           'params'     => array(): Parameters
     *                           'returnType' => null   : Return type
     *                           'stmts'      => array(): Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->name = $name;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('byRef', 'name', 'params', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Global_ extends Node\Stmt
{
    /** @var Node\Expr[] Variables */
    public $vars;

    /**
     * Constructs a global variables list node.
     *
     * @param Node\Expr[] $vars       Variables to unset
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Goto_ extends Stmt
{
    /** @var string Name of label to jump to */
    public $name;

    /**
     * Constructs a goto node.
     *
     * @param string $name       Name of label to jump to
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class HaltCompiler extends Stmt
{
    /** @var string Remaining text after halt compiler statement. */
    public $remaining;

    /**
     * Constructs a __halt_compiler node.
     *
     * @param string $remaining  Remaining text after halt compiler statement.
     * @param array  $attributes Additional attributes
     */
    public function __construct($remaining, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->remaining = $remaining;
    }

    public function getSubNodeNames() {
        return array('remaining');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class If_ extends Node\Stmt
{
    /** @var Node\Expr Condition expression */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;
    /** @var ElseIf_[] Elseif clauses */
    public $elseifs;
    /** @var null|Else_ Else clause */
    public $else;

    /**
     * Constructs an if node.
     *
     * @param Node\Expr $cond       Condition
     * @param array     $subNodes   Array of the following optional subnodes:
     *                              'stmts'   => array(): Statements
     *                              'elseifs' => array(): Elseif clauses
     *                              'else'    => null   : Else clause
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
        $this->elseifs = isset($subNodes['elseifs']) ? $subNodes['elseifs'] : array();
        $this->else = isset($subNodes['else']) ? $subNodes['else'] : null;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts', 'elseifs', 'else');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class InlineHTML extends Stmt
{
    /** @var string String */
    public $value;

    /**
     * Constructs an inline HTML node.
     *
     * @param string $value      String
     * @param array  $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Interface_ extends ClassLike
{
    /** @var Node\Name[] Extended interfaces */
    public $extends;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a class node.
     *
     * @param string $name       Name
     * @param array  $subNodes   Array of the following optional subnodes:
     *                           'extends' => array(): Name of extended interfaces
     *                           'stmts'   => array(): Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();

        if (isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
        }

        foreach ($this->extends as $interface) {
            if (isset(self::$specialNames[strtolower($interface)])) {
                throw new Error(
                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
                    $interface->getAttributes()
                );
            }
        }
    }

    public function getSubNodeNames() {
        return array('name', 'extends', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Label extends Stmt
{
    /** @var string Name */
    public $name;

    /**
     * Constructs a label node.
     *
     * @param string $name       Name
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Namespace_ extends Node\Stmt
{
    /** @var null|Node\Name Name */
    public $name;
    /** @var Node[] Statements */
    public $stmts;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a namespace node.
     *
     * @param null|Node\Name $name       Name
     * @param null|Node[]    $stmts      Statements
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->stmts = $stmts;

        if (isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(
                sprintf('Cannot use \'%s\' as namespace name', $this->name),
                $this->name->getAttributes()
            );
        }

        if (null !== $this->stmts) {
            foreach ($this->stmts as $stmt) {
                if ($stmt instanceof self) {
                    throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
                }
            }
        }
    }

    public function getSubNodeNames() {
        return array('name', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Property extends Node\Stmt
{
    /** @var int Modifiers */
    public $type;
    /** @var PropertyProperty[] Properties */
    public $props;

    /**
     * Constructs a class property list node.
     *
     * @param int                $type       Modifiers
     * @param PropertyProperty[] $props      Properties
     * @param array              $attributes Additional attributes
     */
    public function __construct($type, array $props, array $attributes = array()) {
        if ($type & Class_::MODIFIER_ABSTRACT) {
            throw new Error('Properties cannot be declared abstract');
        }

        if ($type & Class_::MODIFIER_FINAL) {
            throw new Error('Properties cannot be declared final');
        }

        parent::__construct(null, $attributes);
        $this->type = $type;
        $this->props = $props;
    }

    public function getSubNodeNames() {
        return array('type', 'props');
    }

    public function isPublic() {
        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    }

    public function isProtected() {
        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    }

    public function isPrivate() {
        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    }

    public function isStatic() {
        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class PropertyProperty extends Node\Stmt
{
    /** @var string Name */
    public $name;
    /** @var null|Node\Expr Default */
    public $default;

    /**
     * Constructs a class property node.
     *
     * @param string         $name       Name
     * @param null|Node\Expr $default    Default value
     * @param array          $attributes Additional attributes
     */
    public function __construct($name, Node\Expr $default = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->default = $default;
    }

    public function getSubNodeNames() {
        return array('name', 'default');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Return_ extends Node\Stmt
{
    /** @var null|Node\Expr Expression */
    public $expr;

    /**
     * Constructs a return node.
     *
     * @param null|Node\Expr $expr       Expression
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class StaticVar extends Node\Stmt
{
    /** @var string Name */
    public $name;
    /** @var null|Node\Expr Default value */
    public $default;

    /**
     * Constructs a static variable node.
     *
     * @param string         $name       Name
     * @param null|Node\Expr $default    Default value
     * @param array          $attributes Additional attributes
     */
    public function __construct($name, Node\Expr $default = null, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->default = $default;
    }

    public function getSubNodeNames() {
        return array('name', 'default');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Static_ extends Stmt
{
    /** @var StaticVar[] Variable definitions */
    public $vars;

    /**
     * Constructs a static variables list node.
     *
     * @param StaticVar[] $vars       Variable definitions
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Switch_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Case_[] Case list */
    public $cases;

    /**
     * Constructs a case node.
     *
     * @param Node\Expr $cond       Condition
     * @param Case_[]   $cases      Case list
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $cases, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->cases = $cases;
    }

    public function getSubNodeNames() {
        return array('cond', 'cases');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Throw_ extends Node\Stmt
{
    /** @var Node\Expr Expression */
    public $expr;

    /**
     * Constructs a throw node.
     *
     * @param Node\Expr $expr       Expression
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;


class TraitUse extends Node\Stmt
{
    /** @var Node\Name[] Traits */
    public $traits;
    /** @var TraitUseAdaptation[] Adaptations */
    public $adaptations;

    /**
     * Constructs a trait use node.
     *
     * @param Node\Name[]          $traits      Traits
     * @param TraitUseAdaptation[] $adaptations Adaptations
     * @param array                $attributes  Additional attributes
     */
    public function __construct(array $traits, array $adaptations = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->traits = $traits;
        $this->adaptations = $adaptations;
    }

    public function getSubNodeNames() {
        return array('traits', 'adaptations');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

abstract class TraitUseAdaptation extends Node\Stmt
{
    /** @var Node\Name Trait name */
    public $trait;
    /** @var string Method name */
    public $method;
}
<?php

namespace PhpParser\Node\Stmt\TraitUseAdaptation;

use PhpParser\Node;

class Alias extends Node\Stmt\TraitUseAdaptation
{
    /** @var null|int New modifier */
    public $newModifier;
    /** @var null|string New name */
    public $newName;

    /**
     * Constructs a trait use precedence adaptation node.
     *
     * @param null|Node\Name $trait       Trait name
     * @param string         $method      Method name
     * @param null|int       $newModifier New modifier
     * @param null|string    $newName     New name
     * @param array          $attributes  Additional attributes
     */
    public function __construct($trait, $method, $newModifier, $newName, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->trait = $trait;
        $this->method = $method;
        $this->newModifier = $newModifier;
        $this->newName = $newName;
    }

    public function getSubNodeNames() {
        return array('trait', 'method', 'newModifier', 'newName');
    }
}
<?php

namespace PhpParser\Node\Stmt\TraitUseAdaptation;

use PhpParser\Node;

class Precedence extends Node\Stmt\TraitUseAdaptation
{
    /** @var Node\Name[] Overwritten traits */
    public $insteadof;

    /**
     * Constructs a trait use precedence adaptation node.
     *
     * @param Node\Name   $trait       Trait name
     * @param string      $method      Method name
     * @param Node\Name[] $insteadof   Overwritten traits
     * @param array       $attributes  Additional attributes
     */
    public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->trait = $trait;
        $this->method = $method;
        $this->insteadof = $insteadof;
    }

    public function getSubNodeNames() {
        return array('trait', 'method', 'insteadof');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Trait_ extends ClassLike
{
    /**
     * Constructs a trait node.
     *
     * @param string $name       Name
     * @param Node[] $stmts      Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('name', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class TryCatch extends Node\Stmt
{
    /** @var Node[] Statements */
    public $stmts;
    /** @var Catch_[] Catches */
    public $catches;
    /** @var null|Node[] Finally statements */
    public $finallyStmts;

    /**
     * Constructs a try catch node.
     *
     * @param Node[]      $stmts        Statements
     * @param Catch_[]    $catches      Catches
     * @param null|Node[] $finallyStmts Finally statements (null means no finally clause)
     * @param array|null  $attributes   Additional attributes
     */
    public function __construct(array $stmts, array $catches, array $finallyStmts = null, array $attributes = array()) {
        if (empty($catches) && null === $finallyStmts) {
            throw new Error('Cannot use try without catch or finally');
        }

        parent::__construct(null, $attributes);
        $this->stmts = $stmts;
        $this->catches = $catches;
        $this->finallyStmts = $finallyStmts;
    }

    public function getSubNodeNames() {
        return array('stmts', 'catches', 'finallyStmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Unset_ extends Node\Stmt
{
    /** @var Node\Expr[] Variables to unset */
    public $vars;

    /**
     * Constructs an unset node.
     *
     * @param Node\Expr[] $vars       Variables to unset
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class UseUse extends Node\Stmt
{
    /** @var Node\Name Namespace, class, function or constant to alias */
    public $name;
    /** @var string Alias */
    public $alias;

    /**
     * Constructs an alias (use) node.
     *
     * @param Node\Name   $name       Namespace/Class to alias
     * @param null|string $alias      Alias
     * @param array       $attributes Additional attributes
     */
    public function __construct(Node\Name $name, $alias = null, array $attributes = array()) {
        if (null === $alias) {
            $alias = $name->getLast();
        }

        if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
            throw new Error(sprintf(
                'Cannot use %s as %s because \'%2$s\' is a special class name',
                $name, $alias
            ));
        }

        parent::__construct(null, $attributes);
        $this->name = $name;
        $this->alias = $alias;
    }

    public function getSubNodeNames() {
        return array('name', 'alias');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Use_ extends Stmt
{
    const TYPE_NORMAL   = 1;
    const TYPE_FUNCTION = 2;
    const TYPE_CONSTANT = 3;

    /** @var int Type of alias */
    public $type;
    /** @var UseUse[] Aliases */
    public $uses;

    /**
     * Constructs an alias (use) list node.
     *
     * @param UseUse[] $uses       Aliases
     * @param int      $type       Type of alias
     * @param array    $attributes Additional attributes
     */
    public function __construct(array $uses, $type = self::TYPE_NORMAL, array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->type = $type;
        $this->uses = $uses;
    }

    public function getSubNodeNames() {
        return array('type', 'uses');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class While_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a while node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct(null, $attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser;

abstract class NodeAbstract implements Node
{
    private $subNodeNames;
    protected $attributes;

    /**
     * Creates a Node.
     *
     * If null is passed for the $subNodes parameter the node constructor must assign
     * all subnodes by itself and also override the getSubNodeNames() method.
     * DEPRECATED: If an array is passed as $subNodes instead, the properties corresponding
     * to the array keys will be set and getSubNodeNames() will return the keys of that
     * array.
     *
     * @param null|array $subNodes   Null or an array of sub nodes (deprecated)
     * @param array      $attributes Array of attributes
     */
    public function __construct($subNodes = array(), array $attributes = array()) {
        $this->attributes = $attributes;

        if (null !== $subNodes) {
            foreach ($subNodes as $name => $value) {
                $this->$name = $value;
            }
            $this->subNodeNames = array_keys($subNodes);
        }
    }

    /**
     * Gets the type of the node.
     *
     * @return string Type of the node
     */
    public function getType() {
        return strtr(substr(rtrim(get_class($this), '_'), 15), '\\', '_');
    }

    /**
     * Gets the names of the sub nodes.
     *
     * @return array Names of sub nodes
     */
    public function getSubNodeNames() {
        return $this->subNodeNames;
    }

    /**
     * Gets line the node started in.
     *
     * @return int Line
     */
    public function getLine() {
        return $this->getAttribute('startLine', -1);
    }

    /**
     * Sets line the node started in.
     *
     * @param int $line Line
     */
    public function setLine($line) {
        $this->setAttribute('startLine', (int) $line);
    }

    /**
     * Gets the doc comment of the node.
     *
     * The doc comment has to be the last comment associated with the node.
     *
     * @return null|Comment\Doc Doc comment object or null
     */
    public function getDocComment() {
        $comments = $this->getAttribute('comments');
        if (!$comments) {
            return null;
        }

        $lastComment = $comments[count($comments) - 1];
        if (!$lastComment instanceof Comment\Doc) {
            return null;
        }

        return $lastComment;
    }

    /**
     * {@inheritDoc}
     */
    public function setAttribute($key, $value) {
        $this->attributes[$key] = $value;
    }

    /**
     * {@inheritDoc}
     */
    public function hasAttribute($key) {
        return array_key_exists($key, $this->attributes);
    }

    /**
     * {@inheritDoc}
     */
    public function &getAttribute($key, $default = null) {
        if (!array_key_exists($key, $this->attributes)) {
            return $default;
        } else {
            return $this->attributes[$key];
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getAttributes() {
        return $this->attributes;
    }
}
<?php

namespace PhpParser;

class NodeDumper
{
    /**
     * Dumps a node or array.
     *
     * @param array|Node $node Node or array to dump
     *
     * @return string Dumped value
     */
    public function dump($node) {
        if ($node instanceof Node) {
            $r = $node->getType() . '(';

            foreach ($node->getSubNodeNames() as $key) {
                $r .= "\n    " . $key . ': ';

                $value = $node->$key;
                if (null === $value) {
                    $r .= 'null';
                } elseif (false === $value) {
                    $r .= 'false';
                } elseif (true === $value) {
                    $r .= 'true';
                } elseif (is_scalar($value)) {
                    $r .= $value;
                } else {
                    $r .= str_replace("\n", "\n    ", $this->dump($value));
                }
            }
        } elseif (is_array($node)) {
            $r = 'array(';

            foreach ($node as $key => $value) {
                $r .= "\n    " . $key . ': ';

                if (null === $value) {
                    $r .= 'null';
                } elseif (false === $value) {
                    $r .= 'false';
                } elseif (true === $value) {
                    $r .= 'true';
                } elseif (is_scalar($value)) {
                    $r .= $value;
                } else {
                    $r .= str_replace("\n", "\n    ", $this->dump($value));
                }
            }
        } else {
            throw new \InvalidArgumentException('Can only dump nodes and arrays.');
        }

        return $r . "\n)";
    }
}
<?php

namespace PhpParser;

class NodeTraverser implements NodeTraverserInterface
{
    /**
     * @var NodeVisitor[] Visitors
     */
    protected $visitors;

    /**
     * @var bool
     */
    private $cloneNodes;

    /**
     * Constructs a node traverser.
     *
     * @param bool $cloneNodes Should the traverser clone the nodes when traversing the AST
     */
    public function __construct($cloneNodes = true) {
        $this->visitors = array();
        $this->cloneNodes = $cloneNodes;
    }

    /**
     * Adds a visitor.
     *
     * @param NodeVisitor $visitor Visitor to add
     */
    public function addVisitor(NodeVisitor $visitor) {
        $this->visitors[] = $visitor;
    }

    /**
     * Removes an added visitor.
     *
     * @param NodeVisitor $visitor
     */
    public function removeVisitor(NodeVisitor $visitor) {
        foreach ($this->visitors as $index => $storedVisitor) {
            if ($storedVisitor === $visitor) {
                unset($this->visitors[$index]);
                break;
            }
        }
    }

    /**
     * Traverses an array of nodes using the registered visitors.
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return Node[] Traversed array of nodes
     */
    public function traverse(array $nodes) {
        foreach ($this->visitors as $visitor) {
            if (null !== $return = $visitor->beforeTraverse($nodes)) {
                $nodes = $return;
            }
        }

        $nodes = $this->traverseArray($nodes);

        foreach ($this->visitors as $visitor) {
            if (null !== $return = $visitor->afterTraverse($nodes)) {
                $nodes = $return;
            }
        }

        return $nodes;
    }

    protected function traverseNode(Node $node) {
        if ($this->cloneNodes) {
            $node = clone $node;
        }

        foreach ($node->getSubNodeNames() as $name) {
            $subNode =& $node->$name;

            if (is_array($subNode)) {
                $subNode = $this->traverseArray($subNode);
            } elseif ($subNode instanceof Node) {
                $traverseChildren = true;
                foreach ($this->visitors as $visitor) {
                    $return = $visitor->enterNode($subNode);
                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
                        $traverseChildren = false;
                    } else if (null !== $return) {
                        $subNode = $return;
                    }
                }

                if ($traverseChildren) {
                    $subNode = $this->traverseNode($subNode);
                }

                foreach ($this->visitors as $visitor) {
                    if (null !== $return = $visitor->leaveNode($subNode)) {
                        $subNode = $return;
                    }
                }
            }
        }

        return $node;
    }

    protected function traverseArray(array $nodes) {
        $doNodes = array();

        foreach ($nodes as $i => &$node) {
            if (is_array($node)) {
                $node = $this->traverseArray($node);
            } elseif ($node instanceof Node) {
                $traverseChildren = true;
                foreach ($this->visitors as $visitor) {
                    $return = $visitor->enterNode($node);
                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
                        $traverseChildren = false;
                    } else if (null !== $return) {
                        $node = $return;
                    }
                }

                if ($traverseChildren) {
                    $node = $this->traverseNode($node);
                }

                foreach ($this->visitors as $visitor) {
                    $return = $visitor->leaveNode($node);

                    if (self::REMOVE_NODE === $return) {
                        $doNodes[] = array($i, array());
                        break;
                    } elseif (is_array($return)) {
                        $doNodes[] = array($i, $return);
                        break;
                    } elseif (null !== $return) {
                        $node = $return;
                    }
                }
            }
        }

        if (!empty($doNodes)) {
            while (list($i, $replace) = array_pop($doNodes)) {
                array_splice($nodes, $i, 1, $replace);
            }
        }

        return $nodes;
    }
}
<?php

namespace PhpParser;

interface NodeTraverserInterface
{
    /**
     * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
     * of the current node will not be traversed for any visitors.
     *
     * For subsequent visitors enterNode() will still be called on the current
     * node and leaveNode() will also be invoked for the current node.
     */
    const DONT_TRAVERSE_CHILDREN = 1;

    /**
     * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
     * in an array, it will be removed from the array.
     *
     * For subsequent visitors leaveNode() will still be invoked for the
     * removed node.
     */
    const REMOVE_NODE = false;

    /**
     * Adds a visitor.
     *
     * @param NodeVisitor $visitor Visitor to add
     */
    function addVisitor(NodeVisitor $visitor);

    /**
     * Removes an added visitor.
     *
     * @param NodeVisitor $visitor
     */
    function removeVisitor(NodeVisitor $visitor);

    /**
     * Traverses an array of nodes using the registered visitors.
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return Node[] Traversed array of nodes
     */
    function traverse(array $nodes);
}

<?php

namespace PhpParser;

interface NodeVisitor
{
    /**
     * Called once before traversal.
     *
     * Return value semantics:
     *  * null:      $nodes stays as-is
     *  * otherwise: $nodes is set to the return value
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return null|Node[] Array of nodes
     */
    public function beforeTraverse(array $nodes);

    /**
     * Called when entering a node.
     *
     * Return value semantics:
     *  * null:      $node stays as-is
     *  * otherwise: $node is set to the return value
     *
     * @param Node $node Node
     *
     * @return null|Node Node
     */
    public function enterNode(Node $node);

    /**
     * Called when leaving a node.
     *
     * Return value semantics:
     *  * null:      $node stays as-is
     *  * false:     $node is removed from the parent array
     *  * array:     The return value is merged into the parent array (at the position of the $node)
     *  * otherwise: $node is set to the return value
     *
     * @param Node $node Node
     *
     * @return null|Node|false|Node[] Node
     */
    public function leaveNode(Node $node);

    /**
     * Called once after traversal.
     *
     * Return value semantics:
     *  * null:      $nodes stays as-is
     *  * otherwise: $nodes is set to the return value
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return null|Node[] Array of nodes
     */
    public function afterTraverse(array $nodes);
}<?php

namespace PhpParser\NodeVisitor;

use PhpParser\NodeVisitorAbstract;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;

class NameResolver extends NodeVisitorAbstract
{
    /** @var null|Name Current namespace */
    protected $namespace;

    /** @var array Map of format [aliasType => [aliasName => originalName]] */
    protected $aliases;

    public function beforeTraverse(array $nodes) {
        $this->resetState();
    }

    public function enterNode(Node $node) {
        if ($node instanceof Stmt\Namespace_) {
            $this->resetState($node->name);
        } elseif ($node instanceof Stmt\Use_) {
            foreach ($node->uses as $use) {
                $this->addAlias($use, $node->type);
            }
        } elseif ($node instanceof Stmt\Class_) {
            if (null !== $node->extends) {
                $node->extends = $this->resolveClassName($node->extends);
            }

            foreach ($node->implements as &$interface) {
                $interface = $this->resolveClassName($interface);
            }

            if (null !== $node->name) {
                $this->addNamespacedName($node);
            }
        } elseif ($node instanceof Stmt\Interface_) {
            foreach ($node->extends as &$interface) {
                $interface = $this->resolveClassName($interface);
            }

            $this->addNamespacedName($node);
        } elseif ($node instanceof Stmt\Trait_) {
            $this->addNamespacedName($node);
        } elseif ($node instanceof Stmt\Function_) {
            $this->addNamespacedName($node);
            $this->resolveSignature($node);
        } elseif ($node instanceof Stmt\ClassMethod
                  || $node instanceof Expr\Closure
        ) {
            $this->resolveSignature($node);
        } elseif ($node instanceof Stmt\Const_) {
            foreach ($node->consts as $const) {
                $this->addNamespacedName($const);
            }
        } elseif ($node instanceof Expr\StaticCall
                  || $node instanceof Expr\StaticPropertyFetch
                  || $node instanceof Expr\ClassConstFetch
                  || $node instanceof Expr\New_
                  || $node instanceof Expr\Instanceof_
        ) {
            if ($node->class instanceof Name) {
                $node->class = $this->resolveClassName($node->class);
            }
        } elseif ($node instanceof Stmt\Catch_) {
            $node->type = $this->resolveClassName($node->type);
        } elseif ($node instanceof Expr\FuncCall) {
            if ($node->name instanceof Name) {
                $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
            }
        } elseif ($node instanceof Expr\ConstFetch) {
            $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
        } elseif ($node instanceof Stmt\TraitUse) {
            foreach ($node->traits as &$trait) {
                $trait = $this->resolveClassName($trait);
            }

            foreach ($node->adaptations as $adaptation) {
                if (null !== $adaptation->trait) {
                    $adaptation->trait = $this->resolveClassName($adaptation->trait);
                }

                if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
                    foreach ($adaptation->insteadof as &$insteadof) {
                        $insteadof = $this->resolveClassName($insteadof);
                    }
                }
            }

        }
    }

    protected function resetState(Name $namespace = null) {
        $this->namespace = $namespace;
        $this->aliases   = array(
            Stmt\Use_::TYPE_NORMAL   => array(),
            Stmt\Use_::TYPE_FUNCTION => array(),
            Stmt\Use_::TYPE_CONSTANT => array(),
        );
    }

    protected function addAlias(Stmt\UseUse $use, $type) {
        // Constant names are case sensitive, everything else case insensitive
        if ($type === Stmt\Use_::TYPE_CONSTANT) {
            $aliasName = $use->alias;
        } else {
            $aliasName = strtolower($use->alias);
        }

        if (isset($this->aliases[$type][$aliasName])) {
            $typeStringMap = array(
                Stmt\Use_::TYPE_NORMAL   => '',
                Stmt\Use_::TYPE_FUNCTION => 'function ',
                Stmt\Use_::TYPE_CONSTANT => 'const ',
            );

            throw new Error(
                sprintf(
                    'Cannot use %s%s as %s because the name is already in use',
                    $typeStringMap[$type], $use->name, $use->alias
                ),
                $use->getLine()
            );
        }

        $this->aliases[$type][$aliasName] = $use->name;
    }

    /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
    private function resolveSignature($node) {
        foreach ($node->params as $param) {
            if ($param->type instanceof Name) {
                $param->type = $this->resolveClassName($param->type);
            }
        }
        if ($node->returnType instanceof Name) {
            $node->returnType = $this->resolveClassName($node->returnType);
        }
    }

    protected function resolveClassName(Name $name) {
        // don't resolve special class names
        if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
            if (!$name->isUnqualified()) {
                throw new Error(
                    sprintf("'\\%s' is an invalid class name", $name->toString()),
                    $name->getLine()
                );
            }

            return $name;
        }

        // fully qualified names are already resolved
        if ($name->isFullyQualified()) {
            return $name;
        }

        $aliasName = strtolower($name->getFirst());
        if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
            // resolve aliases (for non-relative names)
            $name->setFirst($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName]);
        } elseif (null !== $this->namespace) {
            // if no alias exists prepend current namespace
            $name->prepend($this->namespace);
        }

        return new Name\FullyQualified($name->parts, $name->getAttributes());
    }

    protected function resolveOtherName(Name $name, $type) {
        // fully qualified names are already resolved
        if ($name->isFullyQualified()) {
            return $name;
        }

        // resolve aliases for qualified names
        $aliasName = strtolower($name->getFirst());
        if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
            $name->setFirst($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName]);
        } elseif ($name->isUnqualified()) {
            if ($type === Stmt\Use_::TYPE_CONSTANT) {
                // constant aliases are case-sensitive, function aliases case-insensitive
                $aliasName = $name->getFirst();
            }

            if (isset($this->aliases[$type][$aliasName])) {
                // resolve unqualified aliases
                $name->set($this->aliases[$type][$aliasName]);
            } else {
                // unqualified, unaliased names cannot be resolved at compile-time
                return $name;
            }
        } elseif (null !== $this->namespace) {
            // if no alias exists prepend current namespace
            $name->prepend($this->namespace);
        }

        return new Name\FullyQualified($name->parts, $name->getAttributes());
    }

    protected function addNamespacedName(Node $node) {
        if (null !== $this->namespace) {
            $node->namespacedName = clone $this->namespace;
            $node->namespacedName->append($node->name);
        } else {
            $node->namespacedName = new Name($node->name, $node->getAttributes());
        }
    }
}
<?php

namespace PhpParser;

/**
 * @codeCoverageIgnore
 */
class NodeVisitorAbstract implements NodeVisitor
{
    public function beforeTraverse(array $nodes)    { }
    public function enterNode(Node $node) { }
    public function leaveNode(Node $node) { }
    public function afterTraverse(array $nodes)     { }
}<?php

namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;

/* This is an automatically GENERATED file, which should not be manually edited.
 * Instead edit one of the following:
 *  * the grammar file grammar/zend_language_parser.phpy
 *  * the skeleton file grammar/kymacc.php.parser
 *  * the preprocessing script grammar/rebuildParser.php
 */
class Parser extends ParserAbstract
{
    protected $tokenToSymbolMapSize = 392;
    protected $actionTableSize = 1186;
    protected $gotoTableSize = 591;

    protected $invalidSymbol = 157;
    protected $errorSymbol = 1;
    protected $defaultAction = -32766;
    protected $unexpectedTokenRule = 32767;

    protected $YY2TBLSTATE  = 409;
    protected $YYNLSTATES   = 646;

    const YYERRTOK = 256;
    const T_INCLUDE = 257;
    const T_INCLUDE_ONCE = 258;
    const T_EVAL = 259;
    const T_REQUIRE = 260;
    const T_REQUIRE_ONCE = 261;
    const T_LOGICAL_OR = 262;
    const T_LOGICAL_XOR = 263;
    const T_LOGICAL_AND = 264;
    const T_PRINT = 265;
    const T_YIELD = 266;
    const T_YIELD_FROM = 267;
    const T_PLUS_EQUAL = 268;
    const T_MINUS_EQUAL = 269;
    const T_MUL_EQUAL = 270;
    const T_DIV_EQUAL = 271;
    const T_CONCAT_EQUAL = 272;
    const T_MOD_EQUAL = 273;
    const T_AND_EQUAL = 274;
    const T_OR_EQUAL = 275;
    const T_XOR_EQUAL = 276;
    const T_SL_EQUAL = 277;
    const T_SR_EQUAL = 278;
    const T_POW_EQUAL = 279;
    const T_COALESCE = 280;
    const T_BOOLEAN_OR = 281;
    const T_BOOLEAN_AND = 282;
    const T_IS_EQUAL = 283;
    const T_IS_NOT_EQUAL = 284;
    const T_IS_IDENTICAL = 285;
    const T_IS_NOT_IDENTICAL = 286;
    const T_SPACESHIP = 287;
    const T_IS_SMALLER_OR_EQUAL = 288;
    const T_IS_GREATER_OR_EQUAL = 289;
    const T_SL = 290;
    const T_SR = 291;
    const T_INSTANCEOF = 292;
    const T_INC = 293;
    const T_DEC = 294;
    const T_INT_CAST = 295;
    const T_DOUBLE_CAST = 296;
    const T_STRING_CAST = 297;
    const T_ARRAY_CAST = 298;
    const T_OBJECT_CAST = 299;
    const T_BOOL_CAST = 300;
    const T_UNSET_CAST = 301;
    const T_POW = 302;
    const T_NEW = 303;
    const T_CLONE = 304;
    const T_EXIT = 305;
    const T_IF = 306;
    const T_ELSEIF = 307;
    const T_ELSE = 308;
    const T_ENDIF = 309;
    const T_LNUMBER = 310;
    const T_DNUMBER = 311;
    const T_STRING = 312;
    const T_STRING_VARNAME = 313;
    const T_VARIABLE = 314;
    const T_NUM_STRING = 315;
    const T_INLINE_HTML = 316;
    const T_CHARACTER = 317;
    const T_BAD_CHARACTER = 318;
    const T_ENCAPSED_AND_WHITESPACE = 319;
    const T_CONSTANT_ENCAPSED_STRING = 320;
    const T_ECHO = 321;
    const T_DO = 322;
    const T_WHILE = 323;
    const T_ENDWHILE = 324;
    const T_FOR = 325;
    const T_ENDFOR = 326;
    const T_FOREACH = 327;
    const T_ENDFOREACH = 328;
    const T_DECLARE = 329;
    const T_ENDDECLARE = 330;
    const T_AS = 331;
    const T_SWITCH = 332;
    const T_ENDSWITCH = 333;
    const T_CASE = 334;
    const T_DEFAULT = 335;
    const T_BREAK = 336;
    const T_CONTINUE = 337;
    const T_GOTO = 338;
    const T_FUNCTION = 339;
    const T_CONST = 340;
    const T_RETURN = 341;
    const T_TRY = 342;
    const T_CATCH = 343;
    const T_FINALLY = 344;
    const T_THROW = 345;
    const T_USE = 346;
    const T_INSTEADOF = 347;
    const T_GLOBAL = 348;
    const T_STATIC = 349;
    const T_ABSTRACT = 350;
    const T_FINAL = 351;
    const T_PRIVATE = 352;
    const T_PROTECTED = 353;
    const T_PUBLIC = 354;
    const T_VAR = 355;
    const T_UNSET = 356;
    const T_ISSET = 357;
    const T_EMPTY = 358;
    const T_HALT_COMPILER = 359;
    const T_CLASS = 360;
    const T_TRAIT = 361;
    const T_INTERFACE = 362;
    const T_EXTENDS = 363;
    const T_IMPLEMENTS = 364;
    const T_OBJECT_OPERATOR = 365;
    const T_DOUBLE_ARROW = 366;
    const T_LIST = 367;
    const T_ARRAY = 368;
    const T_CALLABLE = 369;
    const T_CLASS_C = 370;
    const T_TRAIT_C = 371;
    const T_METHOD_C = 372;
    const T_FUNC_C = 373;
    const T_LINE = 374;
    const T_FILE = 375;
    const T_COMMENT = 376;
    const T_DOC_COMMENT = 377;
    const T_OPEN_TAG = 378;
    const T_OPEN_TAG_WITH_ECHO = 379;
    const T_CLOSE_TAG = 380;
    const T_WHITESPACE = 381;
    const T_START_HEREDOC = 382;
    const T_END_HEREDOC = 383;
    const T_DOLLAR_OPEN_CURLY_BRACES = 384;
    const T_CURLY_OPEN = 385;
    const T_PAAMAYIM_NEKUDOTAYIM = 386;
    const T_NAMESPACE = 387;
    const T_NS_C = 388;
    const T_DIR = 389;
    const T_NS_SEPARATOR = 390;
    const T_ELLIPSIS = 391;

    protected $symbolToName = array(
        "EOF",
        "error",
        "T_INCLUDE",
        "T_INCLUDE_ONCE",
        "T_EVAL",
        "T_REQUIRE",
        "T_REQUIRE_ONCE",
        "','",
        "T_LOGICAL_OR",
        "T_LOGICAL_XOR",
        "T_LOGICAL_AND",
        "T_PRINT",
        "T_YIELD",
        "T_YIELD_FROM",
        "'='",
        "T_PLUS_EQUAL",
        "T_MINUS_EQUAL",
        "T_MUL_EQUAL",
        "T_DIV_EQUAL",
        "T_CONCAT_EQUAL",
        "T_MOD_EQUAL",
        "T_AND_EQUAL",
        "T_OR_EQUAL",
        "T_XOR_EQUAL",
        "T_SL_EQUAL",
        "T_SR_EQUAL",
        "T_POW_EQUAL",
        "'?'",
        "':'",
        "T_COALESCE",
        "T_BOOLEAN_OR",
        "T_BOOLEAN_AND",
        "'|'",
        "'^'",
        "'&'",
        "T_IS_EQUAL",
        "T_IS_NOT_EQUAL",
        "T_IS_IDENTICAL",
        "T_IS_NOT_IDENTICAL",
        "T_SPACESHIP",
        "'<'",
        "T_IS_SMALLER_OR_EQUAL",
        "'>'",
        "T_IS_GREATER_OR_EQUAL",
        "T_SL",
        "T_SR",
        "'+'",
        "'-'",
        "'.'",
        "'*'",
        "'/'",
        "'%'",
        "'!'",
        "T_INSTANCEOF",
        "'~'",
        "T_INC",
        "T_DEC",
        "T_INT_CAST",
        "T_DOUBLE_CAST",
        "T_STRING_CAST",
        "T_ARRAY_CAST",
        "T_OBJECT_CAST",
        "T_BOOL_CAST",
        "T_UNSET_CAST",
        "'@'",
        "T_POW",
        "'['",
        "T_NEW",
        "T_CLONE",
        "T_EXIT",
        "T_IF",
        "T_ELSEIF",
        "T_ELSE",
        "T_ENDIF",
        "T_LNUMBER",
        "T_DNUMBER",
        "T_STRING",
        "T_STRING_VARNAME",
        "T_VARIABLE",
        "T_NUM_STRING",
        "T_INLINE_HTML",
        "T_ENCAPSED_AND_WHITESPACE",
        "T_CONSTANT_ENCAPSED_STRING",
        "T_ECHO",
        "T_DO",
        "T_WHILE",
        "T_ENDWHILE",
        "T_FOR",
        "T_ENDFOR",
        "T_FOREACH",
        "T_ENDFOREACH",
        "T_DECLARE",
        "T_ENDDECLARE",
        "T_AS",
        "T_SWITCH",
        "T_ENDSWITCH",
        "T_CASE",
        "T_DEFAULT",
        "T_BREAK",
        "T_CONTINUE",
        "T_GOTO",
        "T_FUNCTION",
        "T_CONST",
        "T_RETURN",
        "T_TRY",
        "T_CATCH",
        "T_FINALLY",
        "T_THROW",
        "T_USE",
        "T_INSTEADOF",
        "T_GLOBAL",
        "T_STATIC",
        "T_ABSTRACT",
        "T_FINAL",
        "T_PRIVATE",
        "T_PROTECTED",
        "T_PUBLIC",
        "T_VAR",
        "T_UNSET",
        "T_ISSET",
        "T_EMPTY",
        "T_HALT_COMPILER",
        "T_CLASS",
        "T_TRAIT",
        "T_INTERFACE",
        "T_EXTENDS",
        "T_IMPLEMENTS",
        "T_OBJECT_OPERATOR",
        "T_DOUBLE_ARROW",
        "T_LIST",
        "T_ARRAY",
        "T_CALLABLE",
        "T_CLASS_C",
        "T_TRAIT_C",
        "T_METHOD_C",
        "T_FUNC_C",
        "T_LINE",
        "T_FILE",
        "T_START_HEREDOC",
        "T_END_HEREDOC",
        "T_DOLLAR_OPEN_CURLY_BRACES",
        "T_CURLY_OPEN",
        "T_PAAMAYIM_NEKUDOTAYIM",
        "T_NAMESPACE",
        "T_NS_C",
        "T_DIR",
        "T_NS_SEPARATOR",
        "T_ELLIPSIS",
        "';'",
        "'{'",
        "'}'",
        "'('",
        "')'",
        "'$'",
        "'`'",
        "']'",
        "'\"'"
    );

    protected $tokenToSymbol = array(
            0,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,   52,  156,  157,  153,   51,   34,  157,
          151,  152,   49,   46,    7,   47,   48,   50,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,   28,  148,
           40,   14,   42,   27,   64,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,   66,  157,  155,   33,  157,  154,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  149,   32,  150,   54,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,    1,    2,    3,    4,
            5,    6,    8,    9,   10,   11,   12,   13,   15,   16,
           17,   18,   19,   20,   21,   22,   23,   24,   25,   26,
           29,   30,   31,   35,   36,   37,   38,   39,   41,   43,
           44,   45,   53,   55,   56,   57,   58,   59,   60,   61,
           62,   63,   65,   67,   68,   69,   70,   71,   72,   73,
           74,   75,   76,   77,   78,   79,   80,  157,  157,   81,
           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
          132,  133,  134,  135,  136,  137,  157,  157,  157,  157,
          157,  157,  138,  139,  140,  141,  142,  143,  144,  145,
          146,  147
    );

    protected $action = array(
          707,   60,   61,  420,   62,   63,-32766,-32766,-32766,-32766,
           64,   65,   66,  223,  224,  225,  226,  227,  228,  229,
          230,  231,    0,  232,  233,  234,  235,  236,  237,  238,
          239,  240,  241,  242,  243,-32766,-32766,-32766,-32766,-32766,
        -32767,-32767,-32767,-32767,  650,   67,   68,   58,  244,  245,
          129,   69,  460,   70,  297,  298,   71,   72,   73,   74,
           75,   76,   77,   78,-32766,   32,  305,   79,  411,  421,
        -32766,-32766,-32766,  968,  969,  463, -118, 1061,  413,  696,
          447,  464,   46,   27,  422,  366,  465,  438,  466,-32766,
          467,-32766,-32766,  423,  220,  221,  222,   36,   37,  468,
          424,  441,   38,  469,  221,  222,   80,-32766,  329,  359,
          360,  491,  751,  207,  425,  470,  471,  472,  473,  474,
          608,-32766,  207,   55,  678,  723,  475,  476,  477,  478,
          128,  974,  975,  976,  977,  971,  972,  315,   84,   85,
           86,  419,  491,  978,  973,  425,  440,  702,  618,  635,
           47,  135,  340,  327,  301,  331,  443,   40,  313,   87,
           88,   89,   90,   91,   92,   93,   94,   95,   96,   97,
           98,   99,  100,  101,  102,  103,  104,  105,  106,  107,
          108,  109,   31,  308,-32766,-32766,-32766,-32766,-32766,  288,
        -32766,  775,  776,  800,  805,  110,  650,  220,  221,  222,
        -32766,-32766, 1027,-32766,-32766,-32766,  125,-32766,  439,-32766,
          569,-32766,  917, -123,-32766,  286,  207,   35,-32766,-32766,
        -32766,  428,  428,-32766,-32766,   22,  693,-32766,  211,  133,
        -32766,  490,  752,-32766,  301, 1090,  470,  471,-32766, -119,
          342, 1018,  694,  378,  917,  678,  723,  475,  476,  616,
          791,   41,  111,  112,  113,  114,  115,  116,  117,  118,
          119,  120,  121,  122,-32766,-32766,-32766,  132,-32766,-32766,
        -32766, 1093,  781, 1095, 1094,-32766,  650,  220,  221,  222,
        -32766,-32766,   57,-32766,-32766,-32766,  935,-32766,   54,-32766,
        -32766,-32766,  856,  858,-32766,  358,  207,  248,-32766,-32766,
        -32766,  428,-32766,-32766,-32766,   39,  300,-32766,  650,  388,
        -32766,  490,-32766,-32766,  304,-32766,-32766,-32766,-32766,-32766,
          630,-32766,  617,-32766,  917,  290,-32766,  782,  356,  357,
        -32766,-32766,-32766,  428, 1068,-32766,-32766,  434,  207,-32766,
          620, 1019,-32766,  490,  621,-32766,  302,  132,-32766,-32766,
        -32766,  212,  606,  241,  242,  243,  917,  412,-32766,-32766,
        -32766,  642, 1055,  326, -401,  126,  623,  470,  471,  244,
          245,  124,  131,  416,  470,  471,  656,  723,  475,  476,
        -32766,-32766,-32766,  678,  723,  475,  476,  222,  650,-32766,
        -32766,-32766,-32766,  428,  810,-32766,-32766,-32766,  339,-32766,
          303,-32766, -171,-32766,  207,  659,-32766, 1026,-32766,  477,
        -32766,-32766,-32766,  428,-32766,-32766,-32766,  428,  445,-32766,
          650,  213,-32766,  490,-32766,-32766,-32766,-32766,-32766,-32766,
        -32766,-32766,-32766,-32766,  428,-32766,  917,  348,-32766,  428,
         1083,-32766,-32766,-32766,-32766,  428, 1061,-32766,-32766,-32766,
        -32766,-32766,-32766, 1027,-32766,  490, 1065,-32766,  289,  332,
        -32766,-32766,-32766, 1083,  214,-32766,-32766,-32766,  917,-32766,
        -32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,  249,-32767,
        -32767,-32767,-32767,  347,-32766,  123,-32766,-32766,-32766,-32766,
          299,  133,-32766,-32766,-32766,  220,  221,  222, 1025,  250,
          650,  220,  221,  222,-32766,-32766,-32766,-32766,-32766,-32766,
        -32766,-32766,  137,-32766,  207,-32766,  566,  980,-32766,  634,
          207,  340,-32766,-32766,-32766,  428,-32766,-32766,-32766,  134,
          575,-32766,  650,  314,-32766,  490,-32766,  716, 1024,-32766,
        -32766,-32766,-32766,-32766,  538,-32766,  706,-32766,  244,  245,
        -32766,  454,  581,-32766,-32766,-32766,-32766,  428,  639,-32766,
        -32766,  449,   28,-32766,  917,  136,-32766,  490,  238,  239,
          240,  110,-32766,-32766,-32766,  104,  105,  106,  308,-32766,
          138,  367,  588,  589,  593,  650,  804,  638,  980,-32766,
          615,  305,-32766,-32766,-32766,  346,-32766,   52,-32766,  650,
        -32766, 1061,   50,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
          428,   59,-32766,-32766,  470,  471,-32766,  917,  605,-32766,
          490,  246,-32766,  678,  723,  475,  476,-32766,  650,  107,
          108,  109,-32766,  308,  945,-32766,-32766,-32766,-32766,-32766,
           56,-32766,   49,-32766,   51,  110,-32766,  775,  776,  917,
        -32766,-32766,-32766,  428,   53,-32766,-32766,-32766,-32766,-32766,
          529,  961,-32766,  490,  657,  625,  491,  815,  645,  425,
        -32766,  528,  516,  512,  429,-32766,  340,  511,  435,  651,
          433,  650,  667,  650, 1088,-32766,  669,  812,-32766,-32766,
        -32766,-32766,-32766,  600,-32766,  938,-32766,  515,  607,-32766,
          686,-32766,  917,-32766,-32766,-32766,  428,-32766,-32766,-32766,
          590,  345,-32766,  650, 1083,-32766,  490,-32766,  559,  437,
        -32766,-32766,-32766,-32766,-32766,  458,-32766,-32766,-32766, -168,
          584,-32766,  307,  285,  531,-32766,-32766,-32766,  428,  572,
        -32766,-32766,  336,  432,-32766,  725,  428,-32766,  490,  724,
           42,  585,  979,  688,-32766,-32766,  338,  127,  330,  718,
        -32766,   23,  811,  341,  521,    0,  650,  434, -308,    0,
        -32766,  335,    0,-32766,-32766,-32766, -402,-32766, -401,-32766,
          328,-32766,  915,  477,-32766,  690,-32766,    0,-32766,-32766,
        -32766,  428,  318,-32766,-32766,    0,-32766,-32766, -300,  613,
        -32766,  490,  650, -309,  381,  368,-32766,  334,-32766,-32766,
        -32766,-32766,  416,-32766, 1056,-32766,  247,-32766,  809,  745,
        -32766,  735,  746,  698,-32766,-32766,-32766,  428,  801,-32766,
        -32766,  683,  663,-32766,  215,  216,-32766,  490,-32766,-32766,
          217,  662,  218,  681,-32766,-32767,-32767,-32767,-32767,  102,
          103,  104,  105,  106,  209,  754,  661,   -1,  660,  215,
          216,  705,  968,  969,  692,  217,-32766,  218,  808,  627,
          970,-32766,-32766,-32766,-32766,-32766,  626,  737,  739,  209,
        -32766,-32766,-32766,  704,  691,  695,  689,  968,  969,  687,
        -32766,-32766,-32766,-32766,-32766,  970,  697,   44,   45,-32766,
          643,-32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,
        -32767,-32767,  644,  640,  637,  632,  631,  629,  556,  624,
          974,  975,  976,  977,  971,  972,  394,  619,   83,  130,
          641,  591,  978,  973, 1066,  793,  959, 1058, 1040,  219,
         1046,-32766, 1060,  556, 1062,  974,  975,  976,  977,  971,
          972,  394, 1089,  453,  722,  985,  720,  978,  973,  412,
          721, 1092,  931, 1087,  219,  326,-32766, 1091,  744,  470,
          471,  743,  921,  470,  471, -104, -104, -104,  656,  723,
          475,  476,  678,  723,  475,  476,  470,  471, -103, -103,
         -103,   43,  470,  471,   34,  678,  723,  475,  476,  415,
          339,  678,  723,  475,  476,  470,  471,   30,  958,   33,
        -32766,  719,  412,  410,  678,  723,  475,  476,  326,  343,
          312,  311,  470,  471,  816,-32766,-32766,-32766,  310,  309,
         -104,  656,  723,  475,  476, -112, -112, -112,  296, -114,
         -114, -114,  295, -103,-32766,  287,-32766,-32766,-32766,-32766,
        -32766,-32766,  417,  339,  210,   82,   81,   48,  337,  896,
          658,  826,  827,  828,  825,  824,  823,  818,  560,  984,
          783,  925,  922,  612,  551,  461,  470,  471,  457,  455,
          470,  471,-32766,-32766,-32766,  678,  723,  475,  476,  678,
          723,  475,  476,  897,  450,  389,   25,   24, -120,  470,
          471,-32766, 1057,-32766,-32766,-32766,-32766,-32766,  678,  723,
          475,  476,  470,  471, -119, 1041,  470,  471, 1045,    0,
         1059,  678,  723,  475,  476,  678,  723,  475,  476,  944,
          470,  471,  597,  929,  470,  471,  930,  710,  927,  678,
          723,  475,  476,  678,  723,  475,  476,  470,  471,  928,
          628,  926,  470,  471,  679,    0,  678,  723,  475,  476,
            0,  678,  723,  475,  476,    0,    0,    0,  712,    0,
            0,    0,  919,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,  920
    );

    protected $actionCheck = array(
            1,    2,    3,    4,    5,    6,   30,   31,   32,   33,
           11,   12,   13,   30,   31,   32,   33,   34,   35,   36,
           37,   38,    0,   40,   41,   42,   43,   44,   45,   46,
           47,   48,   49,   50,   51,   30,   31,   32,   33,   34,
           35,   36,   37,   38,   76,   46,   47,   66,   65,   66,
            7,   52,    7,   54,   55,   56,   57,   58,   59,   60,
           61,   62,   63,   64,    8,   66,   67,   68,   69,   70,
            8,    9,   10,   74,   75,   76,   73,   78,  122,   80,
            7,   82,   83,   84,   85,    7,   87,   28,   89,   27,
           91,   29,   30,   94,    8,    9,   10,   98,   99,  100,
          101,    7,  103,  104,    9,   10,  107,  151,  127,  110,
          111,  143,   28,   27,  146,  112,  113,  118,  119,  120,
           76,    1,   27,   66,  121,  122,  123,  124,  129,  130,
          149,  132,  133,  134,  135,  136,  137,  138,    8,    9,
           10,    7,  143,  144,  145,  146,    7,  148,  149,   28,
          151,   66,  153,  154,   34,  156,   76,   27,    7,   29,
           30,   31,   32,   33,   34,   35,   36,   37,   38,   39,
           40,   41,   42,   43,   44,   45,   46,   47,   48,   49,
           50,   51,    7,   53,   30,   31,   32,   33,   34,    7,
           70,  130,  131,  148,  150,   65,   76,    8,    9,   10,
           80,    1,  122,   83,   84,   85,  149,   87,  149,   89,
           86,   91,   12,  152,   94,  128,   27,    7,   98,   99,
          100,  101,  101,  103,  104,  152,  148,  107,    7,  149,
          110,  111,  148,    1,   34,  150,  112,  113,  118,  152,
            7,  155,  148,   78,   12,  121,  122,  123,  124,   76,
           78,   14,   15,   16,   17,   18,   19,   20,   21,   22,
           23,   24,   25,   26,    8,    9,   10,  147,  148,  149,
           70,   76,  152,   78,   79,  102,   76,    8,    9,   10,
           80,  108,   66,   83,   84,   85,  152,   87,   66,   89,
          117,   91,   55,   56,   94,    7,   27,   28,   98,   99,
          100,  101,   70,  103,  104,  140,  141,  107,   76,   77,
          110,  111,   80,    1,   34,   83,   84,   85,  118,   87,
           28,   89,   76,   91,   12,  153,   94,  152,  101,  102,
           98,   99,  100,  101,  152,  103,  104,  146,   27,  107,
          149,  152,  110,  111,   28,    1,   34,  147,  148,  149,
          118,   14,   90,   49,   50,   51,   12,  102,   30,   31,
           32,   28,   78,  108,  127,  149,   28,  112,  113,   65,
           66,  149,   28,  146,  112,  113,  121,  122,  123,  124,
          148,  149,   70,  121,  122,  123,  124,   10,   76,    8,
            9,   10,   80,  101,  148,   83,   84,   85,  143,   87,
            7,   89,   78,   91,   27,  150,   94,   76,   27,  129,
           98,   99,  100,  101,   70,  103,  104,  101,   76,  107,
           76,   14,  110,  111,   80,    1,  102,   83,   84,   85,
          118,   87,  108,   89,  101,   91,   12,  153,   94,  101,
           81,  117,   98,   99,  100,  101,   78,  103,  104,    8,
            9,  107,   30,  122,  110,  111,   76,    1,   34,   81,
          148,  149,  118,   81,   14,    8,    9,   10,   12,   30,
           31,   32,   33,   34,   35,   36,   37,   38,   14,   40,
           41,   42,   43,   66,   27,   14,   29,   30,   31,   32,
           34,  149,  148,  149,   70,    8,    9,   10,  139,   14,
           76,    8,    9,   10,   80,   30,   31,   83,   84,   85,
            1,   87,  149,   89,   27,   91,  153,  139,   94,   28,
           27,  153,   98,   99,  100,  101,   70,  103,  104,  149,
           81,  107,   76,   28,  110,  111,   80,   34,  156,   83,
           84,   85,  118,   87,  127,   89,   28,   91,   65,   66,
           94,   71,   72,    1,   98,   99,  100,  101,   28,  103,
          104,   71,   72,  107,   12,   28,  110,  111,   46,   47,
           48,   65,  148,  149,  118,   46,   47,   48,   53,   70,
           96,   97,  105,  106,   73,   76,  148,  149,  139,   80,
           88,   67,   83,   84,   85,   81,   87,   66,   89,   76,
           91,   78,   66,   94,  148,  149,    1,   98,   99,  100,
          101,   66,  103,  104,  112,  113,  107,   12,   76,  110,
          111,  128,   70,  121,  122,  123,  124,  118,   76,   49,
           50,   51,   80,   53,  111,   83,   84,   85,    1,   87,
           66,   89,   66,   91,   66,   65,   94,  130,  131,   12,
           98,   99,  100,  101,   66,  103,  104,  148,  149,  107,
           76,  152,  110,  111,  148,  149,  143,  148,  149,  146,
          118,   76,   76,   76,  151,   70,  153,   76,   76,   76,
           76,   76,   76,   76,   76,   80,   76,   76,   83,   84,
           85,    1,   87,   78,   89,   78,   91,   78,   78,   94,
          148,  149,   12,   98,   99,  100,  101,   70,  103,  104,
           78,   81,  107,   76,   81,  110,  111,   80,   93,   85,
           83,   84,   85,  118,   87,  101,   89,    1,   91,   93,
           95,   94,   93,   93,   93,   98,   99,  100,  101,   95,
          103,  104,  126,  101,  107,  122,  101,  110,  111,  122,
          128,  108,  139,  148,  149,  118,  109,  128,  127,  147,
           70,  152,  148,  125,  146,   -1,   76,  146,  142,   -1,
           80,  125,   -1,   83,   84,   85,  127,   87,  127,   89,
          127,   91,  154,  129,   94,  148,  149,   -1,   98,   99,
          100,  101,  142,  103,  104,   -1,   70,  107,  142,  142,
          110,  111,   76,  142,  142,  142,   80,  142,  118,   83,
           84,   85,  146,   87,  150,   89,   28,   91,  148,  148,
           94,  148,  148,  148,   98,   99,  100,  101,  148,  103,
          104,  148,  148,  107,   46,   47,  110,  111,  148,  149,
           52,  148,   54,  148,  118,   40,   41,   42,   43,   44,
           45,   46,   47,   48,   66,  148,  148,    0,  148,   46,
           47,  148,   74,   75,  148,   52,   78,   54,  148,  148,
           82,    8,    9,   10,  148,  149,  148,  148,  148,   66,
            8,    9,   10,  148,  148,  148,  148,   74,   75,  148,
           27,   78,   29,   30,   31,   82,  148,  148,  148,   27,
          149,   29,   30,   31,   32,   33,   34,   35,   36,   37,
           38,   39,  149,  149,  149,  149,  149,  149,  130,  149,
          132,  133,  134,  135,  136,  137,  138,  149,  149,  149,
          149,  155,  144,  145,  150,  150,  150,  150,  155,  151,
          150,  153,  150,  130,  150,  132,  133,  134,  135,  136,
          137,  138,  150,  150,  150,  155,  150,  144,  145,  102,
          150,  150,  150,  155,  151,  108,  153,  150,  150,  112,
          113,  150,  152,  112,  113,   95,   96,   97,  121,  122,
          123,  124,  121,  122,  123,  124,  112,  113,   95,   96,
           97,  151,  112,  113,  151,  121,  122,  123,  124,  151,
          143,  121,  122,  123,  124,  112,  113,  151,  155,  151,
          151,  150,  102,  151,  121,  122,  123,  124,  108,  151,
          151,  151,  112,  113,  150,    8,    9,   10,  151,  151,
          150,  121,  122,  123,  124,   71,   72,   73,  151,   71,
           72,   73,  151,  150,   27,  151,   29,   30,   31,   32,
           33,   34,  102,  143,  151,  151,  151,  151,  108,  152,
          150,  111,  112,  113,  114,  115,  116,  117,  152,  152,
          152,  152,  152,  152,  152,  152,  112,  113,  152,  152,
          112,  113,    8,    9,   10,  121,  122,  123,  124,  121,
          122,  123,  124,  152,  152,  152,  152,  152,  152,  112,
          113,   27,  155,   29,   30,   31,   32,   33,  121,  122,
          123,  124,  112,  113,  152,  155,  112,  113,  155,   -1,
          155,  121,  122,  123,  124,  121,  122,  123,  124,  155,
          112,  113,   92,  155,  112,  113,  155,  150,  155,  121,
          122,  123,  124,  121,  122,  123,  124,  112,  113,  155,
          150,  155,  112,  113,  150,   -1,  121,  122,  123,  124,
           -1,  121,  122,  123,  124,   -1,   -1,   -1,  150,   -1,
           -1,   -1,  150,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,  150
    );

    protected $actionBase = array(
            0,  880,  893,  964,  857,  255,  910,  968, 1004, 1000,
          124, 1040,    3,  262, 1018,  861, 1022,  502, 1035,  987,
          874,  338,  292,  121,  333,  121,  316,  645,  645,  645,
          120,  200,  456,  456,  509,  456,  552,  605,  637,  232,
          344,  424,  312,  690,  690,  690,  690,  726,  726,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,  690,
          690,  690,  690,  690,  690,  690,  690,  690,  690,   84,
          748,  629,  622,  741,  738,  736,  735,  820,  640,  941,
          802,  794,  537,  792,  790,  787,  786,  785,  803,  784,
          776,  664,  130,  130,  130,  130,  130,  130,  130,  130,
          130,  130,  130,   56,  493,  189,  269,   86,  441,  487,
          487,  487,  487,  487,  487,  256,  256,  256,  256,  256,
          256,  256,  256,  256,  256,  256,  256,  256,  256,  256,
          256,  256,   95,  381,  381,  381,  377,  788,  311,  813,
          813,  813,  813,  813,  813,  813,  813,  813,  813,  813,
          813,  813,  813,  813,  813,  813,  813,  813,  813,  813,
          813,  813,  813,  813,  813,  813,  813,  813,  813,  813,
          813,  813,  813,  813,  813,  813,  813,  813,  813,  813,
          813,   62,  -17,  -17,  863,  422,  457,  475, 1074,  328,
         1017,  872,  872,  872,  872,  872,  -24,  154,    5,    5,
            5,    5,  237,  805,  805,  805,  805,  439,  439,  439,
          439,  804,  810,  806,  812,  280,  280,  654,  654,  524,
          780,  529,  529,  522,  522,  523,  523,  523,  523,  523,
          523,  523,  523,  523,  523,  -44,  324,  173,  859,   61,
           61,   61,   61,  517,  517,  378,  359,  382,   80,  580,
          580,  580,  304,  304,  304,   44,  227,  630,  380,  380,
          380,  514,  613,  633,  342,  -32,  -32,  -32,  -32,  191,
          779,  -32,  -32,  -32,   57,  165,  165,  195,  363,  644,
          821,  635,  818,  438,  661,  -19,  666,  666,  666,  172,
          642,  490,  480,  477,  656,   59,  172,   84,  331,  519,
          216,  525,  737,  584,  684,  710,   78,   94,  417,  516,
          222,  284,   73,  708,  693,  916,  907,  182,   85,  649,
          525,  525,  525,  175,  449,  222,   87,  483,  483,  483,
          483,  483,  483,  483,  483,  680,   45,  134,  720,  246,
          503,  843,  597,  856,  856,  595,  607,  597,  632,  503,
          906,  906,  906,  906,  503,  607,  856,  856,  503,  524,
          856,  210,  503,  646,  607,  638,  638,  906,  728,  721,
          597,  619,  616,  856,  856,  856,  616,  595,  503,  906,
          643,  612,  221,  856,  906,  505,  505,  643,  503,  505,
          632,  505,   22,  518,  576,  840,  905,  848,  601,  778,
          627,  623,  891,  887,  904,  596,  604,  894,  858,  618,
          716,  602,  471,  536,  578,  531,  588,  650,  574,  653,
          642,  621,  506,  506,  506,  651,  665,  651,  506,  506,
          506,  506,  506,  506,  506,  506,  996,  662,  626,  631,
          634,  713,  337,  618,  641,  407,  770,  618,  920,  943,
          628,  603,  878,  922,  651,  994,  749,   43,  450,  877,
          625,  606,  651,  870,  651,  768,  651,  919,  608,  811,
          618,  506,  918,  983,  981,  978,  974,  965,  963,  960,
          947,  545,  853,  683,  942,  151,  903,  656,  663,  610,
          675,  233,  808,  651,  651,  767,  779,  651,  766,  707,
          750,  609,  671,  927,  800,  613,  926,  651,  624,  783,
          233,  491,  511,  946,  674,  862,  615,  917,  868,  765,
          464,  817,  530,  695,  945,  944,  962,  730,  764,  781,
          485,  542,  617,  620,  751,  869,  729,  921,  636,  657,
          647,  639,  763,  611,  923,  673,  614,  670,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,  130,  130,  130,  130,  130,
          130,  130,  130,  130,  130,  130,  130,  130,  130,  130,
          130,  130,  130,  130,  130,  130,  130,  130,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,  130,
          -17,  -17,  -17,  -17,  130,  -17,  -17,  -17,  -17,  -17,
          -17,  130,  130,  130,  130,  130,  130,  130,  130,  130,
          130,  130,  130,  130,  130,  130,  130,  130,  -17,  130,
          130,  130,  -17,  523,  -17,  523,  523,  523,  523,  523,
          523,  523,  523,  523,  523,  523,  523,  523,  523,  523,
          523,  523,  523,  523,  523,  523,  523,  523,  523,  523,
          523,  523,  523,  523,  523,  523,  523,  523,  523,  523,
          523,  523,  523,  523,  523,  523,  523,  130,    0,    0,
          130,  -17,  130,  -17,  130,  -17,  130,  130,  130,  130,
          130,  130,  -17,  -17,  -17,  -17,  -17,  -17,    0,  580,
          580,  580,  580,  -17,  -17,  -17,  -17,  950,  950,  950,
          950,  523,  523,  523,  523,  523,  523,  580,  580,  304,
          304,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,  523,  950,  950,  523,  -32,  -32,  -32,  -32,  -32,
          -32,  165,  165,  165,  284,    0,    0,    0,    0,    0,
            0,  -32,  607,  165,  368,  368,  368,  165,  165,  165,
          284,    0,    0,    0,    0,  607,  368,    0,    0,    0,
          856,    0,    0,    0,  368,  484,  484,  484,  484,  233,
          222,    0,  607,  607,  607,    0,  619,    0,    0,    0,
          856,    0,    0,    0,    0,    0,    0,  506,   43,  878,
          139,  288,    0,    0,    0,    0,    0,    0,    0,  288,
          288,  393,  393,    0,    0,  545,  506,  506,  506,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,  139,    0,    0,  233
    );

    protected $actionDefault = array(
            3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  428,  428,32767,  385,32767,32767,32767,32767,
        32767,32767,32767,  189,  189,  189,32767,32767,32767,  417,
          417,  417,  417,  417,  417,  417,  417,  417,  417,  417,
        32767,32767,32767,32767,32767,  271,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,  277,  433,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  252,  253,  255,  256,  188,  418,  140,  278,
          432,  187,  142,  216,  389,32767,32767,32767,  218,   26,
          151,   96,  388,  186,  127,  270,  272,  217,  193,  198,
          199,  200,  201,  202,  203,  204,  205,  206,  207,  208,
          209,  192,  343,  249,  248,  247,  345,32767,  344,  382,
          382,  385,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,  214,  371,  370,  215,  341,  219,  342,  221,  346,
          220,  237,  238,  235,  236,  239,  348,  347,  364,  365,
          362,  363,  191,  240,  241,  242,  243,  366,  367,  368,
          369,  173,  173,  173,  173,32767,32767,  427,  427,32767,
        32767,  228,  229,  355,  356,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,  174,32767,32767,  131,
          131,  131,  131,  131,32767,32767,32767,32767,32767,  223,
          224,  222,  350,  351,  349,32767,32767,  317,32767,32767,
        32767,32767,32767,  319,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,  390,  318,32767,32767,32767,32767,
        32767,32767,32767,32767,  403,  306,32767,32767,32767,32767,
          299,  115,  117,   65,  374,32767,32767,32767,32767,32767,
          408,  233,32767,32767,32767,32767,32767,32767,  440,32767,
          403,32767,32767,32767,32767,32767,32767,32767,32767,  246,
          225,  226,  227,32767,32767,  407,  401,  358,  359,  360,
          361,  352,  353,  354,  357,32767,32767,32767,32767,32767,
           69,  314,32767,  320,  320,32767,32767,32767,32767,   69,
        32767,32767,32767,32767,   69,32767,  406,  405,   69,32767,
          300,  384,   69,   82,32767,   80,   80,32767,  101,  101,
        32767,32767,   84,  380,  396,32767,   84,32767,   69,32767,
          288,   71,  384,32767,32767,  133,  133,  288,   69,  133,
        32767,  133,32767,    4,  324,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,  301,
        32767,32767,32767,  267,  268,  377,  392,32767,  393,32767,
          299,32767,  231,  232,  234,  211,32767,  213,  257,  258,
          259,  260,  261,  262,  263,  265,32767,32767,  304,  307,
        32767,32767,32767,    6,   20,  150,32767,  302,32767,  196,
        32767,32767,32767,32767,  435,32767,32767,  190,32767,32767,
           22,32767,  146,32767,   67,32767,  425,32767,32767,  401,
          303,  230,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,  402,32767,32767,32767,  122,32767,  337,32767,32767,
        32767,   83,32767,  194,  141,32767,32767,  434,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,   68,32767,32767,
           85,32767,32767,  401,32767,32767,32767,32767,32767,32767,
          185,32767,32767,32767,32767,32767,  401,32767,32767,32767,
          126,32767,32767,32767,32767,32767,32767,32767,    4,32767,
          167,32767,32767,32767,32767,32767,32767,32767,   28,   28,
            3,   28,  109,   28,  153,    3,  101,  101,   62,  153,
           28,  153,  153,   28,   28,   28,   28,   28,  160,   28,
           28,   28,   28,   28,   28,   28
    );

    protected $goto = array(
          168,  168,  142,  142,  147,  142,  143,  144,  145,  150,
          152,  188,  170,  166,  166,  166,  166,  147,  147,  167,
          167,  167,  167,  167,  167,  167,  167,  167,  167,  167,
          162,  163,  164,  165,  185,  141,  186,  492,  493,  371,
          494,  498,  499,  500,  501,  502,  503,  504,  505,  843,
          146,  148,  149,  151,  173,  178,  187,  203,  251,  254,
          256,  258,  260,  261,  262,  263,  264,  265,  273,  274,
          275,  276,  291,  292,  319,  320,  321,  390,  391,  392,
          541,  189,  190,  191,  192,  193,  194,  195,  196,  197,
          198,  199,  200,  201,  153,  154,  155,  169,  156,  171,
          157,  204,  172,  158,  159,  160,  205,  161,  139,  557,
          700,  557,  557,  557,  557,  557,  557,  557,  557,  557,
          557,  557,  557,  557,  557,  557,  557,  557,  557,  557,
          557,  557,  557,  557,  557,  557,  557,  557,  557,  557,
          557,  557,  557,  557,  557,  557,  557,  557,  557,  557,
          557,  557,  557,  496,  496,  496,  496,  496,  496,  379,
          653,  653,  653,  496,  496,  496,  496,  496,  496,  496,
          496,  496,  496,  507,  570,  594,  507,  753,  738,  736,
          734,  736,  622,  510,  762,  757,  785,  430,  430,  430,
          430,  430,  430,  767,  767, 1072, 1072,  430,  430,  430,
          430,  430,  430,  430,  430,  430,  430,  946,  519,  350,
          946,  774,  774,  774,  774,  774,  774,  543,  544,  545,
          546,  547,  548,  549,  550,  552,  579,  609,  599,  822,
          409,  604,  282,  369,  283,  284,  530,  732,  732,  732,
          732,  537,    5,  727,  733,  603,  558,    6,  558,  558,
          558,  558,  558,  558,  558,  558,  558,  558,  558,  558,
          558,  558,  558,  558,  558,  558,  558,  558,  558,  558,
          558,  558,  558,  558,  558,  558,  558,  558,  558,  558,
          558,  558,  558,  558,  558,  558,  558,  558,  558,  558,
          981, 1076,  981,  981,  981,  981,  981,  981,  981,  981,
          981,  981,  981,  981,  981,  981,  981,  981,  981,  981,
          981,  981,  981,  981,  981,  981,  981,  981,  981,  981,
          981,  981,  981,  981,  981,  981,  981,  981,  981,  981,
          981,  981,  981,  981,  654,  654,  654,  655,  655,  655,
          573,  576,  614,  176,  508,  957,  956,  508,  179,  180,
          181,  397,  398,  399,  400,  175,  202,  206,  208,  255,
          257,  259,  266,  267,  268,  269,  270,  271,  277,  278,
          279,  280,  293,  294,  322,  323,  324,  401,  402,  403,
          404,  177,  182,  252,  253,  183,  184,    9,  333,    3,
          372,   10,  317,  580,  353,  408,  351,  352,   11,  587,
         1044,    1,   12,   13,    2,   14, 1032,    7,   15,   16,
           17,   18,   19,   20,  396,  596,  536,  536,  563,  532,
          939,  513,  383,  384,  534,  534,  495,  497,  524,  539,
          564,  567,  577,  583,  513,  962, 1069,  595,  386, 1051,
         1082, 1082, 1051,  890,  900,   26,   21,  365,  664,  633,
          841,  513,  513,  513,  771,  509, 1085, 1082,  509,  780,
          789,  553, 1067, 1067, 1067,  380,  380,  380,  373, 1085,
         1085,  542,  522,   29, 1050,  518,  533,  380,  592,  982,
          405, 1052,  942,  943, 1052,  395,  939,  932,  518,  518,
          937,  446,  451,  670,  568,  794,  741, 1029,  459,  940,
         1043,  940,  601,  830,    0,    0,    0,    0,    0,  941,
            0,  513,    0,    0,    0,    0,    0,    0,    0,    0,
          517,    0,    0,    0,    0,    0,    0,    0,    0,  540,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          523
    );

    protected $gotoCheck = array(
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   39,
           32,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   95,   95,   95,   95,   95,   95,    5,
            6,    6,    6,   95,   95,   95,   95,   95,   95,   95,
           95,   95,   95,   95,   22,   22,   95,    6,    6,    6,
            6,    6,    6,    6,    6,    6,   63,   39,   39,   39,
           39,   39,   39,   56,   56,   56,   56,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   79,   51,
           39,   39,   39,   39,   39,   39,   39,   88,   88,   88,
           88,   88,   88,   88,   88,   88,   88,   39,   43,   76,
           76,   43,   47,   43,   47,   47,    5,   39,   39,   39,
           39,   87,    2,   39,   39,   39,   98,    2,   98,   98,
           98,   98,   98,   98,   98,   98,   98,   98,   98,   98,
           98,   98,   98,   98,   98,   98,   98,   98,   98,   98,
           98,   98,   98,   98,   98,   98,   98,   98,   98,   98,
           98,   98,   98,   98,   98,   98,   98,   98,   98,   98,
          105,  119,  105,  105,  105,  105,  105,  105,  105,  105,
          105,  105,  105,  105,  105,  105,  105,  105,  105,  105,
          105,  105,  105,  105,  105,  105,  105,  105,  105,  105,
          105,  105,  105,  105,  105,  105,  105,  105,  105,  105,
          105,  105,  105,  105,    7,    7,    7,    8,    8,    8,
           42,   42,   42,   13,   98,  103,  103,   98,   13,   13,
           13,   13,   13,   13,   13,   13,   13,   13,   13,   13,
           13,   13,   13,   13,   13,   13,   13,   13,   13,   13,
           13,   13,   13,   13,   13,   13,   13,   13,   13,   13,
           13,   13,   13,   13,   13,   13,   13,   14,  104,   14,
           29,   14,  104,   49,   49,   49,   51,   51,   14,  107,
           61,   14,   14,   14,   14,   14,  111,   14,   14,   14,
           14,   14,   14,   14,   33,   33,   33,   33,   33,   33,
           61,    4,    9,    9,   33,   33,   33,   33,   33,   33,
           33,   33,   33,   33,    4,   16,  117,   31,   30,   97,
          120,  120,   97,   80,   16,   16,   16,   16,   11,   53,
           79,    4,    4,    4,   58,  101,  120,  120,  101,   60,
           64,   16,   97,   97,   97,  102,  102,  102,   40,  120,
          120,   26,   40,   16,   97,   26,   40,  102,   16,  106,
           10,   96,   61,   61,   96,  102,   61,   91,   26,   26,
           93,   45,   40,   12,   46,   65,   50,  110,   86,   61,
           61,   61,   40,   78,   -1,   -1,   -1,   -1,   -1,   61,
           -1,    4,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
            4,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,    4,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           79
    );

    protected $gotoBase = array(
            0,    0, -378,    0,   95, -180,  156,  330,  333,   66,
           63,   90,   53,  136, -232,    0,   24,    0,    0,    0,
            0,    0,  130,    0,    0,  -30,  441,    0,    0,  344,
          142,  151,   85,  129,    0,    0,    0,    0,    0,  -98,
           44,    0,   30, -228,    0,   55,   48, -397,    0,   57,
           49, -230,    0,   82,    0,    0,  -92,    0,  141,    0,
          145,   56,    0,  155,   94,   54,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,  -77,    0,   43,  161,
          135,    0,    0,    0,    0,    0,   41,  208,  167,    0,
            0,   73,    0,   71,    0, -132,  176,  134,   39,    0,
            0,  150,  137,   16,   61,   83,  111,  189,    0,    0,
           45,  195,    0,    0,    0,    0,    0,  148,    0,  256,
          124,    0
    );

    protected $gotoDefault = array(
        -32768,  462,    4,  648,  479,  514,  675,  676,  677,  375,
          374,  665,  671,  174,    8,  673,  891,  361,  680,  362,
          582,  682,  526,  684,  685,  140,  480,  376,  377,  527,
          385,  571,  699,  272,  382,  701,  363,  703,  709,  364,
          602,  586,  554,  598,  481,  442,  565,  281,  535,  561,
          740,  349,  748,  636,  756,  759,  482,  555,  770,  448,
          778,  967,  393,  784,  790,  795,  798,  418,  406,  578,
          802,  803,  325,  807,  610,  611,  821,  306,  829,  842,
          414,  910,  912,  483,  484,  520,  456,  506,  525,  485,
          933,  436,  407,  936,  486,  487,  426,  427,  954,  951,
          355, 1037,  354,  444,  316, 1022, 1021,  574,  986,  452,
         1075, 1033,  344,  488,  489,  370,  387, 1070,  431, 1077,
         1084,  562
    );

    protected $ruleToNonTerminal = array(
            0,    1,    2,    2,    4,    4,    5,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    9,    9,
           11,   11,   11,   11,   10,   10,   12,   14,   14,   15,
           15,   15,   15,    6,    6,    6,    6,    6,    6,    6,
            6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
            6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
            6,    6,   36,   36,   38,   37,   37,   30,   30,   40,
           40,   41,   41,    7,    8,    8,    8,   44,   44,   44,
           45,   45,   48,   48,   46,   46,   49,   49,   23,   23,
           32,   32,   35,   35,   34,   34,   50,   24,   24,   24,
           24,   51,   51,   52,   52,   53,   53,   21,   21,   17,
           17,   54,   19,   19,   55,   18,   18,   20,   20,   31,
           31,   31,   42,   42,   57,   57,   58,   58,   60,   60,
           60,   59,   59,   43,   43,   61,   61,   61,   62,   62,
           63,   63,   63,   27,   27,   64,   64,   64,   28,   28,
           65,   65,   47,   47,   66,   66,   66,   66,   71,   71,
           72,   72,   73,   73,   73,   73,   74,   75,   75,   70,
           70,   67,   67,   69,   69,   77,   77,   76,   76,   76,
           76,   76,   76,   68,   68,   78,   78,   29,   29,   22,
           22,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   25,   25,   25,   25,   25,
           25,   25,   25,   25,   25,   16,   16,   26,   26,   83,
           83,   84,   84,   84,   84,   90,   79,   79,   86,   86,
           92,   92,   93,   94,   94,   94,   94,   94,   94,   98,
           98,   39,   39,   39,   80,   80,   99,   99,   95,   95,
          100,  100,  100,  100,   81,   81,   81,   85,   85,   85,
           91,   91,  105,  105,  105,  105,  105,  105,  105,  105,
          105,  105,  105,  105,  105,   13,   13,   13,   13,   13,
           13,  108,  108,  108,  108,  108,  108,  108,  108,  108,
          108,  108,  108,  108,  108,  108,  108,  108,  108,  108,
          108,  108,  108,  108,  108,  108,  108,  108,  108,  108,
          108,  108,  108,  108,   89,   89,   82,   82,   82,   82,
          106,  106,  107,  107,  110,  110,  109,  109,  111,  111,
           33,   33,   33,   33,  113,  113,  112,  112,  112,  112,
          112,  114,  114,   97,   97,  101,  101,   96,   96,  115,
          115,  115,  115,  102,  102,  102,  102,   88,   88,  103,
          103,  103,   56,  116,  116,  117,  117,  117,   87,   87,
          118,  118,  119,  119,  119,  119,  104,  104,  104,  104,
          120,  120,  120,  120,  120,  120,  120,  121,  121,  121
    );

    protected $ruleToLength = array(
            1,    1,    2,    0,    1,    3,    1,    1,    1,    1,
            1,    3,    5,    4,    3,    4,    4,    3,    3,    1,
            1,    3,    2,    4,    3,    1,    3,    2,    0,    1,
            1,    1,    1,    3,    5,    8,    3,    5,    9,    3,
            2,    3,    2,    3,    2,    3,    2,    3,    3,    3,
            1,    2,    5,    7,    9,    5,    1,    6,    3,    3,
            2,    1,    0,    2,    8,    0,    4,    1,    3,    0,
            1,    0,    1,   10,    7,    6,    5,    1,    2,    2,
            0,    2,    0,    2,    0,    2,    1,    3,    1,    4,
            1,    4,    1,    4,    1,    3,    3,    3,    4,    4,
            5,    0,    2,    4,    3,    1,    1,    1,    4,    0,
            2,    3,    0,    2,    4,    0,    2,    0,    3,    1,
            2,    1,    1,    0,    1,    3,    4,    6,    1,    1,
            1,    0,    1,    0,    2,    2,    3,    3,    1,    3,
            1,    2,    2,    3,    1,    1,    2,    4,    3,    1,
            1,    3,    2,    0,    3,    3,    9,    3,    1,    3,
            0,    2,    4,    5,    4,    4,    3,    1,    1,    1,
            3,    1,    1,    0,    1,    1,    2,    1,    1,    1,
            1,    1,    1,    1,    3,    1,    3,    3,    1,    0,
            1,    1,    3,    3,    4,    4,    1,    2,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            2,    2,    2,    2,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            3,    2,    2,    2,    2,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    1,    3,    5,    4,    3,
            4,    4,    2,    2,    2,    2,    2,    2,    2,    2,
            2,    2,    2,    2,    2,    2,    1,    1,    1,    3,
            2,    1,    2,   10,   11,    3,    3,    2,    4,    4,
            3,    4,    4,    4,    4,    7,    3,    2,    0,    4,
            1,    3,    2,    2,    4,    6,    2,    2,    4,    1,
            1,    1,    2,    3,    1,    1,    1,    1,    1,    1,
            3,    3,    4,    4,    0,    2,    1,    0,    1,    1,
            0,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    3,    2,    1,    3,    1,    4,    3,
            1,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    2,    2,
            2,    2,    3,    3,    3,    3,    3,    3,    3,    3,
            5,    4,    4,    3,    1,    3,    1,    1,    3,    3,
            1,    1,    0,    2,    0,    1,    3,    1,    3,    1,
            1,    1,    1,    1,    6,    4,    3,    4,    2,    4,
            4,    1,    3,    1,    2,    1,    1,    4,    1,    3,
            6,    4,    4,    4,    4,    1,    4,    0,    1,    1,
            3,    1,    4,    3,    1,    1,    1,    0,    0,    2,
            3,    1,    3,    1,    4,    2,    2,    2,    1,    2,
            1,    4,    3,    3,    3,    6,    3,    1,    1,    1
    );

    protected function reduceRule0() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule1() {
         $this->semValue = $this->handleNamespaces($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule2() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule3() {
         $this->semValue = array();
    }

    protected function reduceRule4() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule5() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule6() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule7() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule8() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule9() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule10() {
         $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule11() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule12() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule13() {
         $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule14() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule15() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], Stmt\Use_::TYPE_FUNCTION, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule16() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], Stmt\Use_::TYPE_CONSTANT, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule17() {
         $this->semValue = new Stmt\Const_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule18() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule19() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule20() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule21() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule22() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule23() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule24() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule25() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule26() {
         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule27() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule28() {
         $this->semValue = array();
    }

    protected function reduceRule29() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule30() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule31() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule32() {
         throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule33() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule34() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(5-2)], array('stmts' => is_array($this->semStack[$this->stackPos-(5-3)]) ? $this->semStack[$this->stackPos-(5-3)] : array($this->semStack[$this->stackPos-(5-3)]), 'elseifs' => $this->semStack[$this->stackPos-(5-4)], 'else' => $this->semStack[$this->stackPos-(5-5)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule35() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(8-2)], array('stmts' => $this->semStack[$this->stackPos-(8-4)], 'elseifs' => $this->semStack[$this->stackPos-(8-5)], 'else' => $this->semStack[$this->stackPos-(8-6)]), $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule36() {
         $this->semValue = new Stmt\While_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule37() {
         $this->semValue = new Stmt\Do_($this->semStack[$this->stackPos-(5-4)], is_array($this->semStack[$this->stackPos-(5-2)]) ? $this->semStack[$this->stackPos-(5-2)] : array($this->semStack[$this->stackPos-(5-2)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule38() {
         $this->semValue = new Stmt\For_(array('init' => $this->semStack[$this->stackPos-(9-3)], 'cond' => $this->semStack[$this->stackPos-(9-5)], 'loop' => $this->semStack[$this->stackPos-(9-7)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]), $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule39() {
         $this->semValue = new Stmt\Switch_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule40() {
         $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule41() {
         $this->semValue = new Stmt\Break_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule42() {
         $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule43() {
         $this->semValue = new Stmt\Continue_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule44() {
         $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule45() {
         $this->semValue = new Stmt\Return_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule46() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule47() {
         $this->semValue = new Stmt\Global_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule48() {
         $this->semValue = new Stmt\Static_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule49() {
         $this->semValue = new Stmt\Echo_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule50() {
         $this->semValue = new Stmt\InlineHTML($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule51() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule52() {
         $this->semValue = new Stmt\Unset_($this->semStack[$this->stackPos-(5-3)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule53() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(7-3)], $this->semStack[$this->stackPos-(7-5)][0], array('keyVar' => null, 'byRef' => $this->semStack[$this->stackPos-(7-5)][1], 'stmts' => $this->semStack[$this->stackPos-(7-7)]), $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule54() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(9-3)], $this->semStack[$this->stackPos-(9-7)][0], array('keyVar' => $this->semStack[$this->stackPos-(9-5)], 'byRef' => $this->semStack[$this->stackPos-(9-7)][1], 'stmts' => $this->semStack[$this->stackPos-(9-9)]), $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule55() {
         $this->semValue = new Stmt\Declare_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule56() {
         $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule57() {
         $this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule58() {
         $this->semValue = new Stmt\Throw_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule59() {
         $this->semValue = new Stmt\Goto_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule60() {
         $this->semValue = new Stmt\Label($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule61() {
         $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule62() {
         $this->semValue = array();
    }

    protected function reduceRule63() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule64() {
         $this->semValue = new Stmt\Catch_($this->semStack[$this->stackPos-(8-3)], substr($this->semStack[$this->stackPos-(8-4)], 1), $this->semStack[$this->stackPos-(8-7)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule65() {
         $this->semValue = null;
    }

    protected function reduceRule66() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule67() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule68() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule69() {
         $this->semValue = false;
    }

    protected function reduceRule70() {
         $this->semValue = true;
    }

    protected function reduceRule71() {
         $this->semValue = false;
    }

    protected function reduceRule72() {
         $this->semValue = true;
    }

    protected function reduceRule73() {
         $this->semValue = new Stmt\Function_($this->semStack[$this->stackPos-(10-3)], array('byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-5)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]), $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule74() {
         $this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], array('type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]), $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule75() {
         $this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], array('extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]), $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule76() {
         $this->semValue = new Stmt\Trait_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule77() {
         $this->semValue = 0;
    }

    protected function reduceRule78() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule79() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule80() {
         $this->semValue = null;
    }

    protected function reduceRule81() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule82() {
         $this->semValue = array();
    }

    protected function reduceRule83() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule84() {
         $this->semValue = array();
    }

    protected function reduceRule85() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule86() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule87() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule88() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule89() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule90() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule91() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule92() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule93() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule94() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule95() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule96() {
         $this->semValue = new Stmt\DeclareDeclare($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule97() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule98() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule99() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule100() {
         $this->semValue = $this->semStack[$this->stackPos-(5-3)];
    }

    protected function reduceRule101() {
         $this->semValue = array();
    }

    protected function reduceRule102() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule103() {
         $this->semValue = new Stmt\Case_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule104() {
         $this->semValue = new Stmt\Case_(null, $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule105() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule106() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule107() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule108() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule109() {
         $this->semValue = array();
    }

    protected function reduceRule110() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule111() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(3-2)], is_array($this->semStack[$this->stackPos-(3-3)]) ? $this->semStack[$this->stackPos-(3-3)] : array($this->semStack[$this->stackPos-(3-3)]), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule112() {
         $this->semValue = array();
    }

    protected function reduceRule113() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule114() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule115() {
         $this->semValue = null;
    }

    protected function reduceRule116() {
         $this->semValue = new Stmt\Else_(is_array($this->semStack[$this->stackPos-(2-2)]) ? $this->semStack[$this->stackPos-(2-2)] : array($this->semStack[$this->stackPos-(2-2)]), $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule117() {
         $this->semValue = null;
    }

    protected function reduceRule118() {
         $this->semValue = new Stmt\Else_($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule119() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule120() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-2)], true);
    }

    protected function reduceRule121() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule122() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule123() {
         $this->semValue = array();
    }

    protected function reduceRule124() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule125() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule126() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule127() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule128() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule129() {
         $this->semValue = 'array';
    }

    protected function reduceRule130() {
         $this->semValue = 'callable';
    }

    protected function reduceRule131() {
         $this->semValue = null;
    }

    protected function reduceRule132() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule133() {
         $this->semValue = null;
    }

    protected function reduceRule134() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule135() {
         $this->semValue = array();
    }

    protected function reduceRule136() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule137() {
         $this->semValue = array(new Node\Arg($this->semStack[$this->stackPos-(3-2)], false, false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes));
    }

    protected function reduceRule138() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule139() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule140() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(1-1)], false, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule141() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], true, false, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule142() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], false, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule143() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule144() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule145() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule146() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule147() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule148() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule149() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule150() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule151() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule152() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule153() {
         $this->semValue = array();
    }

    protected function reduceRule154() {
         $this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule155() {
         $this->semValue = new Stmt\ClassConst($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule156() {
         $this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], array('type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]), $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule157() {
         $this->semValue = new Stmt\TraitUse($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule158() {
         $this->semValue = array();
    }

    protected function reduceRule159() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule160() {
         $this->semValue = array();
    }

    protected function reduceRule161() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule162() {
         $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule163() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(5-1)][0], $this->semStack[$this->stackPos-(5-1)][1], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule164() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], null, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule165() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule166() {
         $this->semValue = array($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)]);
    }

    protected function reduceRule167() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule168() {
         $this->semValue = array(null, $this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule169() {
         $this->semValue = null;
    }

    protected function reduceRule170() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule171() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule172() {
         $this->semValue = 0;
    }

    protected function reduceRule173() {
         $this->semValue = 0;
    }

    protected function reduceRule174() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule175() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule176() {
         Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule177() {
         $this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
    }

    protected function reduceRule178() {
         $this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
    }

    protected function reduceRule179() {
         $this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
    }

    protected function reduceRule180() {
         $this->semValue = Stmt\Class_::MODIFIER_STATIC;
    }

    protected function reduceRule181() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule182() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule183() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule184() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule185() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule186() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule187() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule188() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule189() {
         $this->semValue = array();
    }

    protected function reduceRule190() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule191() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule192() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule193() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule194() {
         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule195() {
         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule196() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule197() {
         $this->semValue = new Expr\Clone_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule198() {
         $this->semValue = new Expr\AssignOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule199() {
         $this->semValue = new Expr\AssignOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule200() {
         $this->semValue = new Expr\AssignOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule201() {
         $this->semValue = new Expr\AssignOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule202() {
         $this->semValue = new Expr\AssignOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule203() {
         $this->semValue = new Expr\AssignOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule204() {
         $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule205() {
         $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule206() {
         $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule207() {
         $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule208() {
         $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule209() {
         $this->semValue = new Expr\AssignOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule210() {
         $this->semValue = new Expr\PostInc($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule211() {
         $this->semValue = new Expr\PreInc($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule212() {
         $this->semValue = new Expr\PostDec($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule213() {
         $this->semValue = new Expr\PreDec($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule214() {
         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule215() {
         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule216() {
         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule217() {
         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule218() {
         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule219() {
         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule220() {
         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule221() {
         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule222() {
         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule223() {
         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule224() {
         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule225() {
         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule226() {
         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule227() {
         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule228() {
         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule229() {
         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule230() {
         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule231() {
         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule232() {
         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule233() {
         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule234() {
         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule235() {
         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule236() {
         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule237() {
         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule238() {
         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule239() {
         $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule240() {
         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule241() {
         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule242() {
         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule243() {
         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule244() {
         $this->semValue = new Expr\Instanceof_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule245() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule246() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule247() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule248() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule249() {
         $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule250() {
         $this->semValue = new Expr\Isset_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule251() {
         $this->semValue = new Expr\Empty_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule252() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule253() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule254() {
         $this->semValue = new Expr\Eval_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule255() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule256() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule257() {
         $this->semValue = new Expr\Cast\Int_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule258() {
         $this->semValue = new Expr\Cast\Double($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule259() {
         $this->semValue = new Expr\Cast\String_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule260() {
         $this->semValue = new Expr\Cast\Array_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule261() {
         $this->semValue = new Expr\Cast\Object_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule262() {
         $this->semValue = new Expr\Cast\Bool_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule263() {
         $this->semValue = new Expr\Cast\Unset_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule264() {
         $this->semValue = new Expr\Exit_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule265() {
         $this->semValue = new Expr\ErrorSuppress($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule266() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule267() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule268() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule269() {
         $this->semValue = new Expr\ShellExec($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule270() {
         $this->semValue = new Expr\Print_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule271() {
         $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule272() {
         $this->semValue = new Expr\YieldFrom($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule273() {
         $this->semValue = new Expr\Closure(array('static' => false, 'byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-4)], 'uses' => $this->semStack[$this->stackPos-(10-6)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]), $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule274() {
         $this->semValue = new Expr\Closure(array('static' => true, 'byRef' => $this->semStack[$this->stackPos-(11-3)], 'params' => $this->semStack[$this->stackPos-(11-5)], 'uses' => $this->semStack[$this->stackPos-(11-7)], 'returnType' => $this->semStack[$this->stackPos-(11-8)], 'stmts' => $this->semStack[$this->stackPos-(11-10)]), $this->startAttributeStack[$this->stackPos-(11-1)] + $this->endAttributes);
    }

    protected function reduceRule275() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule276() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule277() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule278() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule279() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule280() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule281() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule282() {
         $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(4-1)]), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule283() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule284() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule285() {
         $this->semValue = array(new Stmt\Class_(null, array('type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]), $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
    }

    protected function reduceRule286() {
         $this->semValue = new Expr\New_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule287() {
         list($class, $ctorArgs) = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule288() {
         $this->semValue = array();
    }

    protected function reduceRule289() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule290() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule291() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule292() {
         $this->semValue = new Expr\ClosureUse(substr($this->semStack[$this->stackPos-(2-2)], 1), $this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule293() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule294() {
         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule295() {
         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-4)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule296() {

            if ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\StaticPropertyFetch) {
                $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(2-1)]->class, new Expr\Variable($this->semStack[$this->stackPos-(2-1)]->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
            } elseif ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\ArrayDimFetch) {
                $tmp = $this->semStack[$this->stackPos-(2-1)];
                while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
                    $tmp = $tmp->var;
                }

                $this->semValue = new Expr\StaticCall($tmp->var->class, $this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
                $tmp->var = new Expr\Variable($tmp->var->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
            } else {
                throw new \Exception;
            }

    }

    protected function reduceRule297() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule298() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule299() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule300() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule301() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule302() {
         $this->semValue = new Name\FullyQualified($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule303() {
         $this->semValue = new Name\Relative($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule304() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule305() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule306() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule307() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule308() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule309() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule310() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule311() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule312() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule313() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule314() {
         $this->semValue = null;
    }

    protected function reduceRule315() {
         $this->semValue = null;
    }

    protected function reduceRule316() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule317() {
         $this->semValue = array();
    }

    protected function reduceRule318() {
         $this->semValue = array(Scalar\String_::parseEscapeSequences($this->semStack[$this->stackPos-(1-1)], '`'));
    }

    protected function reduceRule319() {
         foreach ($this->semStack[$this->stackPos-(1-1)] as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, '`'); } }; $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule320() {
         $this->semValue = array();
    }

    protected function reduceRule321() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule322() {
         $this->semValue = new Scalar\LNumber(Scalar\LNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule323() {
         $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule324() {
         $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule325() {
         $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule326() {
         $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule327() {
         $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule328() {
         $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule329() {
         $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule330() {
         $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule331() {
         $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule332() {
         $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule333() {
         $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)]), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule334() {
         $this->semValue = new Scalar\String_('', $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule335() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule336() {
         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule337() {
         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule338() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule339() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule340() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule341() {
         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule342() {
         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule343() {
         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule344() {
         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule345() {
         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule346() {
         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule347() {
         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule348() {
         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule349() {
         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule350() {
         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule351() {
         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule352() {
         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule353() {
         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule354() {
         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule355() {
         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule356() {
         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule357() {
         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule358() {
         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule359() {
         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule360() {
         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule361() {
         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule362() {
         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule363() {
         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule364() {
         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule365() {
         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule366() {
         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule367() {
         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule368() {
         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule369() {
         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule370() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule371() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule372() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule373() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule374() {
         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule375() {
         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule376() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule377() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule378() {
         foreach ($this->semStack[$this->stackPos-(3-2)] as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, '"'); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule379() {
         foreach ($this->semStack[$this->stackPos-(3-2)] as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, null); } } $s = preg_replace('~(\r\n|\n|\r)$~', '', $s); if ('' === $s) array_pop($this->semStack[$this->stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule380() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule381() {
         $this->semValue = 'class';
    }

    protected function reduceRule382() {
         $this->semValue = array();
    }

    protected function reduceRule383() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule384() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule385() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule386() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule387() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule388() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule389() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule390() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule391() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule392() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule393() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule394() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule395() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule396() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule397() {
         $this->semValue = new Expr\MethodCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule398() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule399() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule400() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule401() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule402() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule403() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule404() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule405() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule406() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule407() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule408() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule409() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], substr($this->semStack[$this->stackPos-(3-3)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule410() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule411() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule412() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule413() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule414() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule415() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule416() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule417() {
         $this->semValue = null;
    }

    protected function reduceRule418() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule419() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule420() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule421() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule422() {
         $this->semValue = new Expr\List_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule423() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule424() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule425() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule426() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule427() {
         $this->semValue = null;
    }

    protected function reduceRule428() {
         $this->semValue = array();
    }

    protected function reduceRule429() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule430() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule431() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule432() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule433() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule434() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-1)], true, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule435() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(2-2)], null, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule436() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule437() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule438() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule439() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]);
    }

    protected function reduceRule440() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule441() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule442() {
         $this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule443() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule444() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule445() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule446() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule447() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule448() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule449() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }
}
<?php

namespace PhpParser;

/*
 * This parser is based on a skeleton written by Moriyoshi Koizumi, which in
 * turn is based on work by Masato Bito.
 */
abstract class ParserAbstract
{
    const SYMBOL_NONE = -1;

    /*
     * The following members will be filled with generated parsing data:
     */

    /** @var int Size of $tokenToSymbol map */
    protected $tokenToSymbolMapSize;
    /** @var int Size of $action table */
    protected $actionTableSize;
    /** @var int Size of $goto table */
    protected $gotoTableSize;

    /** @var int Symbol number signifying an invalid token */
    protected $invalidSymbol;
    /** @var int Symbol number of error recovery token */
    protected $errorSymbol;
    /** @var int Action number signifying default action */
    protected $defaultAction;
    /** @var int Rule number signifying that an unexpected token was encountered */
    protected $unexpectedTokenRule;

    protected $YY2TBLSTATE;
    protected $YYNLSTATES;

    /** @var array Map of lexer tokens to internal symbols */
    protected $tokenToSymbol;
    /** @var array Map of symbols to their names */
    protected $symbolToName;
    /** @var array Names of the production rules (only necessary for debugging) */
    protected $productions;

    /** @var array Map of states to a displacement into the $action table. The corresponding action for this
     *             state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
                   action is defaulted, i.e. $actionDefault[$state] should be used instead. */
    protected $actionBase;
    /** @var array Table of actions. Indexed according to $actionBase comment. */
    protected $action;
    /** @var array Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
     *             then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
    protected $actionCheck;
    /** @var array Map of states to their default action */
    protected $actionDefault;

    /** @var array Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
     *             non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
    protected $gotoBase;
    /** @var array Table of states to goto after reduction. Indexed according to $gotoBase comment. */
    protected $goto;
    /** @var array Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
     *             then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
    protected $gotoCheck;
    /** @var array Map of non-terminals to the default state to goto after their reduction */
    protected $gotoDefault;

    /** @var array Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
     *             determining the state to goto after reduction. */
    protected $ruleToNonTerminal;
    /** @var array Map of rules to the length of their right-hand side, which is the number of elements that have to
     *             be popped from the stack(s) on reduction. */
    protected $ruleToLength;

    /*
     * The following members are part of the parser state:
     */

    /** @var Lexer Lexer that is used when parsing */
    protected $lexer;
    /** @var mixed Temporary value containing the result of last semantic action (reduction) */
    protected $semValue;
    /** @var int Position in stacks (state stack, semantic value stack, attribute stack) */
    protected $stackPos;
    /** @var array Semantic value stack (contains values of tokens and semantic action results) */
    protected $semStack;
    /** @var array[] Start attribute stack */
    protected $startAttributeStack;
    /** @var array End attributes of last *shifted* token */
    protected $endAttributes;

    /** @var bool Whether to throw on first error */
    protected $throwOnError;
    /** @var Error[] Errors collected during last parse */
    protected $errors;

    /**
     * Creates a parser instance.
     *
     * @param Lexer $lexer A lexer
     * @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
     *                       thrown on first error, or if the parser should try to continue parsing the remaining code
     *                       and build a partial AST.
     */
    public function __construct(Lexer $lexer, array $options = array()) {
        $this->lexer = $lexer;
        $this->errors = array();
        $this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
    }

    /**
     * Get array of errors that occurred during the last parse.
     *
     * This method may only return multiple errors if the 'throwOnError' option is disabled.
     *
     * @return Error[]
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * Parses PHP code into a node tree.
     *
     * @param string $code The source code to parse
     *
     * @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
     *                     unable to recover from an error).
     */
    public function parse($code) {
        $this->lexer->startLexing($code);
        $this->errors = array();

        // We start off with no lookahead-token
        $symbol = self::SYMBOL_NONE;

        // The attributes for a node are taken from the first and last token of the node.
        // From the first token only the startAttributes are taken and from the last only
        // the endAttributes. Both are merged using the array union operator (+).
        $startAttributes = '*POISON';
        $endAttributes = '*POISON';
        $this->endAttributes = $endAttributes;

        // In order to figure out the attributes for the starting token, we have to keep
        // them in a stack
        $this->startAttributeStack = array();

        // Start off in the initial state and keep a stack of previous states
        $state = 0;
        $stateStack = array($state);

        // Semantic value stack (contains values of tokens and semantic action results)
        $this->semStack = array();

        // Current position in the stack(s)
        $this->stackPos = 0;

        $errorState = 0;

        for (;;) {
            //$this->traceNewState($state, $symbol);

            if ($this->actionBase[$state] == 0) {
                $rule = $this->actionDefault[$state];
            } else {
                if ($symbol === self::SYMBOL_NONE) {
                    // Fetch the next token id from the lexer and fetch additional info by-ref.
                    // The end attributes are fetched into a temporary variable and only set once the token is really
                    // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
                    // reduced after a token was read but not yet shifted.
                    $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);

                    // map the lexer token id to the internally used symbols
                    $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
                        ? $this->tokenToSymbol[$tokenId]
                        : $this->invalidSymbol;

                    if ($symbol === $this->invalidSymbol) {
                        throw new \RangeException(sprintf(
                            'The lexer returned an invalid token (id=%d, value=%s)',
                            $tokenId, $tokenValue
                        ));
                    }

                    // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
                    // the attributes of the next token, even though they don't contain it themselves.
                    $this->startAttributeStack[$this->stackPos+1] = $startAttributes;

                    //$this->traceRead($symbol);
                }

                $idx = $this->actionBase[$state] + $symbol;
                if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
                     || ($state < $this->YY2TBLSTATE
                         && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
                         && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
                    && ($action = $this->action[$idx]) != $this->defaultAction) {
                    /*
                     * >= YYNLSTATES: shift and reduce
                     * > 0: shift
                     * = 0: accept
                     * < 0: reduce
                     * = -YYUNEXPECTED: error
                     */
                    if ($action > 0) {
                        /* shift */
                        //$this->traceShift($symbol);

                        ++$this->stackPos;
                        $stateStack[$this->stackPos] = $state = $action;
                        $this->semStack[$this->stackPos] = $tokenValue;
                        $this->startAttributeStack[$this->stackPos] = $startAttributes;
                        $this->endAttributes = $endAttributes;
                        $symbol = self::SYMBOL_NONE;

                        if ($errorState) {
                            --$errorState;
                        }

                        if ($action < $this->YYNLSTATES) {
                            continue;
                        }

                        /* $yyn >= YYNLSTATES means shift-and-reduce */
                        $rule = $action - $this->YYNLSTATES;
                    } else {
                        $rule = -$action;
                    }
                } else {
                    $rule = $this->actionDefault[$state];
                }
            }

            for (;;) {
                if ($rule === 0) {
                    /* accept */
                    //$this->traceAccept();
                    return $this->semValue;
                } elseif ($rule !== $this->unexpectedTokenRule) {
                    /* reduce */
                    //$this->traceReduce($rule);

                    try {
                        $this->{'reduceRule' . $rule}();
                    } catch (Error $e) {
                        if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
                            $e->setStartLine($startAttributes['startLine']);
                        }

                        $this->errors[] = $e;
                        if ($this->throwOnError) {
                            throw $e;
                        } else {
                            // Currently can't recover from "special" errors
                            return null;
                        }
                    }

                    /* Goto - shift nonterminal */
                    $this->stackPos -= $this->ruleToLength[$rule];
                    $nonTerminal = $this->ruleToNonTerminal[$rule];
                    $idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
                    if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
                        $state = $this->goto[$idx];
                    } else {
                        $state = $this->gotoDefault[$nonTerminal];
                    }

                    ++$this->stackPos;
                    $stateStack[$this->stackPos]     = $state;
                    $this->semStack[$this->stackPos] = $this->semValue;
                } else {
                    /* error */
                    switch ($errorState) {
                        case 0:
                            $msg = $this->getErrorMessage($symbol, $state);
                            $error = new Error($msg, $startAttributes + $endAttributes);
                            $this->errors[] = $error;
                            if ($this->throwOnError) {
                                throw $error;
                            }
                            // Break missing intentionally
                        case 1:
                        case 2:
                            $errorState = 3;

                            // Pop until error-expecting state uncovered
                            while (!(
                                (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
                                || ($state < $this->YY2TBLSTATE
                                    && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
                            ) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
                                if ($this->stackPos <= 0) {
                                    // Could not recover from error
                                    return null;
                                }
                                $state = $stateStack[--$this->stackPos];
                                //$this->tracePop($state);
                            }

                            //$this->traceShift($this->errorSymbol);
                            $stateStack[++$this->stackPos] = $state = $action;
                            break;

                        case 3:
                            if ($symbol === 0) {
                                // Reached EOF without recovering from error
                                return null;
                            }

                            //$this->traceDiscard($symbol);
                            $symbol = self::SYMBOL_NONE;
                            break 2;
                    }
                }

                if ($state < $this->YYNLSTATES) {
                    break;
                }

                /* >= YYNLSTATES means shift-and-reduce */
                $rule = $state - $this->YYNLSTATES;
            }
        }

        throw new \RuntimeException('Reached end of parser loop');
    }

    protected function getErrorMessage($symbol, $state) {
        $expectedString = '';
        if ($expected = $this->getExpectedTokens($state)) {
            $expectedString = ', expecting ' . implode(' or ', $expected);
        }

        return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
    }

    protected function getExpectedTokens($state) {
        $expected = array();

        $base = $this->actionBase[$state];
        foreach ($this->symbolToName as $symbol => $name) {
            $idx = $base + $symbol;
            if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
                || $state < $this->YY2TBLSTATE
                && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
                && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
            ) {
                if ($this->action[$idx] != $this->unexpectedTokenRule) {
                    if (count($expected) == 4) {
                        /* Too many expected tokens */
                        return array();
                    }

                    $expected[] = $name;
                }
            }
        }

        return $expected;
    }

    /*
     * Tracing functions used for debugging the parser.
     */

    /*
    protected function traceNewState($state, $symbol) {
        echo '% State ' . $state
            . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
    }

    protected function traceRead($symbol) {
        echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
    }

    protected function traceShift($symbol) {
        echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
    }

    protected function traceAccept() {
        echo "% Accepted.\n";
    }

    protected function traceReduce($n) {
        echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
    }

    protected function tracePop($state) {
        echo '% Recovering, uncovered state ' . $state . "\n";
    }

    protected function traceDiscard($symbol) {
        echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
    }
    */

    /*
     * Helper functions invoked by semantic actions
     */

    /**
     * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
     *
     * @param Node[] $stmts
     * @return Node[]
     */
    protected function handleNamespaces(array $stmts) {
        $style = $this->getNamespacingStyle($stmts);
        if (null === $style) {
            // not namespaced, nothing to do
            return $stmts;
        } elseif ('brace' === $style) {
            // For braced namespaces we only have to check that there are no invalid statements between the namespaces
            $afterFirstNamespace = false;
            foreach ($stmts as $stmt) {
                if ($stmt instanceof Node\Stmt\Namespace_) {
                    $afterFirstNamespace = true;
                } elseif (!$stmt instanceof Node\Stmt\HaltCompiler && $afterFirstNamespace) {
                    throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
                }
            }
            return $stmts;
        } else {
            // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
            $resultStmts = array();
            $targetStmts =& $resultStmts;
            foreach ($stmts as $stmt) {
                if ($stmt instanceof Node\Stmt\Namespace_) {
                    $stmt->stmts = array();
                    $targetStmts =& $stmt->stmts;
                    $resultStmts[] = $stmt;
                } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
                    // __halt_compiler() is not moved into the namespace
                    $resultStmts[] = $stmt;
                } else {
                    $targetStmts[] = $stmt;
                }
            }
            return $resultStmts;
        }
    }

    private function getNamespacingStyle(array $stmts) {
        $style = null;
        $hasNotAllowedStmts = false;
        foreach ($stmts as $stmt) {
            if ($stmt instanceof Node\Stmt\Namespace_) {
                $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
                if (null === $style) {
                    $style = $currentStyle;
                    if ($hasNotAllowedStmts) {
                        throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
                    }
                } elseif ($style !== $currentStyle) {
                    throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
                }
            } elseif (!$stmt instanceof Node\Stmt\Declare_ && !$stmt instanceof Node\Stmt\HaltCompiler) {
                $hasNotAllowedStmts = true;
            }
        }
        return $style;
    }
}
<?php

namespace PhpParser\PrettyPrinter;

use PhpParser\PrettyPrinterAbstract;
use PhpParser\Node;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Stmt;
use PhpParser\Node\Name;

class Standard extends PrettyPrinterAbstract
{
    // Special nodes

    public function pParam(Node\Param $node) {
        return ($node->type ? $this->pType($node->type) . ' ' : '')
             . ($node->byRef ? '&' : '')
             . ($node->variadic ? '...' : '')
             . '$' . $node->name
             . ($node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pArg(Node\Arg $node) {
        return ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value);
    }

    public function pConst(Node\Const_ $node) {
        return $node->name . ' = ' . $this->p($node->value);
    }

    // Names

    public function pName(Name $node) {
        return implode('\\', $node->parts);
    }

    public function pName_FullyQualified(Name\FullyQualified $node) {
        return '\\' . implode('\\', $node->parts);
    }

    public function pName_Relative(Name\Relative $node) {
        return 'namespace\\' . implode('\\', $node->parts);
    }

    // Magic Constants

    public function pScalar_MagicConst_Class(MagicConst\Class_ $node) {
        return '__CLASS__';
    }

    public function pScalar_MagicConst_Dir(MagicConst\Dir $node) {
        return '__DIR__';
    }

    public function pScalar_MagicConst_File(MagicConst\File $node) {
        return '__FILE__';
    }

    public function pScalar_MagicConst_Function(MagicConst\Function_ $node) {
        return '__FUNCTION__';
    }

    public function pScalar_MagicConst_Line(MagicConst\Line $node) {
        return '__LINE__';
    }

    public function pScalar_MagicConst_Method(MagicConst\Method $node) {
        return '__METHOD__';
    }

    public function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) {
        return '__NAMESPACE__';
    }

    public function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) {
        return '__TRAIT__';
    }

    // Scalars

    public function pScalar_String(Scalar\String_ $node) {
        return '\'' . $this->pNoIndent(addcslashes($node->value, '\'\\')) . '\'';
    }

    public function pScalar_Encapsed(Scalar\Encapsed $node) {
        return '"' . $this->pEncapsList($node->parts, '"') . '"';
    }

    public function pScalar_LNumber(Scalar\LNumber $node) {
        return (string) $node->value;
    }

    public function pScalar_DNumber(Scalar\DNumber $node) {
        $stringValue = sprintf('%.16G', $node->value);
        if ($node->value !== (double) $stringValue) {
            $stringValue = sprintf('%.17G', $node->value);
        }

        // ensure that number is really printed as float
        return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
    }

    // Assignments

    public function pExpr_Assign(Expr\Assign $node) {
        return $this->pInfixOp('Expr_Assign', $node->var, ' = ', $node->expr);
    }

    public function pExpr_AssignRef(Expr\AssignRef $node) {
        return $this->pInfixOp('Expr_AssignRef', $node->var, ' =& ', $node->expr);
    }

    public function pExpr_AssignOp_Plus(AssignOp\Plus $node) {
        return $this->pInfixOp('Expr_AssignOp_Plus', $node->var, ' += ', $node->expr);
    }

    public function pExpr_AssignOp_Minus(AssignOp\Minus $node) {
        return $this->pInfixOp('Expr_AssignOp_Minus', $node->var, ' -= ', $node->expr);
    }

    public function pExpr_AssignOp_Mul(AssignOp\Mul $node) {
        return $this->pInfixOp('Expr_AssignOp_Mul', $node->var, ' *= ', $node->expr);
    }

    public function pExpr_AssignOp_Div(AssignOp\Div $node) {
        return $this->pInfixOp('Expr_AssignOp_Div', $node->var, ' /= ', $node->expr);
    }

    public function pExpr_AssignOp_Concat(AssignOp\Concat $node) {
        return $this->pInfixOp('Expr_AssignOp_Concat', $node->var, ' .= ', $node->expr);
    }

    public function pExpr_AssignOp_Mod(AssignOp\Mod $node) {
        return $this->pInfixOp('Expr_AssignOp_Mod', $node->var, ' %= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseAnd', $node->var, ' &= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseOr', $node->var, ' |= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseXor', $node->var, ' ^= ', $node->expr);
    }

    public function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) {
        return $this->pInfixOp('Expr_AssignOp_ShiftLeft', $node->var, ' <<= ', $node->expr);
    }

    public function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) {
        return $this->pInfixOp('Expr_AssignOp_ShiftRight', $node->var, ' >>= ', $node->expr);
    }

    public function pExpr_AssignOp_Pow(AssignOp\Pow $node) {
        return $this->pInfixOp('Expr_AssignOp_Pow', $node->var, ' **= ', $node->expr);
    }

    // Binary expressions

    public function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) {
        return $this->pInfixOp('Expr_BinaryOp_Plus', $node->left, ' + ', $node->right);
    }

    public function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) {
        return $this->pInfixOp('Expr_BinaryOp_Minus', $node->left, ' - ', $node->right);
    }

    public function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) {
        return $this->pInfixOp('Expr_BinaryOp_Mul', $node->left, ' * ', $node->right);
    }

    public function pExpr_BinaryOp_Div(BinaryOp\Div $node) {
        return $this->pInfixOp('Expr_BinaryOp_Div', $node->left, ' / ', $node->right);
    }

    public function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) {
        return $this->pInfixOp('Expr_BinaryOp_Concat', $node->left, ' . ', $node->right);
    }

    public function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) {
        return $this->pInfixOp('Expr_BinaryOp_Mod', $node->left, ' % ', $node->right);
    }

    public function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_BooleanAnd', $node->left, ' && ', $node->right);
    }

    public function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_BooleanOr', $node->left, ' || ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseAnd', $node->left, ' & ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseOr', $node->left, ' | ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseXor', $node->left, ' ^ ', $node->right);
    }

    public function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) {
        return $this->pInfixOp('Expr_BinaryOp_ShiftLeft', $node->left, ' << ', $node->right);
    }

    public function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) {
        return $this->pInfixOp('Expr_BinaryOp_ShiftRight', $node->left, ' >> ', $node->right);
    }

    public function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) {
        return $this->pInfixOp('Expr_BinaryOp_Pow', $node->left, ' ** ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalAnd', $node->left, ' and ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalOr', $node->left, ' or ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalXor', $node->left, ' xor ', $node->right);
    }

    public function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) {
        return $this->pInfixOp('Expr_BinaryOp_Equal', $node->left, ' == ', $node->right);
    }

    public function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_NotEqual', $node->left, ' != ', $node->right);
    }

    public function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) {
        return $this->pInfixOp('Expr_BinaryOp_Identical', $node->left, ' === ', $node->right);
    }

    public function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) {
        return $this->pInfixOp('Expr_BinaryOp_NotIdentical', $node->left, ' !== ', $node->right);
    }

    public function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) {
        return $this->pInfixOp('Expr_BinaryOp_Spaceship', $node->left, ' <=> ', $node->right);
    }

    public function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) {
        return $this->pInfixOp('Expr_BinaryOp_Greater', $node->left, ' > ', $node->right);
    }

    public function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_GreaterOrEqual', $node->left, ' >= ', $node->right);
    }

    public function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) {
        return $this->pInfixOp('Expr_BinaryOp_Smaller', $node->left, ' < ', $node->right);
    }

    public function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_SmallerOrEqual', $node->left, ' <= ', $node->right);
    }

    public function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) {
        return $this->pInfixOp('Expr_BinaryOp_Coalesce', $node->left, ' ?? ', $node->right);
    }

    public function pExpr_Instanceof(Expr\Instanceof_ $node) {
        return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
    }

    // Unary expressions

    public function pExpr_BooleanNot(Expr\BooleanNot $node) {
        return $this->pPrefixOp('Expr_BooleanNot', '!', $node->expr);
    }

    public function pExpr_BitwiseNot(Expr\BitwiseNot $node) {
        return $this->pPrefixOp('Expr_BitwiseNot', '~', $node->expr);
    }

    public function pExpr_UnaryMinus(Expr\UnaryMinus $node) {
        return $this->pPrefixOp('Expr_UnaryMinus', '-', $node->expr);
    }

    public function pExpr_UnaryPlus(Expr\UnaryPlus $node) {
        return $this->pPrefixOp('Expr_UnaryPlus', '+', $node->expr);
    }

    public function pExpr_PreInc(Expr\PreInc $node) {
        return $this->pPrefixOp('Expr_PreInc', '++', $node->var);
    }

    public function pExpr_PreDec(Expr\PreDec $node) {
        return $this->pPrefixOp('Expr_PreDec', '--', $node->var);
    }

    public function pExpr_PostInc(Expr\PostInc $node) {
        return $this->pPostfixOp('Expr_PostInc', $node->var, '++');
    }

    public function pExpr_PostDec(Expr\PostDec $node) {
        return $this->pPostfixOp('Expr_PostDec', $node->var, '--');
    }

    public function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) {
        return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
    }

    public function pExpr_YieldFrom(Expr\YieldFrom $node) {
        return $this->pPrefixOp('Expr_YieldFrom', 'yield from ', $node->expr);
    }

    public function pExpr_Print(Expr\Print_ $node) {
        return $this->pPrefixOp('Expr_Print', 'print ', $node->expr);
    }

    // Casts

    public function pExpr_Cast_Int(Cast\Int_ $node) {
        return $this->pPrefixOp('Expr_Cast_Int', '(int) ', $node->expr);
    }

    public function pExpr_Cast_Double(Cast\Double $node) {
        return $this->pPrefixOp('Expr_Cast_Double', '(double) ', $node->expr);
    }

    public function pExpr_Cast_String(Cast\String_ $node) {
        return $this->pPrefixOp('Expr_Cast_String', '(string) ', $node->expr);
    }

    public function pExpr_Cast_Array(Cast\Array_ $node) {
        return $this->pPrefixOp('Expr_Cast_Array', '(array) ', $node->expr);
    }

    public function pExpr_Cast_Object(Cast\Object_ $node) {
        return $this->pPrefixOp('Expr_Cast_Object', '(object) ', $node->expr);
    }

    public function pExpr_Cast_Bool(Cast\Bool_ $node) {
        return $this->pPrefixOp('Expr_Cast_Bool', '(bool) ', $node->expr);
    }

    public function pExpr_Cast_Unset(Cast\Unset_ $node) {
        return $this->pPrefixOp('Expr_Cast_Unset', '(unset) ', $node->expr);
    }

    // Function calls and similar constructs

    public function pExpr_FuncCall(Expr\FuncCall $node) {
        return $this->p($node->name) . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_MethodCall(Expr\MethodCall $node) {
        return $this->pVarOrNewExpr($node->var) . '->' . $this->pObjectProperty($node->name)
             . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_StaticCall(Expr\StaticCall $node) {
        return $this->p($node->class) . '::'
             . ($node->name instanceof Expr
                ? ($node->name instanceof Expr\Variable
                   || $node->name instanceof Expr\ArrayDimFetch
                   ? $this->p($node->name)
                   : '{' . $this->p($node->name) . '}')
                : $node->name)
             . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_Empty(Expr\Empty_ $node) {
        return 'empty(' . $this->p($node->expr) . ')';
    }

    public function pExpr_Isset(Expr\Isset_ $node) {
        return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
    }

    public function pExpr_Eval(Expr\Eval_ $node) {
        return 'eval(' . $this->p($node->expr) . ')';
    }

    public function pExpr_Include(Expr\Include_ $node) {
        static $map = array(
            Expr\Include_::TYPE_INCLUDE      => 'include',
            Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once',
            Expr\Include_::TYPE_REQUIRE      => 'require',
            Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once',
        );

        return $map[$node->type] . ' ' . $this->p($node->expr);
    }

    public function pExpr_List(Expr\List_ $node) {
        $pList = array();
        foreach ($node->vars as $var) {
            if (null === $var) {
                $pList[] = '';
            } else {
                $pList[] = $this->p($var);
            }
        }

        return 'list(' . implode(', ', $pList) . ')';
    }

    // Other

    public function pExpr_Variable(Expr\Variable $node) {
        if ($node->name instanceof Expr) {
            return '${' . $this->p($node->name) . '}';
        } else {
            return '$' . $node->name;
        }
    }

    public function pExpr_Array(Expr\Array_ $node) {
        return 'array(' . $this->pCommaSeparated($node->items) . ')';
    }

    public function pExpr_ArrayItem(Expr\ArrayItem $node) {
        return (null !== $node->key ? $this->p($node->key) . ' => ' : '')
             . ($node->byRef ? '&' : '') . $this->p($node->value);
    }

    public function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) {
        return $this->pVarOrNewExpr($node->var)
             . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']';
    }

    public function pExpr_ConstFetch(Expr\ConstFetch $node) {
        return $this->p($node->name);
    }

    public function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
        return $this->p($node->class) . '::' . $node->name;
    }

    public function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
        return $this->pVarOrNewExpr($node->var) . '->' . $this->pObjectProperty($node->name);
    }

    public function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
        return $this->p($node->class) . '::$' . $this->pObjectProperty($node->name);
    }

    public function pExpr_ShellExec(Expr\ShellExec $node) {
        return '`' . $this->pEncapsList($node->parts, '`') . '`';
    }

    public function pExpr_Closure(Expr\Closure $node) {
        return ($node->static ? 'static ' : '')
             . 'function ' . ($node->byRef ? '&' : '')
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')': '')
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pExpr_ClosureUse(Expr\ClosureUse $node) {
        return ($node->byRef ? '&' : '') . '$' . $node->var;
    }

    public function pExpr_New(Expr\New_ $node) {
        if ($node->class instanceof Stmt\Class_) {
            $args = $node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '';
            return 'new ' . $this->pClassCommon($node->class, $args);
        }
        return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_Clone(Expr\Clone_ $node) {
        return 'clone ' . $this->p($node->expr);
    }

    public function pExpr_Ternary(Expr\Ternary $node) {
        // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
        // this is okay because the part between ? and : never needs parentheses.
        return $this->pInfixOp('Expr_Ternary',
            $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
        );
    }

    public function pExpr_Exit(Expr\Exit_ $node) {
        return 'die' . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : '');
    }

    public function pExpr_Yield(Expr\Yield_ $node) {
        if ($node->value === null) {
            return 'yield';
        } else {
            // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary
            return '(yield '
                 . ($node->key !== null ? $this->p($node->key) . ' => ' : '')
                 . $this->p($node->value)
                 . ')';
        }
    }

    // Declarations

    public function pStmt_Namespace(Stmt\Namespace_ $node) {
        if ($this->canUseSemicolonNamespaces) {
            return 'namespace ' . $this->p($node->name) . ';' . "\n" . $this->pStmts($node->stmts, false);
        } else {
            return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '')
                 . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
        }
    }

    public function pStmt_Use(Stmt\Use_ $node) {
        return 'use '
             . ($node->type === Stmt\Use_::TYPE_FUNCTION ? 'function ' : '')
             . ($node->type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : '')
             . $this->pCommaSeparated($node->uses) . ';';
    }

    public function pStmt_UseUse(Stmt\UseUse $node) {
        return $this->p($node->name)
             . ($node->name->getLast() !== $node->alias ? ' as ' . $node->alias : '');
    }

    public function pStmt_Interface(Stmt\Interface_ $node) {
        return 'interface ' . $node->name
             . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Class(Stmt\Class_ $node) {
        return $this->pClassCommon($node, ' ' . $node->name);
    }

    public function pStmt_Trait(Stmt\Trait_ $node) {
        return 'trait ' . $node->name
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_TraitUse(Stmt\TraitUse $node) {
        return 'use ' . $this->pCommaSeparated($node->traits)
             . (empty($node->adaptations)
                ? ';'
                : ' {' . $this->pStmts($node->adaptations) . "\n" . '}');
    }

    public function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) {
        return $this->p($node->trait) . '::' . $node->method
             . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';';
    }

    public function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) {
        return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
             . $node->method . ' as'
             . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '')
             . (null !== $node->newName     ? ' ' . $node->newName                        : '')
             . ';';
    }

    public function pStmt_Property(Stmt\Property $node) {
        return (0 === $node->type ? 'var ' : $this->pModifiers($node->type)) . $this->pCommaSeparated($node->props) . ';';
    }

    public function pStmt_PropertyProperty(Stmt\PropertyProperty $node) {
        return '$' . $node->name
             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pStmt_ClassMethod(Stmt\ClassMethod $node) {
        return $this->pModifiers($node->type)
             . 'function ' . ($node->byRef ? '&' : '') . $node->name
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . (null !== $node->stmts
                ? "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}'
                : ';');
    }

    public function pStmt_ClassConst(Stmt\ClassConst $node) {
        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    }

    public function pStmt_Function(Stmt\Function_ $node) {
        return 'function ' . ($node->byRef ? '&' : '') . $node->name
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Const(Stmt\Const_ $node) {
        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    }

    public function pStmt_Declare(Stmt\Declare_ $node) {
        return 'declare (' . $this->pCommaSeparated($node->declares) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) {
        return $node->key . ' = ' . $this->p($node->value);
    }

    // Control flow

    public function pStmt_If(Stmt\If_ $node) {
        return 'if (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}'
             . $this->pImplode($node->elseifs)
             . (null !== $node->else ? $this->p($node->else) : '');
    }

    public function pStmt_ElseIf(Stmt\ElseIf_ $node) {
        return ' elseif (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Else(Stmt\Else_ $node) {
        return ' else {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_For(Stmt\For_ $node) {
        return 'for ('
             . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '')
             . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '')
             . $this->pCommaSeparated($node->loop)
             . ') {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Foreach(Stmt\Foreach_ $node) {
        return 'foreach (' . $this->p($node->expr) . ' as '
             . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '')
             . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_While(Stmt\While_ $node) {
        return 'while (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Do(Stmt\Do_ $node) {
        return 'do {' . $this->pStmts($node->stmts) . "\n"
             . '} while (' . $this->p($node->cond) . ');';
    }

    public function pStmt_Switch(Stmt\Switch_ $node) {
        return 'switch (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->cases) . "\n" . '}';
    }

    public function pStmt_Case(Stmt\Case_ $node) {
        return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':'
             . $this->pStmts($node->stmts);
    }

    public function pStmt_TryCatch(Stmt\TryCatch $node) {
        return 'try {' . $this->pStmts($node->stmts) . "\n" . '}'
             . $this->pImplode($node->catches)
             . ($node->finallyStmts !== null
                ? ' finally {' . $this->pStmts($node->finallyStmts) . "\n" . '}'
                : '');
    }

    public function pStmt_Catch(Stmt\Catch_ $node) {
        return ' catch (' . $this->p($node->type) . ' $' . $node->var . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Break(Stmt\Break_ $node) {
        return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    }

    public function pStmt_Continue(Stmt\Continue_ $node) {
        return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    }

    public function pStmt_Return(Stmt\Return_ $node) {
        return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
    }

    public function pStmt_Throw(Stmt\Throw_ $node) {
        return 'throw ' . $this->p($node->expr) . ';';
    }

    public function pStmt_Label(Stmt\Label $node) {
        return $node->name . ':';
    }

    public function pStmt_Goto(Stmt\Goto_ $node) {
        return 'goto ' . $node->name . ';';
    }

    // Other

    public function pStmt_Echo(Stmt\Echo_ $node) {
        return 'echo ' . $this->pCommaSeparated($node->exprs) . ';';
    }

    public function pStmt_Static(Stmt\Static_ $node) {
        return 'static ' . $this->pCommaSeparated($node->vars) . ';';
    }

    public function pStmt_Global(Stmt\Global_ $node) {
        return 'global ' . $this->pCommaSeparated($node->vars) . ';';
    }

    public function pStmt_StaticVar(Stmt\StaticVar $node) {
        return '$' . $node->name
             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pStmt_Unset(Stmt\Unset_ $node) {
        return 'unset(' . $this->pCommaSeparated($node->vars) . ');';
    }

    public function pStmt_InlineHTML(Stmt\InlineHTML $node) {
        return '?>' . $this->pNoIndent("\n" . $node->value) . '<?php ';
    }

    public function pStmt_HaltCompiler(Stmt\HaltCompiler $node) {
        return '__halt_compiler();' . $node->remaining;
    }

    // Helpers

    protected function pType($node) {
        return is_string($node) ? $node : $this->p($node);
    }

    protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
        return $this->pModifiers($node->type)
        . 'class' . $afterClassToken
        . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
        . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
        . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    /** @internal */
    public function pObjectProperty($node) {
        if ($node instanceof Expr) {
            return '{' . $this->p($node) . '}';
        } else {
            return $node;
        }
    }

    /** @internal */
    public function pModifiers($modifiers) {
        return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC    ? 'public '    : '')
             . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '')
             . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE   ? 'private '   : '')
             . ($modifiers & Stmt\Class_::MODIFIER_STATIC    ? 'static '    : '')
             . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT  ? 'abstract '  : '')
             . ($modifiers & Stmt\Class_::MODIFIER_FINAL     ? 'final '     : '');
    }

    /** @internal */
    public function pEncapsList(array $encapsList, $quote) {
        $return = '';
        foreach ($encapsList as $element) {
            if (is_string($element)) {
                $return .= addcslashes($element, "\n\r\t\f\v$" . $quote . "\\");
            } else {
                $return .= '{' . $this->p($element) . '}';
            }
        }

        return $return;
    }

    /** @internal */
    public function pVarOrNewExpr(Node $node) {
        if ($node instanceof Expr\New_) {
            return '(' . $this->p($node) . ')';
        } else {
            return $this->p($node);
        }
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;

abstract class PrettyPrinterAbstract
{
    protected $precedenceMap = array(
        // [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right
        'Expr_BinaryOp_Pow'            => array(  0,  1),
        'Expr_BitwiseNot'              => array( 10,  1),
        'Expr_PreInc'                  => array( 10,  1),
        'Expr_PreDec'                  => array( 10,  1),
        'Expr_PostInc'                 => array( 10, -1),
        'Expr_PostDec'                 => array( 10, -1),
        'Expr_UnaryPlus'               => array( 10,  1),
        'Expr_UnaryMinus'              => array( 10,  1),
        'Expr_Cast_Int'                => array( 10,  1),
        'Expr_Cast_Double'             => array( 10,  1),
        'Expr_Cast_String'             => array( 10,  1),
        'Expr_Cast_Array'              => array( 10,  1),
        'Expr_Cast_Object'             => array( 10,  1),
        'Expr_Cast_Bool'               => array( 10,  1),
        'Expr_Cast_Unset'              => array( 10,  1),
        'Expr_ErrorSuppress'           => array( 10,  1),
        'Expr_Instanceof'              => array( 20,  0),
        'Expr_BooleanNot'              => array( 30,  1),
        'Expr_BinaryOp_Mul'            => array( 40, -1),
        'Expr_BinaryOp_Div'            => array( 40, -1),
        'Expr_BinaryOp_Mod'            => array( 40, -1),
        'Expr_BinaryOp_Plus'           => array( 50, -1),
        'Expr_BinaryOp_Minus'          => array( 50, -1),
        'Expr_BinaryOp_Concat'         => array( 50, -1),
        'Expr_BinaryOp_ShiftLeft'      => array( 60, -1),
        'Expr_BinaryOp_ShiftRight'     => array( 60, -1),
        'Expr_BinaryOp_Smaller'        => array( 70,  0),
        'Expr_BinaryOp_SmallerOrEqual' => array( 70,  0),
        'Expr_BinaryOp_Greater'        => array( 70,  0),
        'Expr_BinaryOp_GreaterOrEqual' => array( 70,  0),
        'Expr_BinaryOp_Equal'          => array( 80,  0),
        'Expr_BinaryOp_NotEqual'       => array( 80,  0),
        'Expr_BinaryOp_Identical'      => array( 80,  0),
        'Expr_BinaryOp_NotIdentical'   => array( 80,  0),
        'Expr_BinaryOp_Spaceship'      => array( 80,  0),
        'Expr_BinaryOp_BitwiseAnd'     => array( 90, -1),
        'Expr_BinaryOp_BitwiseXor'     => array(100, -1),
        'Expr_BinaryOp_BitwiseOr'      => array(110, -1),
        'Expr_BinaryOp_BooleanAnd'     => array(120, -1),
        'Expr_BinaryOp_BooleanOr'      => array(130, -1),
        'Expr_BinaryOp_Coalesce'       => array(140,  1),
        'Expr_Ternary'                 => array(150, -1),
        // parser uses %left for assignments, but they really behave as %right
        'Expr_Assign'                  => array(160,  1),
        'Expr_AssignRef'               => array(160,  1),
        'Expr_AssignOp_Plus'           => array(160,  1),
        'Expr_AssignOp_Minus'          => array(160,  1),
        'Expr_AssignOp_Mul'            => array(160,  1),
        'Expr_AssignOp_Div'            => array(160,  1),
        'Expr_AssignOp_Concat'         => array(160,  1),
        'Expr_AssignOp_Mod'            => array(160,  1),
        'Expr_AssignOp_BitwiseAnd'     => array(160,  1),
        'Expr_AssignOp_BitwiseOr'      => array(160,  1),
        'Expr_AssignOp_BitwiseXor'     => array(160,  1),
        'Expr_AssignOp_ShiftLeft'      => array(160,  1),
        'Expr_AssignOp_ShiftRight'     => array(160,  1),
        'Expr_AssignOp_Pow'            => array(160,  1),
        'Expr_YieldFrom'               => array(165,  1),
        'Expr_Print'                   => array(168,  1),
        'Expr_BinaryOp_LogicalAnd'     => array(170, -1),
        'Expr_BinaryOp_LogicalXor'     => array(180, -1),
        'Expr_BinaryOp_LogicalOr'      => array(190, -1),
        'Expr_Include'                 => array(200, -1),
    );

    protected $noIndentToken;
    protected $canUseSemicolonNamespaces;

    public function __construct() {
        $this->noIndentToken = '_NO_INDENT_' . mt_rand();
    }

    /**
     * Pretty prints an array of statements.
     *
     * @param Node[] $stmts Array of statements
     *
     * @return string Pretty printed statements
     */
    public function prettyPrint(array $stmts) {
        $this->preprocessNodes($stmts);

        return ltrim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
    }

    /**
     * Pretty prints an expression.
     *
     * @param Expr $node Expression node
     *
     * @return string Pretty printed node
     */
    public function prettyPrintExpr(Expr $node) {
        return str_replace("\n" . $this->noIndentToken, "\n", $this->p($node));
    }

    /**
     * Pretty prints a file of statements (includes the opening <?php tag if it is required).
     *
     * @param Node[] $stmts Array of statements
     *
     * @return string Pretty printed statements
     */
    public function prettyPrintFile(array $stmts) {
        $p = rtrim($this->prettyPrint($stmts));

        $p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
        $p = preg_replace('/<\?php$/', '', $p);

        if (!$count) {
            $p = "<?php\n\n" . $p;
        }

        return $p;
    }

    /**
     * Preprocesses the top-level nodes to initialize pretty printer state.
     *
     * @param Node[] $nodes Array of nodes
     */
    protected function preprocessNodes(array $nodes) {
        /* We can use semicolon-namespaces unless there is a global namespace declaration */
        $this->canUseSemicolonNamespaces = true;
        foreach ($nodes as $node) {
            if ($node instanceof Stmt\Namespace_ && null === $node->name) {
                $this->canUseSemicolonNamespaces = false;
            }
        }
    }

    /**
     * Pretty prints an array of nodes (statements) and indents them optionally.
     *
     * @param Node[] $nodes  Array of nodes
     * @param bool   $indent Whether to indent the printed nodes
     *
     * @return string Pretty printed statements
     */
    protected function pStmts(array $nodes, $indent = true) {
        $result = '';
        foreach ($nodes as $node) {
            $result .= "\n"
                    . $this->pComments($node->getAttribute('comments', array()))
                    . $this->p($node)
                    . ($node instanceof Expr ? ';' : '');
        }

        if ($indent) {
            return preg_replace('~\n(?!$|' . $this->noIndentToken . ')~', "\n    ", $result);
        } else {
            return $result;
        }
    }

    /**
     * Pretty prints a node.
     *
     * @param Node $node Node to be pretty printed
     *
     * @return string Pretty printed node
     */
    protected function p(Node $node) {
        return $this->{'p' . $node->getType()}($node);
    }

    protected function pInfixOp($type, Node $leftNode, $operatorString, Node $rightNode) {
        list($precedence, $associativity) = $this->precedenceMap[$type];

        return $this->pPrec($leftNode, $precedence, $associativity, -1)
             . $operatorString
             . $this->pPrec($rightNode, $precedence, $associativity, 1);
    }

    protected function pPrefixOp($type, $operatorString, Node $node) {
        list($precedence, $associativity) = $this->precedenceMap[$type];
        return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
    }

    protected function pPostfixOp($type, Node $node, $operatorString) {
        list($precedence, $associativity) = $this->precedenceMap[$type];
        return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
    }

    /**
     * Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
     *
     * @param Node $node                Node to pretty print
     * @param int  $parentPrecedence    Precedence of the parent operator
     * @param int  $parentAssociativity Associativity of parent operator
     *                                  (-1 is left, 0 is nonassoc, 1 is right)
     * @param int  $childPosition       Position of the node relative to the operator
     *                                  (-1 is left, 1 is right)
     *
     * @return string The pretty printed node
     */
    protected function pPrec(Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
        $type = $node->getType();
        if (isset($this->precedenceMap[$type])) {
            $childPrecedence = $this->precedenceMap[$type][0];
            if ($childPrecedence > $parentPrecedence
                || ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
            ) {
                return '(' . $this->{'p' . $type}($node) . ')';
            }
        }

        return $this->{'p' . $type}($node);
    }

    /**
     * Pretty prints an array of nodes and implodes the printed values.
     *
     * @param Node[] $nodes Array of Nodes to be printed
     * @param string $glue  Character to implode with
     *
     * @return string Imploded pretty printed nodes
     */
    protected function pImplode(array $nodes, $glue = '') {
        $pNodes = array();
        foreach ($nodes as $node) {
            $pNodes[] = $this->p($node);
        }

        return implode($glue, $pNodes);
    }

    /**
     * Pretty prints an array of nodes and implodes the printed values with commas.
     *
     * @param Node[] $nodes Array of Nodes to be printed
     *
     * @return string Comma separated pretty printed nodes
     */
    protected function pCommaSeparated(array $nodes) {
        return $this->pImplode($nodes, ', ');
    }

    /**
     * Signals the pretty printer that a string shall not be indented.
     *
     * @param string $string Not to be indented string
     *
     * @return mixed String marked with $this->noIndentToken's.
     */
    protected function pNoIndent($string) {
        return str_replace("\n", "\n" . $this->noIndentToken, $string);
    }

    protected function pComments(array $comments) {
        $result = '';

        foreach ($comments as $comment) {
            $result .= $comment->getReformattedText() . "\n";
        }

        return $result;
    }
}
<?php

namespace PhpParser;

interface Serializer
{
    /**
     * Serializes statements into some string format.
     *
     * @param array $nodes Statements
     *
     * @return string Serialized string
     */
    public function serialize(array $nodes);
}<?php

namespace PhpParser\Serializer;

use XMLWriter;
use PhpParser\Node;
use PhpParser\Comment;
use PhpParser\Serializer;

class XML implements Serializer
{
    protected $writer;

    /**
     * Constructs a XML serializer.
     */
    public function __construct() {
        $this->writer = new XMLWriter;
        $this->writer->openMemory();
        $this->writer->setIndent(true);
    }

    public function serialize(array $nodes) {
        $this->writer->flush();
        $this->writer->startDocument('1.0', 'UTF-8');

        $this->writer->startElement('AST');
        $this->writer->writeAttribute('xmlns:node',      'http://nikic.github.com/PHPParser/XML/node');
        $this->writer->writeAttribute('xmlns:subNode',   'http://nikic.github.com/PHPParser/XML/subNode');
        $this->writer->writeAttribute('xmlns:attribute', 'http://nikic.github.com/PHPParser/XML/attribute');
        $this->writer->writeAttribute('xmlns:scalar',    'http://nikic.github.com/PHPParser/XML/scalar');

        $this->_serialize($nodes);

        $this->writer->endElement();

        return $this->writer->outputMemory();
    }

    protected function _serialize($node) {
        if ($node instanceof Node) {
            $this->writer->startElement('node:' . $node->getType());

            foreach ($node->getAttributes() as $name => $value) {
                $this->writer->startElement('attribute:' . $name);
                $this->_serialize($value);
                $this->writer->endElement();
            }

            foreach ($node as $name => $subNode) {
                $this->writer->startElement('subNode:' . $name);
                $this->_serialize($subNode);
                $this->writer->endElement();
            }

            $this->writer->endElement();
        } elseif ($node instanceof Comment) {
            $this->writer->startElement('comment');
            $this->writer->writeAttribute('isDocComment', $node instanceof Comment\Doc ? 'true' : 'false');
            $this->writer->writeAttribute('line', (string) $node->getLine());
            $this->writer->text($node->getText());
            $this->writer->endElement();
        } elseif (is_array($node)) {
            $this->writer->startElement('scalar:array');
            foreach ($node as $subNode) {
                $this->_serialize($subNode);
            }
            $this->writer->endElement();
        } elseif (is_string($node)) {
            $this->writer->writeElement('scalar:string', $node);
        } elseif (is_int($node)) {
            $this->writer->writeElement('scalar:int', (string) $node);
        } elseif (is_float($node)) {
            // TODO Higher precision conversion?
            $this->writer->writeElement('scalar:float', (string) $node);
        } elseif (true === $node) {
            $this->writer->writeElement('scalar:true');
        } elseif (false === $node) {
            $this->writer->writeElement('scalar:false');
        } elseif (null === $node) {
            $this->writer->writeElement('scalar:null');
        } else {
            throw new \InvalidArgumentException('Unexpected node type');
        }
    }
}
<?php

namespace PhpParser;

interface Unserializer
{
    /**
     * Unserializes a string in some format into a node tree.
     *
     * @param string $string Serialized string
     *
     * @return mixed Node tree
     */
    public function unserialize($string);
}
<?php

namespace PhpParser\Unserializer;

use XMLReader;
use DomainException;
use PhpParser\Unserializer;

class XML implements Unserializer
{
    protected $reader;

    public function __construct() {
        $this->reader = new XMLReader;
    }

    public function unserialize($string) {
        $this->reader->XML($string);

        $this->reader->read();
        if ('AST' !== $this->reader->name) {
            throw new DomainException('AST root element not found');
        }

        return $this->read($this->reader->depth);
    }

    protected function read($depthLimit, $throw = true, &$nodeFound = null) {
        $nodeFound = true;
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
                continue;
            }

            if ('node' === $this->reader->prefix) {
                return $this->readNode();
            } elseif ('scalar' === $this->reader->prefix) {
                return $this->readScalar();
            } elseif ('comment' === $this->reader->name) {
                return $this->readComment();
            } else {
                throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name));
            }
        }

        $nodeFound = false;
        if ($throw) {
            throw new DomainException('Expected node or scalar');
        }
    }

    protected function readNode() {
        $className = $this->getClassNameFromType($this->reader->localName);

        // create the node without calling it's constructor
        $node = unserialize(
            sprintf(
                "O:%d:\"%s\":1:{s:13:\"\0*\0attributes\";a:0:{}}",
                strlen($className), $className
            )
        );

        $depthLimit = $this->reader->depth;
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
                continue;
            }

            $type = $this->reader->prefix;
            if ('subNode' !== $type && 'attribute' !== $type) {
                throw new DomainException(
                    sprintf('Expected sub node or attribute, got node of type "%s"', $this->reader->name)
                );
            }

            $name = $this->reader->localName;
            $value = $this->read($this->reader->depth);

            if ('subNode' === $type) {
                $node->$name = $value;
            } else {
                $node->setAttribute($name, $value);
            }
        }

        return $node;
    }

    protected function readScalar() {
        switch ($name = $this->reader->localName) {
            case 'array':
                $depth = $this->reader->depth;
                $array = array();
                while (true) {
                    $node = $this->read($depth, false, $nodeFound);
                    if (!$nodeFound) {
                        break;
                    }
                    $array[] = $node;
                }
                return $array;
            case 'string':
                return $this->reader->readString();
            case 'int':
                return $this->parseInt($this->reader->readString());
            case 'float':
                $text = $this->reader->readString();
                if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
                    throw new DomainException(sprintf('"%s" is not a valid float', $text));
                }
                return $float;
            case 'true':
            case 'false':
            case 'null':
                if (!$this->reader->isEmptyElement) {
                    throw new DomainException(sprintf('"%s" scalar must be empty', $name));
                }
                return constant($name);
            default:
                throw new DomainException(sprintf('Unknown scalar type "%s"', $name));
        }
    }

    private function parseInt($text) {
        if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
            throw new DomainException(sprintf('"%s" is not a valid integer', $text));
        }
        return $int;
    }

    protected function readComment() {
        $className = $this->reader->getAttribute('isDocComment') === 'true'
            ? 'PhpParser\Comment\Doc'
            : 'PhpParser\Comment'
        ;
        return new $className(
            $this->reader->readString(),
            $this->parseInt($this->reader->getAttribute('line'))
        );
    }

    protected function getClassNameFromType($type) {
        $className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
        if (!class_exists($className)) {
            $className .= '_';
        }
        if (!class_exists($className)) {
            throw new DomainException(sprintf('Unknown node type "%s"', $type));
        }
        return $className;
    }
}
<?php

if (!class_exists('PhpParser\Autoloader')) {
    require __DIR__ . '/PhpParser/Autoloader.php';
}
PhpParser\Autoloader::register();
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="false"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="./lib/bootstrap.php">
    <testsuites>
        <testsuite name="PHPParser Test Suite">
            <directory>./test/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory suffix=".php">./lib/PhpParser/</directory>
        </whitelist>
    </filter>
</phpunit><?php

namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;

/* The autoloader is already active at this point, so we only check effects here. */

class AutoloaderTest extends \PHPUnit_Framework_TestCase {
    public function testLegacyNames() {
        $lexer = new \PHPParser_Lexer;
        $parser = new \PHPParser_Parser($lexer);
        $prettyPrinter = new \PHPParser_PrettyPrinter_Default;

        $this->assertInstanceof('PhpParser\Lexer', $lexer);
        $this->assertInstanceof('PhpParser\Parser', $parser);
        $this->assertInstanceof('PhpParser\PrettyPrinter\Standard', $prettyPrinter);
    }

    public function testPhp7ReservedNames() {
        if (version_compare(PHP_VERSION, '7.0-dev', '>=')) {
            $this->markTestSkipped('Cannot create aliases to reserved names on PHP 7');
        }

        $this->assertTrue(new Expr\Cast\Bool_(new Expr\Variable('foo')) instanceof Expr\Cast\Bool);
        $this->assertTrue(new Expr\Cast\Int_(new Expr\Variable('foo')) instanceof Expr\Cast\Int);

        $this->assertInstanceof('PhpParser\Node\Expr\Cast\Object_', new Expr\Cast\Object(new Expr\Variable('foo')));
        $this->assertInstanceof('PhpParser\Node\Expr\Cast\String_', new Expr\Cast\String(new Expr\Variable('foo')));
        $this->assertInstanceof('PhpParser\Node\Scalar\String_', new Scalar\String('foobar'));
    }

    public function testClassExists() {
        $this->assertTrue(class_exists('PhpParser\NodeVisitorAbstract'));
        $this->assertTrue(class_exists('PHPParser_NodeVisitor_NameResolver'));

        $this->assertFalse(class_exists('PhpParser\FooBar'));
        $this->assertFalse(class_exists('PHPParser_FooBar'));
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class ClassTest extends \PHPUnit_Framework_TestCase
{
    protected function createClassBuilder($class) {
        return new Class_($class);
    }

    public function testExtendsImplements() {
        $node = $this->createClassBuilder('SomeLogger')
            ->extend('BaseLogger')
            ->implement('Namespaced\Logger', new Name('SomeInterface'))
            ->implement('\Fully\Qualified', 'namespace\NamespaceRelative')
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Class_('SomeLogger', array(
                'extends' => new Name('BaseLogger'),
                'implements' => array(
                    new Name('Namespaced\Logger'),
                    new Name('SomeInterface'),
                    new Name\FullyQualified('Fully\Qualified'),
                    new Name\Relative('NamespaceRelative'),
                ),
            )),
            $node
        );
    }

    public function testAbstract() {
        $node = $this->createClassBuilder('Test')
            ->makeAbstract()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Class_('Test', array(
                'type' => Stmt\Class_::MODIFIER_ABSTRACT
            )),
            $node
        );
    }

    public function testFinal() {
        $node = $this->createClassBuilder('Test')
            ->makeFinal()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Class_('Test', array(
                'type' => Stmt\Class_::MODIFIER_FINAL
            )),
            $node
        );
    }

    public function testStatementOrder() {
        $method = new Stmt\ClassMethod('testMethod');
        $property = new Stmt\Property(
            Stmt\Class_::MODIFIER_PUBLIC,
            array(new Stmt\PropertyProperty('testProperty'))
        );
        $const = new Stmt\ClassConst(array(
            new Node\Const_('TEST_CONST', new Node\Scalar\String_('ABC'))
        ));
        $use = new Stmt\TraitUse(array(new Name('SomeTrait')));

        $node = $this->createClassBuilder('Test')
            ->addStmt($method)
            ->addStmt($property)
            ->addStmts(array($const, $use))
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Class_('Test', array(
                'stmts' => array($use, $const, $property, $method)
            )),
            $node
        );
    }

    public function testDocComment() {
        $docComment = <<<'DOC'
/**
 * Test
 */
DOC;
        $class = $this->createClassBuilder('Test')
            ->setDocComment($docComment)
            ->getNode();

        $this->assertEquals(
            new Stmt\Class_('Test', array(), array(
                'comments' => array(
                    new Comment\Doc($docComment)
                )
            )),
            $class
        );

        $class = $this->createClassBuilder('Test')
            ->setDocComment(new Comment\Doc($docComment))
            ->getNode();

        $this->assertEquals(
            new Stmt\Class_('Test', array(), array(
                'comments' => array(
                    new Comment\Doc($docComment)
                )
            )),
            $class
        );
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
     */
    public function testInvalidStmtError() {
        $this->createClassBuilder('Test')
            ->addStmt(new Stmt\Echo_(array()))
        ;
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Doc comment must be a string or an instance of PhpParser\Comment\Doc
     */
    public function testInvalidDocComment() {
        $this->createClassBuilder('Test')
            ->setDocComment(new Comment('Test'));
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Name cannot be empty
     */
    public function testEmptyName() {
        $this->createClassBuilder('Test')
            ->extend('');
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Name must be a string or an instance of PhpParser\Node\Name
     */
    public function testInvalidName() {
        $this->createClassBuilder('Test')
            ->extend(array('Foo'));
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr\Print_;
use PhpParser\Node\Scalar\String_;

class FunctionTest extends \PHPUnit_Framework_TestCase
{
    public function createFunctionBuilder($name) {
        return new Function_($name);
    }

    public function testReturnByRef() {
        $node = $this->createFunctionBuilder('test')
            ->makeReturnByRef()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Function_('test', array(
                'byRef' => true
            )),
            $node
        );
    }

    public function testParams() {
        $param1 = new Node\Param('test1');
        $param2 = new Node\Param('test2');
        $param3 = new Node\Param('test3');

        $node = $this->createFunctionBuilder('test')
            ->addParam($param1)
            ->addParams(array($param2, $param3))
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Function_('test', array(
                'params' => array($param1, $param2, $param3)
            )),
            $node
        );
    }

    public function testStmts() {
        $stmt1 = new Print_(new String_('test1'));
        $stmt2 = new Print_(new String_('test2'));
        $stmt3 = new Print_(new String_('test3'));

        $node = $this->createFunctionBuilder('test')
            ->addStmt($stmt1)
            ->addStmts(array($stmt2, $stmt3))
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Function_('test', array(
                'stmts' => array($stmt1, $stmt2, $stmt3)
            )),
            $node
        );
    }

    public function testDocComment() {
        $node = $this->createFunctionBuilder('test')
            ->setDocComment('/** Test */')
            ->getNode();

        $this->assertEquals(new Stmt\Function_('test', array(), array(
            'comments' => array(new Comment\Doc('/** Test */'))
        )), $node);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Expected parameter node, got "Name"
     */
    public function testInvalidParamError() {
        $this->createFunctionBuilder('test')
            ->addParam(new Node\Name('foo'))
        ;
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Comment;

class InterfaceTest extends \PHPUnit_Framework_TestCase
{
    /** @var Interface_ */
    protected $builder;

    protected function setUp() {
        $this->builder = new Interface_('Contract');
    }

    private function dump($node) {
        $pp = new \PhpParser\PrettyPrinter\Standard;
        return $pp->prettyPrint(array($node));
    }

    public function testEmpty() {
        $contract = $this->builder->getNode();
        $this->assertInstanceOf('PhpParser\Node\Stmt\Interface_', $contract);
        $this->assertSame('Contract', $contract->name);
    }

    public function testExtending() {
        $contract = $this->builder->extend('Space\Root1', 'Root2')->getNode();
        $this->assertEquals(
            new Stmt\Interface_('Contract', array(
                'extends' => array(
                    new Node\Name('Space\Root1'),
                    new Node\Name('Root2')
                ),
            )), $contract
        );
    }

    public function testAddMethod() {
        $method = new Stmt\ClassMethod('doSomething');
        $contract = $this->builder->addStmt($method)->getNode();
        $this->assertSame(array($method), $contract->stmts);
    }

    public function testAddConst() {
        $const = new Stmt\ClassConst(array(
            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458.0))
        ));
        $contract = $this->builder->addStmt($const)->getNode();
        $this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
    }

    public function testOrder() {
        $const = new Stmt\ClassConst(array(
            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
        ));
        $method = new Stmt\ClassMethod('doSomething');
        $contract = $this->builder
            ->addStmt($method)
            ->addStmt($const)
            ->getNode()
        ;

        $this->assertInstanceOf('PhpParser\Node\Stmt\ClassConst', $contract->stmts[0]);
        $this->assertInstanceOf('PhpParser\Node\Stmt\ClassMethod', $contract->stmts[1]);
    }

    public function testDocComment() {
        $node = $this->builder
            ->setDocComment('/** Test */')
            ->getNode();

        $this->assertEquals(new Stmt\Interface_('Contract', array(), array(
            'comments' => array(new Comment\Doc('/** Test */'))
        )), $node);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Unexpected node of type "Stmt_PropertyProperty"
     */
    public function testInvalidStmtError() {
        $this->builder->addStmt(new Stmt\PropertyProperty('invalid'));
    }

    public function testFullFunctional() {
        $const = new Stmt\ClassConst(array(
            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
        ));
        $method = new Stmt\ClassMethod('doSomething');
        $contract = $this->builder
            ->addStmt($method)
            ->addStmt($const)
            ->getNode()
        ;

        eval($this->dump($contract));

        $this->assertTrue(interface_exists('Contract', false));
    }
}

<?php

namespace PhpParser\Builder;

use PhpParser\Node;
use PhpParser\Node\Expr\Print_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Comment;

class MethodTest extends \PHPUnit_Framework_TestCase
{
    public function createMethodBuilder($name) {
        return new Method($name);
    }

    public function testModifiers() {
        $node = $this->createMethodBuilder('test')
            ->makePublic()
            ->makeAbstract()
            ->makeStatic()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'type' => Stmt\Class_::MODIFIER_PUBLIC
                        | Stmt\Class_::MODIFIER_ABSTRACT
                        | Stmt\Class_::MODIFIER_STATIC,
                'stmts' => null,
            )),
            $node
        );

        $node = $this->createMethodBuilder('test')
            ->makeProtected()
            ->makeFinal()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'type' => Stmt\Class_::MODIFIER_PROTECTED
                        | Stmt\Class_::MODIFIER_FINAL
            )),
            $node
        );

        $node = $this->createMethodBuilder('test')
            ->makePrivate()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'type' => Stmt\Class_::MODIFIER_PRIVATE
            )),
            $node
        );
    }

    public function testReturnByRef() {
        $node = $this->createMethodBuilder('test')
            ->makeReturnByRef()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'byRef' => true
            )),
            $node
        );
    }

    public function testParams() {
        $param1 = new Node\Param('test1');
        $param2 = new Node\Param('test2');
        $param3 = new Node\Param('test3');

        $node = $this->createMethodBuilder('test')
            ->addParam($param1)
            ->addParams(array($param2, $param3))
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'params' => array($param1, $param2, $param3)
            )),
            $node
        );
    }

    public function testStmts() {
        $stmt1 = new Print_(new String_('test1'));
        $stmt2 = new Print_(new String_('test2'));
        $stmt3 = new Print_(new String_('test3'));

        $node = $this->createMethodBuilder('test')
            ->addStmt($stmt1)
            ->addStmts(array($stmt2, $stmt3))
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\ClassMethod('test', array(
                'stmts' => array($stmt1, $stmt2, $stmt3)
            )),
            $node
        );
    }
    public function testDocComment() {
        $node = $this->createMethodBuilder('test')
            ->setDocComment('/** Test */')
            ->getNode();

        $this->assertEquals(new Stmt\ClassMethod('test', array(), array(
            'comments' => array(new Comment\Doc('/** Test */'))
        )), $node);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot add statements to an abstract method
     */
    public function testAddStmtToAbstractMethodError() {
        $this->createMethodBuilder('test')
            ->makeAbstract()
            ->addStmt(new Print_(new String_('test')))
        ;
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot make method with statements abstract
     */
    public function testMakeMethodWithStmtsAbstractError() {
        $this->createMethodBuilder('test')
            ->addStmt(new Print_(new String_('test')))
            ->makeAbstract()
        ;
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Expected parameter node, got "Name"
     */
    public function testInvalidParamError() {
        $this->createMethodBuilder('test')
            ->addParam(new Node\Name('foo'))
        ;
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Node;
use PhpParser\Node\Stmt;

class NamespaceTest extends \PHPUnit_Framework_TestCase
{
    protected function createNamespaceBuilder($fqn) {
        return new Namespace_($fqn);
    }

    public function testCreation() {
        $stmt1 = new Stmt\Class_('SomeClass');
        $stmt2 = new Stmt\Interface_('SomeInterface');
        $stmt3 = new Stmt\Function_('someFunction');
        $expected = new Stmt\Namespace_(
            new Node\Name('Name\Space'),
            array($stmt1, $stmt2, $stmt3)
        );

        $node = $this->createNamespaceBuilder('Name\Space')
            ->addStmt($stmt1)
            ->addStmts(array($stmt2, $stmt3))
            ->getNode()
        ;
        $this->assertEquals($expected, $node);

        $node = $this->createNamespaceBuilder(new Node\Name(array('Name', 'Space')))
            ->addStmts(array($stmt1, $stmt2))
            ->addStmt($stmt3)
            ->getNode()
        ;
        $this->assertEquals($expected, $node);

        $node = $this->createNamespaceBuilder(null)->getNode();
        $this->assertNull($node->name);
        $this->assertEmpty($node->stmts);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;

class ParamTest extends \PHPUnit_Framework_TestCase
{
    public function createParamBuilder($name) {
        return new Param($name);
    }

    /**
     * @dataProvider provideTestDefaultValues
     */
    public function testDefaultValues($value, $expectedValueNode) {
        $node = $this->createParamBuilder('test')
            ->setDefault($value)
            ->getNode()
        ;

        $this->assertEquals($expectedValueNode, $node->default);
    }

    public function provideTestDefaultValues() {
        return array(
            array(
                null,
                new Expr\ConstFetch(new Node\Name('null'))
            ),
            array(
                true,
                new Expr\ConstFetch(new Node\Name('true'))
            ),
            array(
                false,
                new Expr\ConstFetch(new Node\Name('false'))
            ),
            array(
                31415,
                new Scalar\LNumber(31415)
            ),
            array(
                3.1415,
                new Scalar\DNumber(3.1415)
            ),
            array(
                'Hallo World',
                new Scalar\String_('Hallo World')
            ),
            array(
                array(1, 2, 3),
                new Expr\Array_(array(
                    new Expr\ArrayItem(new Scalar\LNumber(1)),
                    new Expr\ArrayItem(new Scalar\LNumber(2)),
                    new Expr\ArrayItem(new Scalar\LNumber(3)),
                ))
            ),
            array(
                array('foo' => 'bar', 'bar' => 'foo'),
                new Expr\Array_(array(
                    new Expr\ArrayItem(
                        new Scalar\String_('bar'),
                        new Scalar\String_('foo')
                    ),
                    new Expr\ArrayItem(
                        new Scalar\String_('foo'),
                        new Scalar\String_('bar')
                    ),
                ))
            ),
            array(
                new Scalar\MagicConst\Dir,
                new Scalar\MagicConst\Dir
            )
        );
    }

    public function testTypeHints() {
        $node = $this->createParamBuilder('test')
            ->setTypeHint('array')
            ->getNode()
        ;

        $this->assertEquals(
            new Node\Param('test', null, 'array'),
            $node
        );

        $node = $this->createParamBuilder('test')
            ->setTypeHint('callable')
            ->getNode()
        ;

        $this->assertEquals(
            new Node\Param('test', null, 'callable'),
            $node
        );

        $node = $this->createParamBuilder('test')
            ->setTypeHint('Some\Class')
            ->getNode()
        ;

        $this->assertEquals(
            new Node\Param('test', null, new Node\Name('Some\Class')),
            $node
        );
    }

    public function testByRef() {
        $node = $this->createParamBuilder('test')
            ->makeByRef()
            ->getNode()
        ;

        $this->assertEquals(
            new Node\Param('test', null, null, true),
            $node
        );
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
use PhpParser\Comment;

class PropertyTest extends \PHPUnit_Framework_TestCase
{
    public function createPropertyBuilder($name) {
        return new Property($name);
    }

    public function testModifiers() {
        $node = $this->createPropertyBuilder('test')
            ->makePrivate()
            ->makeStatic()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Property(
                Stmt\Class_::MODIFIER_PRIVATE
              | Stmt\Class_::MODIFIER_STATIC,
                array(
                    new Stmt\PropertyProperty('test')
                )
            ),
            $node
        );

        $node = $this->createPropertyBuilder('test')
            ->makeProtected()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Property(
                Stmt\Class_::MODIFIER_PROTECTED,
                array(
                    new Stmt\PropertyProperty('test')
                )
            ),
            $node
        );

        $node = $this->createPropertyBuilder('test')
            ->makePublic()
            ->getNode()
        ;

        $this->assertEquals(
            new Stmt\Property(
                Stmt\Class_::MODIFIER_PUBLIC,
                array(
                    new Stmt\PropertyProperty('test')
                )
            ),
            $node
        );
    }

    public function testDocComment() {
        $node = $this->createPropertyBuilder('test')
            ->setDocComment('/** Test */')
            ->getNode();

        $this->assertEquals(new Stmt\Property(
            Stmt\Class_::MODIFIER_PUBLIC,
            array(
                new Stmt\PropertyProperty('test')
            ),
            array(
                'comments' => array(new Comment\Doc('/** Test */'))
            )
        ), $node);
    }

    /**
     * @dataProvider provideTestDefaultValues
     */
    public function testDefaultValues($value, $expectedValueNode) {
        $node = $this->createPropertyBuilder('test')
            ->setDefault($value)
            ->getNode()
        ;

        $this->assertEquals($expectedValueNode, $node->props[0]->default);
    }

    public function provideTestDefaultValues() {
        return array(
            array(
                null,
                new Expr\ConstFetch(new Name('null'))
            ),
            array(
                true,
                new Expr\ConstFetch(new Name('true'))
            ),
            array(
                false,
                new Expr\ConstFetch(new Name('false'))
            ),
            array(
                31415,
                new Scalar\LNumber(31415)
            ),
            array(
                3.1415,
                new Scalar\DNumber(3.1415)
            ),
            array(
                'Hallo World',
                new Scalar\String_('Hallo World')
            ),
            array(
                array(1, 2, 3),
                new Expr\Array_(array(
                    new Expr\ArrayItem(new Scalar\LNumber(1)),
                    new Expr\ArrayItem(new Scalar\LNumber(2)),
                    new Expr\ArrayItem(new Scalar\LNumber(3)),
                ))
            ),
            array(
                array('foo' => 'bar', 'bar' => 'foo'),
                new Expr\Array_(array(
                    new Expr\ArrayItem(
                        new Scalar\String_('bar'),
                        new Scalar\String_('foo')
                    ),
                    new Expr\ArrayItem(
                        new Scalar\String_('foo'),
                        new Scalar\String_('bar')
                    ),
                ))
            ),
            array(
                new Scalar\MagicConst\Dir,
                new Scalar\MagicConst\Dir
            )
        );
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class TraitTest extends \PHPUnit_Framework_TestCase
{
    protected function createTraitBuilder($class) {
        return new Trait_($class);
    }

    public function testStmtAddition() {
        $method1 = new Stmt\ClassMethod('test1');
        $method2 = new Stmt\ClassMethod('test2');
        $method3 = new Stmt\ClassMethod('test3');
        $prop = new Stmt\Property(Stmt\Class_::MODIFIER_PUBLIC, array(
            new Stmt\PropertyProperty('test')
        ));
        $trait = $this->createTraitBuilder('TestTrait')
            ->setDocComment('/** Nice trait */')
            ->addStmt($method1)
            ->addStmts(array($method2, $method3))
            ->addStmt($prop)
            ->getNode();
        $this->assertEquals(new Stmt\Trait_('TestTrait', array(
            $prop, $method1, $method2, $method3
        ), array(
            'comments' => array(
                new Comment\Doc('/** Nice trait */')
            )
        )), $trait);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
     */
    public function testInvalidStmtError() {
        $this->createTraitBuilder('Test')
            ->addStmt(new Stmt\Echo_(array()))
        ;
    }
}
<?php

use PhpParser\Builder;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class UseTest extends \PHPUnit_Framework_TestCase
{
    protected function createUseBuilder($name, $type = Stmt\Use_::TYPE_NORMAL) {
        return new Builder\Use_($name, $type);
    }

    public function testCreation() {
        $node = $this->createUseBuilder('Foo\Bar')->getNode();
        $this->assertEquals(new Stmt\Use_(array(
            new Stmt\UseUse(new Name('Foo\Bar'), 'Bar')
        )), $node);

        $node = $this->createUseBuilder(new Name('Foo\Bar'))->as('XYZ')->getNode();
        $this->assertEquals(new Stmt\Use_(array(
            new Stmt\UseUse(new Name('Foo\Bar'), 'XYZ')
        )), $node);

        $node = $this->createUseBuilder('foo\bar', Stmt\Use_::TYPE_FUNCTION)->as('foo')->getNode();
        $this->assertEquals(new Stmt\Use_(array(
            new Stmt\UseUse(new Name('foo\bar'), 'foo')
        ), Stmt\Use_::TYPE_FUNCTION), $node);
    }

    public function testNonExistingMethod() {
        $this->setExpectedException('LogicException', 'Method "foo" does not exist');
        $builder = $this->createUseBuilder('Test');
        $builder->foo();
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Expr;

class BuilderFactoryTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provideTestFactory
     */
    public function testFactory($methodName, $className) {
        $factory = new BuilderFactory;
        $this->assertInstanceOf($className, $factory->$methodName('test'));
    }

    public function provideTestFactory() {
        return array(
            array('namespace', 'PhpParser\Builder\Namespace_'),
            array('class',     'PhpParser\Builder\Class_'),
            array('interface', 'PhpParser\Builder\Interface_'),
            array('trait',     'PhpParser\Builder\Trait_'),
            array('method',    'PhpParser\Builder\Method'),
            array('function',  'PhpParser\Builder\Function_'),
            array('property',  'PhpParser\Builder\Property'),
            array('param',     'PhpParser\Builder\Param'),
            array('use',       'PhpParser\Builder\Use_'),
        );
    }

    public function testNonExistingMethod() {
        $this->setExpectedException('LogicException', 'Method "foo" does not exist');
        $factory = new BuilderFactory();
        $factory->foo();
    }

    public function testIntegration() {
        $factory = new BuilderFactory;
        $node = $factory->namespace('Name\Space')
            ->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
            ->addStmt($factory->use('Foo\Bar')->as('A'))
            ->addStmt($factory
                ->class('SomeClass')
                ->extend('SomeOtherClass')
                ->implement('A\Few', '\Interfaces')
                ->makeAbstract()

                ->addStmt($factory->method('firstMethod'))

                ->addStmt($factory->method('someMethod')
                    ->makePublic()
                    ->makeAbstract()
                    ->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
                    ->setDocComment('/**
                                      * This method does something.
                                      *
                                      * @param SomeClass And takes a parameter
                                      */'))

                ->addStmt($factory->method('anotherMethod')
                    ->makeProtected()
                    ->addParam($factory->param('someParam')->setDefault('test'))
                    ->addStmt(new Expr\Print_(new Expr\Variable('someParam'))))

                ->addStmt($factory->property('someProperty')->makeProtected())
                ->addStmt($factory->property('anotherProperty')
                    ->makePrivate()
                    ->setDefault(array(1, 2, 3))))
            ->getNode()
        ;

        $expected = <<<'EOC'
<?php

namespace Name\Space;

use Foo\Bar\SomeOtherClass;
use Foo\Bar as A;
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
{
    protected $someProperty;
    private $anotherProperty = array(1, 2, 3);
    function firstMethod()
    {
    }
    /**
     * This method does something.
     *
     * @param SomeClass And takes a parameter
     */
    public abstract function someMethod(SomeClass $someParam);
    protected function anotherMethod($someParam = 'test')
    {
        print $someParam;
    }
}
EOC;

        $stmts = array($node);
        $prettyPrinter = new PrettyPrinter\Standard();
        $generated = $prettyPrinter->prettyPrintFile($stmts);

        $this->assertEquals(
            str_replace("\r\n", "\n", $expected),
            str_replace("\r\n", "\n", $generated)
        );
    }
}
<?php

namespace PhpParser;

abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
{
    protected function getTests($directory, $fileExtension) {
        $it = new \RecursiveDirectoryIterator($directory);
        $it = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::LEAVES_ONLY);
        $it = new \RegexIterator($it, '(\.' . preg_quote($fileExtension) . '$)');

        $tests = array();
        foreach ($it as $file) {
            $fileName = realpath($file->getPathname());
            $fileContents = file_get_contents($fileName);

            // evaluate @@{expr}@@ expressions
            $fileContents = preg_replace_callback(
                '/@@\{(.*?)\}@@/',
                array($this, 'evalCallback'),
                $fileContents
            );

            // parse sections
            $parts = array_map('trim', explode('-----', $fileContents));

            // first part is the name
            $name = array_shift($parts) . ' (' . $fileName . ')';

            // multiple sections possible with always two forming a pair
            foreach (array_chunk($parts, 2) as $chunk) {
                $tests[] = array($name, $chunk[0], $chunk[1]);
            }
        }

        return $tests;
    }

    protected function evalCallback($matches) {
        return eval('return ' . $matches[1] . ';');
    }

    protected function canonicalize($str) {
        // trim from both sides
        $str = trim($str);

        // normalize EOL to \n
        $str = str_replace(array("\r\n", "\r"), "\n", $str);

        // trim right side of all lines
        return implode("\n", array_map('rtrim', explode("\n", $str)));
    }
}
<?php

namespace PhpParser;

class CommentTest extends \PHPUnit_Framework_TestCase
{
    public function testGetSet() {
        $comment = new Comment('/* Some comment */', 1);

        $this->assertSame('/* Some comment */', $comment->getText());
        $this->assertSame('/* Some comment */', (string) $comment);
        $this->assertSame(1, $comment->getLine());

        $comment->setText('/* Some other comment */');
        $comment->setLine(10);

        $this->assertSame('/* Some other comment */', $comment->getText());
        $this->assertSame('/* Some other comment */', (string) $comment);
        $this->assertSame(10, $comment->getLine());
    }

    /**
     * @dataProvider provideTestReformatting
     */
    public function testReformatting($commentText, $reformattedText) {
        $comment = new Comment($commentText);
        $this->assertSame($reformattedText, $comment->getReformattedText());
    }

    public function provideTestReformatting() {
        return array(
            array('// Some text' . "\n", '// Some text'),
            array('/* Some text */', '/* Some text */'),
            array(
                '/**
     * Some text.
     * Some more text.
     */',
                '/**
 * Some text.
 * Some more text.
 */'
            ),
            array(
                '/*
        Some text.
        Some more text.
    */',
                '/*
    Some text.
    Some more text.
*/'
            ),
            array(
                '/* Some text.
       More text.
       Even more text. */',
                '/* Some text.
   More text.
   Even more text. */'
            ),
            // invalid comment -> no reformatting
            array(
                'hallo
    world',
                'hallo
    world',
            ),
        );
    }
}<?php

namespace PhpParser;

class ErrorTest extends \PHPUnit_Framework_TestCase
{
    public function testConstruct() {
        $attributes = array(
            'startLine' => 10,
            'endLine' => 11,
        );
        $error = new Error('Some error', $attributes);

        $this->assertSame('Some error', $error->getRawMessage());
        $this->assertSame($attributes, $error->getAttributes());
        $this->assertSame(10, $error->getStartLine());
        $this->assertSame(11, $error->getEndLine());
        $this->assertSame(10, $error->getRawLine());
        $this->assertSame('Some error on line 10', $error->getMessage());

        return $error;
    }

    /**
     * @depends testConstruct
     */
    public function testSetMessageAndLine(Error $error) {
        $error->setRawMessage('Some other error');
        $this->assertSame('Some other error', $error->getRawMessage());

        $error->setStartLine(15);
        $this->assertSame(15, $error->getStartLine());
        $this->assertSame('Some other error on line 15', $error->getMessage());

        $error->setRawLine(17);
        $this->assertSame(17, $error->getRawLine());
        $this->assertSame('Some other error on line 17', $error->getMessage());
    }

    public function testUnknownLine() {
        $error = new Error('Some error');

        $this->assertSame(-1, $error->getStartLine());
        $this->assertSame(-1, $error->getEndLine());
        $this->assertSame(-1, $error->getRawLine());
        $this->assertSame('Some error on unknown line', $error->getMessage());
    }

    /** @dataProvider provideTestColumnInfo */
    public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn) {
        $error = new Error('Some error', array(
            'startFilePos' => $startPos,
            'endFilePos' => $endPos,
        ));

        $this->assertSame(true, $error->hasColumnInfo());
        $this->assertSame($startColumn, $error->getStartColumn($code));
        $this->assertSame($endColumn, $error->getEndColumn($code));

    }

    public function provideTestColumnInfo() {
        return array(
            // Error at "bar"
            array("<?php foo bar baz", 10, 12, 11, 13),
            array("<?php\nfoo bar baz", 10, 12, 5, 7),
            array("<?php foo\nbar baz", 10, 12, 1, 3),
            array("<?php foo bar\nbaz", 10, 12, 11, 13),
            array("<?php\r\nfoo bar baz", 11, 13, 5, 7),
            // Error at "baz"
            array("<?php foo bar baz", 14, 16, 15, 17),
            array("<?php foo bar\nbaz", 14, 16, 1, 3),
            // Error at string literal
            array("<?php foo 'bar\nbaz' xyz", 10, 18, 11, 4),
            array("<?php\nfoo 'bar\nbaz' xyz", 10, 18, 5, 4),
            array("<?php foo\n'\nbarbaz\n'\nxyz", 10, 19, 1, 1),
            // Error over full string
            array("<?php", 0, 4, 1, 5),
            array("<?\nphp", 0, 5, 1, 3),
        );
    }

    public function testNoColumnInfo() {
        $error = new Error('Some error', 3);

        $this->assertSame(false, $error->hasColumnInfo());
        try {
            $error->getStartColumn('');
            $this->fail('Expected RuntimeException');
        } catch (\RuntimeException $e) {
            $this->assertSame('Error does not have column information', $e->getMessage());
        }
        try {
            $error->getEndColumn('');
            $this->fail('Expected RuntimeException');
        } catch (\RuntimeException $e) {
            $this->assertSame('Error does not have column information', $e->getMessage());
        }
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Invalid position information
     */
    public function testInvalidPosInfo() {
        $error = new Error('Some error', array(
            'startFilePos' => 10,
            'endFilePos' => 11,
        ));
        $error->getStartColumn('code');
    }
}
<?php

namespace PhpParser\Lexer;

use PhpParser\LexerTest;
use PhpParser\Parser;

require_once __DIR__ . '/../LexerTest.php';

class EmulativeTest extends LexerTest
{
    protected function getLexer(array $options = array()) {
        return new Emulative($options);
    }

    /**
     * @dataProvider provideTestReplaceKeywords
     */
    public function testReplaceKeywords($keyword, $expectedToken) {
        $lexer = $this->getLexer();
        $lexer->startLexing('<?php ' . $keyword);

        $this->assertSame($expectedToken, $lexer->getNextToken());
        $this->assertSame(0, $lexer->getNextToken());
    }

    /**
     * @dataProvider provideTestReplaceKeywords
     */
    public function testNoReplaceKeywordsAfterObjectOperator($keyword) {
        $lexer = $this->getLexer();
        $lexer->startLexing('<?php ->' . $keyword);

        $this->assertSame(Parser::T_OBJECT_OPERATOR, $lexer->getNextToken());
        $this->assertSame(Parser::T_STRING, $lexer->getNextToken());
        $this->assertSame(0, $lexer->getNextToken());
    }

    public function provideTestReplaceKeywords() {
        return array(
            // PHP 5.5
            array('finally',       Parser::T_FINALLY),
            array('yield',         Parser::T_YIELD),

            // PHP 5.4
            array('callable',      Parser::T_CALLABLE),
            array('insteadof',     Parser::T_INSTEADOF),
            array('trait',         Parser::T_TRAIT),
            array('__TRAIT__',     Parser::T_TRAIT_C),

            // PHP 5.3
            array('__DIR__',       Parser::T_DIR),
            array('goto',          Parser::T_GOTO),
            array('namespace',     Parser::T_NAMESPACE),
            array('__NAMESPACE__', Parser::T_NS_C),
        );
    }

    /**
     * @dataProvider provideTestLexNewFeatures
     */
    public function testLexNewFeatures($code, array $expectedTokens) {
        $lexer = $this->getLexer();
        $lexer->startLexing('<?php ' . $code);

        foreach ($expectedTokens as $expectedToken) {
            list($expectedTokenType, $expectedTokenText) = $expectedToken;
            $this->assertSame($expectedTokenType, $lexer->getNextToken($text));
            $this->assertSame($expectedTokenText, $text);
        }
        $this->assertSame(0, $lexer->getNextToken());
    }

    /**
     * @dataProvider provideTestLexNewFeatures
     */
    public function testLeaveStuffAloneInStrings($code) {
        $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';

        $lexer = $this->getLexer();
        $lexer->startLexing('<?php ' . $stringifiedToken);

        $this->assertSame(Parser::T_CONSTANT_ENCAPSED_STRING, $lexer->getNextToken($text));
        $this->assertSame($stringifiedToken, $text);
        $this->assertSame(0, $lexer->getNextToken());
    }

    public function provideTestLexNewFeatures() {
        return array(
            array('yield from', array(
                array(Parser::T_YIELD_FROM, 'yield from'),
            )),
            array("yield\r\nfrom", array(
                array(Parser::T_YIELD_FROM, "yield\r\nfrom"),
            )),
            array('...', array(
                array(Parser::T_ELLIPSIS, '...'),
            )),
            array('**', array(
                array(Parser::T_POW, '**'),
            )),
            array('**=', array(
                array(Parser::T_POW_EQUAL, '**='),
            )),
            array('??', array(
                array(Parser::T_COALESCE, '??'),
            )),
            array('<=>', array(
                array(Parser::T_SPACESHIP, '<=>'),
            )),
            array('0b1010110', array(
                array(Parser::T_LNUMBER, '0b1010110'),
            )),
            array('0b1011010101001010110101010010101011010101010101101011001110111100', array(
                array(Parser::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'),
            )),
            array('\\', array(
                array(Parser::T_NS_SEPARATOR, '\\'),
            )),
            array("<<<'NOWDOC'\nNOWDOC;\n", array(
                array(Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
                array(Parser::T_END_HEREDOC, 'NOWDOC'),
                array(ord(';'), ';'),
            )),
            array("<<<'NOWDOC'\nFoobar\nNOWDOC;\n", array(
                array(Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
                array(Parser::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"),
                array(Parser::T_END_HEREDOC, 'NOWDOC'),
                array(ord(';'), ';'),
            )),
        );
    }
}
<?php

namespace PhpParser;

class LexerTest extends \PHPUnit_Framework_TestCase
{
    /* To allow overwriting in parent class */
    protected function getLexer(array $options = array()) {
        return new Lexer($options);
    }

    /**
     * @dataProvider provideTestError
     */
    public function testError($code, $message) {
        if (defined('HHVM_VERSION')) {
            $this->markTestSkipped('HHVM does not throw warnings from token_get_all()');
        }

        $lexer = $this->getLexer();
        try {
            $lexer->startLexing($code);
        } catch (Error $e) {
            $this->assertSame($message, $e->getMessage());

            return;
        }

        $this->fail('Expected PhpParser\Error');
    }

    public function provideTestError() {
        return array(
            array('<?php /*', 'Unterminated comment on line 1'),
            array('<?php ' . "\1", 'Unexpected character "' . "\1" . '" (ASCII 1) on unknown line'),
            array('<?php ' . "\0", 'Unexpected null byte on unknown line'),
        );
    }

    /**
     * @dataProvider provideTestLex
     */
    public function testLex($code, $options, $tokens) {
        $lexer = $this->getLexer($options);
        $lexer->startLexing($code);
        while ($id = $lexer->getNextToken($value, $startAttributes, $endAttributes)) {
            $token = array_shift($tokens);

            $this->assertSame($token[0], $id);
            $this->assertSame($token[1], $value);
            $this->assertEquals($token[2], $startAttributes);
            $this->assertEquals($token[3], $endAttributes);
        }
    }

    public function provideTestLex() {
        return array(
            // tests conversion of closing PHP tag and drop of whitespace and opening tags
            array(
                '<?php tokens ?>plaintext',
                array(),
                array(
                    array(
                        Parser::T_STRING, 'tokens',
                        array('startLine' => 1), array('endLine' => 1)
                    ),
                    array(
                        ord(';'), '?>',
                        array('startLine' => 1), array('endLine' => 1)
                    ),
                    array(
                        Parser::T_INLINE_HTML, 'plaintext',
                        array('startLine' => 1), array('endLine' => 1)
                    ),
                )
            ),
            // tests line numbers
            array(
                '<?php' . "\n" . '$ token /** doc' . "\n" . 'comment */ $',
                array(),
                array(
                    array(
                        ord('$'), '$',
                        array('startLine' => 2), array('endLine' => 2)
                    ),
                    array(
                        Parser::T_STRING, 'token',
                        array('startLine' => 2), array('endLine' => 2)
                    ),
                    array(
                        ord('$'), '$',
                        array(
                            'startLine' => 3,
                            'comments' => array(new Comment\Doc('/** doc' . "\n" . 'comment */', 2))
                        ),
                        array('endLine' => 3)
                    ),
                )
            ),
            // tests comment extraction
            array(
                '<?php /* comment */ // comment' . "\n" . '/** docComment 1 *//** docComment 2 */ token',
                array(),
                array(
                    array(
                        Parser::T_STRING, 'token',
                        array(
                            'startLine' => 2,
                            'comments' => array(
                                new Comment('/* comment */', 1),
                                new Comment('// comment' . "\n", 1),
                                new Comment\Doc('/** docComment 1 */', 2),
                                new Comment\Doc('/** docComment 2 */', 2),
                            ),
                        ),
                        array('endLine' => 2)
                    ),
                )
            ),
            // tests differing start and end line
            array(
                '<?php "foo' . "\n" . 'bar"',
                array(),
                array(
                    array(
                        Parser::T_CONSTANT_ENCAPSED_STRING, '"foo' . "\n" . 'bar"',
                        array('startLine' => 1), array('endLine' => 2)
                    ),
                )
            ),
            // tests exact file offsets
            array(
                '<?php "a";' . "\n" . '// foo' . "\n" . '"b";',
                array('usedAttributes' => array('startFilePos', 'endFilePos')),
                array(
                    array(
                        Parser::T_CONSTANT_ENCAPSED_STRING, '"a"',
                        array('startFilePos' => 6), array('endFilePos' => 8)
                    ),
                    array(
                        ord(';'), ';',
                        array('startFilePos' => 9), array('endFilePos' => 9)
                    ),
                    array(
                        Parser::T_CONSTANT_ENCAPSED_STRING, '"b"',
                        array('startFilePos' => 18), array('endFilePos' => 20)
                    ),
                    array(
                        ord(';'), ';',
                        array('startFilePos' => 21), array('endFilePos' => 21)
                    ),
                )
            ),
            // tests token offsets
            array(
                '<?php "a";' . "\n" . '// foo' . "\n" . '"b";',
                array('usedAttributes' => array('startTokenPos', 'endTokenPos')),
                array(
                    array(
                        Parser::T_CONSTANT_ENCAPSED_STRING, '"a"',
                        array('startTokenPos' => 1), array('endTokenPos' => 1)
                    ),
                    array(
                        ord(';'), ';',
                        array('startTokenPos' => 2), array('endTokenPos' => 2)
                    ),
                    array(
                        Parser::T_CONSTANT_ENCAPSED_STRING, '"b"',
                        array('startTokenPos' => 5), array('endTokenPos' => 5)
                    ),
                    array(
                        ord(';'), ';',
                        array('startTokenPos' => 6), array('endTokenPos' => 6)
                    ),
                )
            ),
            // tests all attributes being disabled
            array(
                '<?php /* foo */ $bar;',
                array('usedAttributes' => array()),
                array(
                    array(
                        Parser::T_VARIABLE, '$bar',
                        array(), array()
                    ),
                    array(
                        ord(';'), ';',
                        array(), array()
                    )
                )
            )
        );
    }

    /**
     * @dataProvider provideTestHaltCompiler
     */
    public function testHandleHaltCompiler($code, $remaining) {
        $lexer = $this->getLexer();
        $lexer->startLexing($code);

        while (Parser::T_HALT_COMPILER !== $lexer->getNextToken());

        $this->assertSame($remaining, $lexer->handleHaltCompiler());
        $this->assertSame(0, $lexer->getNextToken());
    }

    public function provideTestHaltCompiler() {
        return array(
            array('<?php ... __halt_compiler();Remaining Text', 'Remaining Text'),
            array('<?php ... __halt_compiler ( ) ;Remaining Text', 'Remaining Text'),
            array('<?php ... __halt_compiler() ?>Remaining Text', 'Remaining Text'),
            //array('<?php ... __halt_compiler();' . "\0", "\0"),
            //array('<?php ... __halt_compiler /* */ ( ) ;Remaining Text', 'Remaining Text'),
        );
    }

    /**
     * @expectedException \PhpParser\Error
     * @expectedExceptionMessage __HALT_COMPILER must be followed by "();"
     */
    public function testHandleHaltCompilerError() {
        $lexer = $this->getLexer();
        $lexer->startLexing('<?php ... __halt_compiler invalid ();');

        while (Parser::T_HALT_COMPILER !== $lexer->getNextToken());
        $lexer->handleHaltCompiler();
    }

    public function testGetTokens() {
        $code = '<?php "a";' . "\n" . '// foo' . "\n" . '"b";';
        $expectedTokens = array(
            array(T_OPEN_TAG, '<?php ', 1),
            array(T_CONSTANT_ENCAPSED_STRING, '"a"', 1),
            ';',
            array(T_WHITESPACE, "\n", 1),
            array(T_COMMENT, '// foo' . "\n", 2),
            array(T_CONSTANT_ENCAPSED_STRING, '"b"', 3),
            ';',
        );

        $lexer = $this->getLexer();
        $lexer->startLexing($code);
        $this->assertSame($expectedTokens, $lexer->getTokens());
    }
}
<?php

namespace PhpParser\Node;

class NameTest extends \PHPUnit_Framework_TestCase
{
    public function testConstruct() {
        $name = new Name(array('foo', 'bar'));
        $this->assertSame(array('foo', 'bar'), $name->parts);

        $name = new Name('foo\bar');
        $this->assertSame(array('foo', 'bar'), $name->parts);
    }

    public function testGet() {
        $name = new Name('foo');
        $this->assertSame('foo', $name->getFirst());
        $this->assertSame('foo', $name->getLast());

        $name = new Name('foo\bar');
        $this->assertSame('foo', $name->getFirst());
        $this->assertSame('bar', $name->getLast());
    }

    public function testToString() {
        $name = new Name('foo\bar');

        $this->assertSame('foo\bar', (string) $name);
        $this->assertSame('foo\bar', $name->toString());
        $this->assertSame('foo_bar', $name->toString('_'));
    }

    public function testSet() {
        $name = new Name('foo');

        $name->set('foo\bar');
        $this->assertSame('foo\bar', $name->toString());

        $name->set(array('foo', 'bar'));
        $this->assertSame('foo\bar', $name->toString());

        $name->set(new Name('foo\bar'));
        $this->assertSame('foo\bar', $name->toString());
    }

    public function testSetFirst() {
        $name = new Name('foo');

        $name->setFirst('bar');
        $this->assertSame('bar', $name->toString());

        $name->setFirst('A\B');
        $this->assertSame('A\B', $name->toString());

        $name->setFirst('C');
        $this->assertSame('C\B', $name->toString());

        $name->setFirst('D\E');
        $this->assertSame('D\E\B', $name->toString());
    }

    public function testSetLast() {
        $name = new Name('foo');

        $name->setLast('bar');
        $this->assertSame('bar', $name->toString());

        $name->setLast('A\B');
        $this->assertSame('A\B', $name->toString());

        $name->setLast('C');
        $this->assertSame('A\C', $name->toString());

        $name->setLast('D\E');
        $this->assertSame('A\D\E', $name->toString());
    }

    public function testAppend() {
        $name = new Name('foo');

        $name->append('bar');
        $this->assertSame('foo\bar', $name->toString());

        $name->append('bar\foo');
        $this->assertSame('foo\bar\bar\foo', $name->toString());
    }

    public function testPrepend() {
        $name = new Name('foo');

        $name->prepend('bar');
        $this->assertSame('bar\foo', $name->toString());

        $name->prepend('foo\bar');
        $this->assertSame('foo\bar\bar\foo', $name->toString());
    }

    public function testIs() {
        $name = new Name('foo');
        $this->assertTrue ($name->isUnqualified());
        $this->assertFalse($name->isQualified());
        $this->assertFalse($name->isFullyQualified());
        $this->assertFalse($name->isRelative());

        $name = new Name('foo\bar');
        $this->assertFalse($name->isUnqualified());
        $this->assertTrue ($name->isQualified());
        $this->assertFalse($name->isFullyQualified());
        $this->assertFalse($name->isRelative());

        $name = new Name\FullyQualified('foo');
        $this->assertFalse($name->isUnqualified());
        $this->assertFalse($name->isQualified());
        $this->assertTrue ($name->isFullyQualified());
        $this->assertFalse($name->isRelative());

        $name = new Name\Relative('foo');
        $this->assertFalse($name->isUnqualified());
        $this->assertFalse($name->isQualified());
        $this->assertFalse($name->isFullyQualified());
        $this->assertTrue ($name->isRelative());
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage When changing a name you need to pass either a string, an array or a Name node
     */
    public function testInvalidArg() {
        $name = new Name('foo');
        $name->set(new \stdClass);
    }
}<?php

namespace PhpParser\Node\Scalar;

class MagicConstTest extends \PHPUnit_Framework_TestCase {
    /**
     * @dataProvider provideTestGetName
     */
    public function testGetName(MagicConst $magicConst, $name) {
        $this->assertSame($name, $magicConst->getName());
    }

    public function provideTestGetName() {
        return array(
            array(new MagicConst\Class_, '__CLASS__'),
            array(new MagicConst\Dir, '__DIR__'),
            array(new MagicConst\File, '__FILE__'),
            array(new MagicConst\Function_, '__FUNCTION__'),
            array(new MagicConst\Line, '__LINE__'),
            array(new MagicConst\Method, '__METHOD__'),
            array(new MagicConst\Namespace_, '__NAMESPACE__'),
            array(new MagicConst\Trait_, '__TRAIT__'),
        );
    }
}<?php

namespace PhpParser\Node\Scalar;

class StringTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provideTestParseEscapeSequences
     */
    public function testParseEscapeSequences($expected, $string, $quote) {
        $this->assertSame(
            $expected,
            String_::parseEscapeSequences($string, $quote)
        );
    }

    /**
     * @dataProvider provideTestParse
     */
    public function testCreate($expected, $string) {
        $this->assertSame(
            $expected,
            String_::parse($string)
        );
    }

    public function provideTestParseEscapeSequences() {
        return array(
            array('"',              '\\"',              '"'),
            array('\\"',            '\\"',              '`'),
            array('\\"\\`',         '\\"\\`',           null),
            array("\\\$\n\r\t\f\v", '\\\\\$\n\r\t\f\v', null),
            array("\x1B",           '\e',               null),
            array(chr(255),         '\xFF',             null),
            array(chr(255),         '\377',             null),
            array(chr(0),           '\400',             null),
            array("\0",             '\0',               null),
            array('\xFF',           '\\\\xFF',          null),
        );
    }

    public function provideTestParse() {
        $tests = array(
            array('A', '\'A\''),
            array('A', 'b\'A\''),
            array('A', '"A"'),
            array('A', 'b"A"'),
            array('\\', '\'\\\\\''),
            array('\'', '\'\\\'\''),
        );

        foreach ($this->provideTestParseEscapeSequences() as $i => $test) {
            // skip second and third tests, they aren't for double quotes
            if ($i != 1 && $i != 2) {
                $tests[] = array($test[0], '"' . $test[1] . '"');
            }
        }

        return $tests;
    }
}
<?php

namespace PhpParser\Node\Stmt;

class ClassMethodTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provideModifiers
     */
    public function testModifiers($modifier) {
        $node = new ClassMethod('foo', array(
            'type' => constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier))
        ));

        $this->assertTrue($node->{'is' . $modifier}());
    }

    public function testNoModifiers() {
        $node = new ClassMethod('foo', array('type' => 0));

        $this->assertTrue($node->isPublic());
        $this->assertFalse($node->isProtected());
        $this->assertFalse($node->isPrivate());
        $this->assertFalse($node->isAbstract());
        $this->assertFalse($node->isFinal());
        $this->assertFalse($node->isStatic());
    }

    public function provideModifiers() {
        return array(
            array('public'),
            array('protected'),
            array('private'),
            array('abstract'),
            array('final'),
            array('static'),
        );
    }

    /**
     * Checks that implicit public modifier detection for method is working
     *
     * @dataProvider implicitPublicModifiers
     *
     * @param integer $modifier Node type modifier
     */
    public function testImplicitPublic($modifier)
    {
        $node = new ClassMethod('foo', array(
            'type' => constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier))
        ));

        $this->assertTrue($node->isPublic(), 'Node should be implicitly public');
    }

    public function implicitPublicModifiers() {
        return array(
            array('abstract'),
            array('final'),
            array('static'),
        );
    }
}
<?php

namespace PhpParser\Node\Stmt;

class ClassTest extends \PHPUnit_Framework_TestCase
{
    public function testIsAbstract() {
        $class = new Class_('Foo', array('type' => Class_::MODIFIER_ABSTRACT));
        $this->assertTrue($class->isAbstract());

        $class = new Class_('Foo');
        $this->assertFalse($class->isAbstract());
    }

    public function testIsFinal() {
        $class = new Class_('Foo', array('type' => Class_::MODIFIER_FINAL));
        $this->assertTrue($class->isFinal());

        $class = new Class_('Foo');
        $this->assertFalse($class->isFinal());
    }

    public function testGetMethods() {
        $methods = array(
            new ClassMethod('foo'),
            new ClassMethod('bar'),
            new ClassMethod('fooBar'),
        );
        $class = new Class_('Foo', array(
            'stmts' => array(
                new TraitUse(array()),
                $methods[0],
                new ClassConst(array()),
                $methods[1],
                new Property(0, array()),
                $methods[2],
            )
        ));

        $this->assertSame($methods, $class->getMethods());
    }

    public function testGetMethod() {
        $methodConstruct = new ClassMethod('__CONSTRUCT');
        $methodTest = new ClassMethod('test');
        $class = new Class_('Foo', array(
            'stmts' => array(
                new ClassConst(array()),
                $methodConstruct,
                new Property(0, array()),
                $methodTest,
            )
        ));

        $this->assertSame($methodConstruct, $class->getMethod('__construct'));
        $this->assertSame($methodTest, $class->getMethod('test'));
        $this->assertNull($class->getMethod('nonExisting'));
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class InterfaceTest extends \PHPUnit_Framework_TestCase
{
    public function testGetMethods() {
        $methods = array(
            new ClassMethod('foo'),
            new ClassMethod('bar'),
        );
        $interface = new Class_('Foo', array(
            'stmts' => array(
                new Node\Stmt\ClassConst(array(new Node\Const_('C1', new Node\Scalar\String_('C1')))),
                $methods[0],
                new Node\Stmt\ClassConst(array(new Node\Const_('C2', new Node\Scalar\String_('C2')))),
                $methods[1],
                new Node\Stmt\ClassConst(array(new Node\Const_('C3', new Node\Scalar\String_('C3')))),
            )
        ));

        $this->assertSame($methods, $interface->getMethods());
    }
}
<?php

namespace PhpParser\Node\Stmt;

class PropertyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provideModifiers
     */
    public function testModifiers($modifier) {
        $node = new Property(
            constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier)),
            array() // invalid
        );

        $this->assertTrue($node->{'is' . $modifier}());
    }

    public function testNoModifiers() {
        $node = new Property(0, array());

        $this->assertTrue($node->isPublic());
        $this->assertFalse($node->isProtected());
        $this->assertFalse($node->isPrivate());
        $this->assertFalse($node->isStatic());
    }

    public function testStaticImplicitlyPublic() {
        $node = new Property(Class_::MODIFIER_STATIC, array());
        $this->assertTrue($node->isPublic());
        $this->assertFalse($node->isProtected());
        $this->assertFalse($node->isPrivate());
        $this->assertTrue($node->isStatic());
    }

    public function provideModifiers() {
        return array(
            array('public'),
            array('protected'),
            array('private'),
            array('static'),
        );
    }
}
<?php

namespace PhpParser;

class DummyNode extends NodeAbstract {
    public $subNode1;
    public $subNode2;

    public function __construct($subNode1, $subNode2, $attributes) {
        parent::__construct(null, $attributes);
        $this->subNode1 = $subNode1;
        $this->subNode2 = $subNode2;
    }

    public function getSubNodeNames() {
        return array('subNode1', 'subNode2');
    }

    // This method is only overwritten because the node is located in an unusual namespace
    public function getType() {
        return 'Dummy';
    }
}

class NodeAbstractTest extends \PHPUnit_Framework_TestCase
{
    public function provideNodes() {
        $attributes = array(
            'startLine' => 10,
            'comments'  => array(
                new Comment('// Comment' . "\n"),
                new Comment\Doc('/** doc comment */'),
            ),
        );

        $node1 = $this->getMockForAbstractClass(
            'PhpParser\NodeAbstract',
            array(
                array(
                    'subNode1' => 'value1',
                    'subNode2' => 'value2',
                ),
                $attributes
            ),
            'PhpParser_Node_Dummy'
        );
        $node1->notSubNode = 'value3';

        $node2 = new DummyNode('value1', 'value2', $attributes);
        $node2->notSubNode = 'value3';

        return array(
            array($attributes, $node1),
            array($attributes, $node2),
        );
    }

    /**
     * @dataProvider provideNodes
     */
    public function testConstruct(array $attributes, Node $node) {
        $this->assertSame('Dummy', $node->getType());
        $this->assertSame(array('subNode1', 'subNode2'), $node->getSubNodeNames());
        $this->assertSame(10, $node->getLine());
        $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
        $this->assertSame('value1', $node->subNode1);
        $this->assertSame('value2', $node->subNode2);
        $this->assertTrue(isset($node->subNode1));
        $this->assertTrue(isset($node->subNode2));
        $this->assertFalse(isset($node->subNode3));
        $this->assertSame($attributes, $node->getAttributes());

        return $node;
    }

    /**
     * @dataProvider provideNodes
     */
    public function testGetDocComment(array $attributes, Node $node) {
        $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
        array_pop($node->getAttribute('comments')); // remove doc comment
        $this->assertNull($node->getDocComment());
        array_pop($node->getAttribute('comments')); // remove comment
        $this->assertNull($node->getDocComment());
    }

    /**
     * @dataProvider provideNodes
     */
    public function testChange(array $attributes, Node $node) {
        // change of line
        $node->setLine(15);
        $this->assertSame(15, $node->getLine());

        // direct modification
        $node->subNode = 'newValue';
        $this->assertSame('newValue', $node->subNode);

        // indirect modification
        $subNode =& $node->subNode;
        $subNode = 'newNewValue';
        $this->assertSame('newNewValue', $node->subNode);

        // removal
        unset($node->subNode);
        $this->assertFalse(isset($node->subNode));
    }

    /**
     * @dataProvider provideNodes
     */
    public function testIteration(array $attributes, Node $node) {
        // Iteration is simple object iteration over properties,
        // not over subnodes
        $i = 0;
        foreach ($node as $key => $value) {
            if ($i === 0) {
                $this->assertSame('subNode1', $key);
                $this->assertSame('value1', $value);
            } else if ($i === 1) {
                $this->assertSame('subNode2', $key);
                $this->assertSame('value2', $value);
            } else if ($i === 2) {
                $this->assertSame('notSubNode', $key);
                $this->assertSame('value3', $value);
            } else {
                throw new \Exception;
            }
            $i++;
        }
        $this->assertSame(3, $i);
    }

    public function testAttributes() {
        /** @var $node Node */
        $node = $this->getMockForAbstractClass('PhpParser\NodeAbstract');

        $this->assertEmpty($node->getAttributes());

        $node->setAttribute('key', 'value');
        $this->assertTrue($node->hasAttribute('key'));
        $this->assertSame('value', $node->getAttribute('key'));

        $this->assertFalse($node->hasAttribute('doesNotExist'));
        $this->assertNull($node->getAttribute('doesNotExist'));
        $this->assertSame('default', $node->getAttribute('doesNotExist', 'default'));

        $node->setAttribute('null', null);
        $this->assertTrue($node->hasAttribute('null'));
        $this->assertNull($node->getAttribute('null'));
        $this->assertNull($node->getAttribute('null', 'default'));

        $this->assertSame(
            array(
                'key'  => 'value',
                'null' => null,
            ),
            $node->getAttributes()
        );
    }
}
<?php

namespace PhpParser;

class NodeDumperTest extends \PHPUnit_Framework_TestCase
{
    private function canonicalize($string) {
        return str_replace("\r\n", "\n", $string);
    }

    /**
     * @dataProvider provideTestDump
     * @covers PhpParser\NodeDumper::dump
     */
    public function testDump($node, $dump) {
        $dumper = new NodeDumper;

        $this->assertSame($this->canonicalize($dump), $this->canonicalize($dumper->dump($node)));
    }

    public function provideTestDump() {
        return array(
            array(
                array(),
'array(
)'
            ),
            array(
                array('Foo', 'Bar', 'Key' => 'FooBar'),
'array(
    0: Foo
    1: Bar
    Key: FooBar
)'
            ),
            array(
                new Node\Name(array('Hallo', 'World')),
'Name(
    parts: array(
        0: Hallo
        1: World
    )
)'
            ),
            array(
                new Node\Expr\Array_(array(
                    new Node\Expr\ArrayItem(new Node\Scalar\String_('Foo'))
                )),
'Expr_Array(
    items: array(
        0: Expr_ArrayItem(
            key: null
            value: Scalar_String(
                value: Foo
            )
            byRef: false
        )
    )
)'
            ),
        );
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Can only dump nodes and arrays.
     */
    public function testError() {
        $dumper = new NodeDumper;
        $dumper->dump(new \stdClass);
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Expr;

class NodeTraverserTest extends \PHPUnit_Framework_TestCase
{
    public function testNonModifying() {
        $str1Node = new String_('Foo');
        $str2Node = new String_('Bar');
        $echoNode = new Node\Stmt\Echo_(array($str1Node, $str2Node));
        $stmts    = array($echoNode);

        $visitor = $this->getMock('PhpParser\NodeVisitor');

        $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
        $visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
        $visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
        $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
        $visitor->expects($this->at(4))->method('enterNode')->with($str2Node);
        $visitor->expects($this->at(5))->method('leaveNode')->with($str2Node);
        $visitor->expects($this->at(6))->method('leaveNode')->with($echoNode);
        $visitor->expects($this->at(7))->method('afterTraverse')->with($stmts);

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor);

        $this->assertEquals($stmts, $traverser->traverse($stmts));
    }

    public function testModifying() {
        $str1Node  = new String_('Foo');
        $str2Node  = new String_('Bar');
        $printNode = new Expr\Print_($str1Node);

        // first visitor changes the node, second verifies the change
        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
        $visitor2 = $this->getMock('PhpParser\NodeVisitor');

        // replace empty statements with string1 node
        $visitor1->expects($this->at(0))->method('beforeTraverse')->with(array())
                 ->will($this->returnValue(array($str1Node)));
        $visitor2->expects($this->at(0))->method('beforeTraverse')->with(array($str1Node));

        // replace string1 node with print node
        $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
                 ->will($this->returnValue($printNode));
        $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);

        // replace string1 node with string2 node
        $visitor1->expects($this->at(2))->method('enterNode')->with($str1Node)
                 ->will($this->returnValue($str2Node));
        $visitor2->expects($this->at(2))->method('enterNode')->with($str2Node);

        // replace string2 node with string1 node again
        $visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node)
                 ->will($this->returnValue($str1Node));
        $visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node);

        // replace print node with string1 node again
        $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
                 ->will($this->returnValue($str1Node));
        $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);

        // replace string1 node with empty statements again
        $visitor1->expects($this->at(5))->method('afterTraverse')->with(array($str1Node))
                 ->will($this->returnValue(array()));
        $visitor2->expects($this->at(5))->method('afterTraverse')->with(array());

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor1);
        $traverser->addVisitor($visitor2);

        // as all operations are reversed we end where we start
        $this->assertEquals(array(), $traverser->traverse(array()));
    }

    public function testRemove() {
        $str1Node = new String_('Foo');
        $str2Node = new String_('Bar');

        $visitor = $this->getMock('PhpParser\NodeVisitor');

        // remove the string1 node, leave the string2 node
        $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
                ->will($this->returnValue(false));

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor);

        $this->assertEquals(array($str2Node), $traverser->traverse(array($str1Node, $str2Node)));
    }

    public function testMerge() {
        $strStart  = new String_('Start');
        $strMiddle = new String_('End');
        $strEnd    = new String_('Middle');
        $strR1     = new String_('Replacement 1');
        $strR2     = new String_('Replacement 2');

        $visitor = $this->getMock('PhpParser\NodeVisitor');

        // replace strMiddle with strR1 and strR2 by merge
        $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
                ->will($this->returnValue(array($strR1, $strR2)));

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor);

        $this->assertEquals(
            array($strStart, $strR1, $strR2, $strEnd),
            $traverser->traverse(array($strStart, $strMiddle, $strEnd))
        );
    }

    public function testDeepArray() {
        $strNode = new String_('Foo');
        $stmts = array(array(array($strNode)));

        $visitor = $this->getMock('PhpParser\NodeVisitor');
        $visitor->expects($this->at(1))->method('enterNode')->with($strNode);

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor);

        $this->assertEquals($stmts, $traverser->traverse($stmts));
    }

    public function testDontTraverseChildren() {
        $strNode = new String_('str');
        $printNode = new Expr\Print_($strNode);
        $varNode = new Expr\Variable('foo');
        $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
        $negNode = new Expr\UnaryMinus($mulNode);
        $stmts = array($printNode, $negNode);

        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
        $visitor2 = $this->getMock('PhpParser\NodeVisitor');

        $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
            ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
        $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);

        $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
        $visitor2->expects($this->at(2))->method('leaveNode')->with($printNode);

        $visitor1->expects($this->at(3))->method('enterNode')->with($negNode);
        $visitor2->expects($this->at(3))->method('enterNode')->with($negNode);

        $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode);
        $visitor2->expects($this->at(4))->method('enterNode')->with($mulNode)
            ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));

        $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode);
        $visitor2->expects($this->at(5))->method('leaveNode')->with($mulNode);

        $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode);
        $visitor2->expects($this->at(6))->method('leaveNode')->with($negNode);

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor1);
        $traverser->addVisitor($visitor2);

        $this->assertEquals($stmts, $traverser->traverse($stmts));
    }

    public function testRemovingVisitor() {
        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
        $visitor2 = $this->getMock('PhpParser\NodeVisitor');
        $visitor3 = $this->getMock('PhpParser\NodeVisitor');

        $traverser = new NodeTraverser;
        $traverser->addVisitor($visitor1);
        $traverser->addVisitor($visitor2);
        $traverser->addVisitor($visitor3);

        $preExpected = array($visitor1, $visitor2, $visitor3);
        $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added');

        $traverser->removeVisitor($visitor2);

        $postExpected = array(0 => $visitor1, 2 => $visitor3);
        $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
    }

    public function testCloneNodesByDefault() {
        $stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));

        $traverser = new NodeTraverser;

        $this->assertNotSame($stmts, $traverser->traverse($stmts));
    }

    public function testCloneNodesDisabled() {
        $stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));

        $traverser = new NodeTraverser(false);

        $this->assertSame($stmts, $traverser->traverse($stmts));
    }
}
<?php

namespace PhpParser\NodeVisitor;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;

class NameResolverTest extends \PHPUnit_Framework_TestCase
{
    private function canonicalize($string) {
        return str_replace("\r\n", "\n", $string);
    }

    /**
     * @covers PhpParser\NodeVisitor\NameResolver
     */
    public function testResolveNames() {
        $code = <<<'EOC'
<?php

namespace Foo {
    use Hallo as Hi;

    new Bar();
    new Hi();
    new Hi\Bar();
    new \Bar();
    new namespace\Bar();

    bar();
    hi();
    Hi\bar();
    foo\bar();
    \bar();
    namespace\bar();
}
namespace {
    use Hallo as Hi;

    new Bar();
    new Hi();
    new Hi\Bar();
    new \Bar();
    new namespace\Bar();

    bar();
    hi();
    Hi\bar();
    foo\bar();
    \bar();
    namespace\bar();
}
namespace Bar {
    use function foo\bar as baz;
    use const foo\BAR as BAZ;
    use foo as bar;

    bar();
    baz();
    bar\foo();
    baz\foo();
    BAR();
    BAZ();
    BAR\FOO();
    BAZ\FOO();

    bar;
    baz;
    bar\foo;
    baz\foo;
    BAR;
    BAZ;
    BAR\FOO;
    BAZ\FOO;
}
EOC;
        $expectedCode = <<<'EOC'
namespace Foo {
    use Hallo as Hi;
    new \Foo\Bar();
    new \Hallo();
    new \Hallo\Bar();
    new \Bar();
    new \Foo\Bar();
    bar();
    hi();
    \Hallo\bar();
    \Foo\foo\bar();
    \bar();
    \Foo\bar();
}
namespace {
    use Hallo as Hi;
    new \Bar();
    new \Hallo();
    new \Hallo\Bar();
    new \Bar();
    new \Bar();
    bar();
    hi();
    \Hallo\bar();
    \foo\bar();
    \bar();
    \bar();
}
namespace Bar {
    use function foo\bar as baz;
    use const foo\BAR as BAZ;
    use foo as bar;
    bar();
    \foo\bar();
    \foo\foo();
    \Bar\baz\foo();
    BAR();
    \foo\bar();
    \foo\FOO();
    \Bar\BAZ\FOO();
    bar;
    baz;
    \foo\foo;
    \Bar\baz\foo;
    BAR;
    \foo\BAR;
    \foo\FOO;
    \Bar\BAZ\FOO;
}
EOC;

        $parser        = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
        $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
        $traverser     = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $parser->parse($code);
        $stmts = $traverser->traverse($stmts);

        $this->assertSame(
            $this->canonicalize($expectedCode),
            $prettyPrinter->prettyPrint($stmts)
        );
    }

    /**
     * @covers PhpParser\NodeVisitor\NameResolver
     */
    public function testResolveLocations() {
        $code = <<<'EOC'
<?php
namespace NS;

class A extends B implements C, D {
    use E, F, G {
        f as private g;
        E::h as i;
        E::j insteadof F, G;
    }
}

interface A extends C, D {
    public function a(A $a) : A;
}

function fn() : A {}
function fn2() : array {}
function() : A {};

A::b();
A::$b;
A::B;
new A;
$a instanceof A;

namespace\a();
namespace\A;

try {
    $someThing;
} catch (A $a) {
    $someThingElse;
}
EOC;
        $expectedCode = <<<'EOC'
namespace NS;

class A extends \NS\B implements \NS\C, \NS\D
{
    use \NS\E, \NS\F, \NS\G {
        f as private g;
        \NS\E::h as i;
        \NS\E::j insteadof \NS\F, \NS\G;
    }
}
interface A extends \NS\C, \NS\D
{
    public function a(\NS\A $a) : \NS\A;
}
function fn() : \NS\A
{
}
function fn2() : array
{
}
function () : \NS\A {
};
\NS\A::b();
\NS\A::$b;
\NS\A::B;
new \NS\A();
$a instanceof \NS\A;
\NS\a();
\NS\A;
try {
    $someThing;
} catch (\NS\A $a) {
    $someThingElse;
}
EOC;

        $parser        = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
        $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
        $traverser     = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $parser->parse($code);
        $stmts = $traverser->traverse($stmts);

        $this->assertSame(
            $this->canonicalize($expectedCode),
            $prettyPrinter->prettyPrint($stmts)
        );
    }

    public function testNoResolveSpecialName() {
        $stmts = array(new Node\Expr\New_(new Name('self')));

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $this->assertEquals($stmts, $traverser->traverse($stmts));
    }

    protected function createNamespacedAndNonNamespaced(array $stmts) {
        return array(
            new Stmt\Namespace_(new Name('NS'), $stmts),
            new Stmt\Namespace_(null,                          $stmts),
        );
    }

    public function testAddNamespacedName() {
        $stmts = $this->createNamespacedAndNonNamespaced(array(
            new Stmt\Class_('A'),
            new Stmt\Interface_('B'),
            new Stmt\Function_('C'),
            new Stmt\Const_(array(
                new Node\Const_('D', new Node\Scalar\String_('E'))
            )),
            new Expr\New_(new Stmt\Class_(null)),
        ));

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $traverser->traverse($stmts);

        $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
        $this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
        $this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
        $this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
        $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[4]->class);
        $this->assertSame('A',     (string) $stmts[1]->stmts[0]->namespacedName);
        $this->assertSame('B',     (string) $stmts[1]->stmts[1]->namespacedName);
        $this->assertSame('C',     (string) $stmts[1]->stmts[2]->namespacedName);
        $this->assertSame('D',     (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
        $this->assertObjectNotHasAttribute('namespacedName', $stmts[1]->stmts[4]->class);
    }

    public function testAddTraitNamespacedName() {
        $stmts = $this->createNamespacedAndNonNamespaced(array(
            new Stmt\Trait_('A')
        ));

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $traverser->traverse($stmts);

        $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
        $this->assertSame('A',     (string) $stmts[1]->stmts[0]->namespacedName);
    }

    /**
     * @dataProvider provideTestError
     */
    public function testError(Node $stmt, $errorMsg) {
        $this->setExpectedException('PhpParser\Error', $errorMsg);

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);
        $traverser->traverse(array($stmt));
    }

    public function provideTestError() {
        return array(
            array(
                new Stmt\Use_(array(
                    new Stmt\UseUse(new Name('A\B'), 'B', array('startLine' => 1)),
                    new Stmt\UseUse(new Name('C\D'), 'B', array('startLine' => 2)),
                ), Stmt\Use_::TYPE_NORMAL),
                'Cannot use C\D as B because the name is already in use on line 2'
            ),
            array(
                new Stmt\Use_(array(
                    new Stmt\UseUse(new Name('a\b'), 'b', array('startLine' => 1)),
                    new Stmt\UseUse(new Name('c\d'), 'B', array('startLine' => 2)),
                ), Stmt\Use_::TYPE_FUNCTION),
                'Cannot use function c\d as B because the name is already in use on line 2'
            ),
            array(
                new Stmt\Use_(array(
                    new Stmt\UseUse(new Name('A\B'), 'B', array('startLine' => 1)),
                    new Stmt\UseUse(new Name('C\D'), 'B', array('startLine' => 2)),
                ), Stmt\Use_::TYPE_CONSTANT),
                'Cannot use const C\D as B because the name is already in use on line 2'
            ),
            array(
                new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
                "'\\self' is an invalid class name on line 3"
            ),
            array(
                new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
                "'\\self' is an invalid class name on line 3"
            ),
            array(
                new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
                "'\\PARENT' is an invalid class name on line 3"
            ),
            array(
                new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
                "'\\STATIC' is an invalid class name on line 3"
            ),
        );
    }

    public function testClassNameIsCaseInsensitive()
    {
        $source = <<<'EOC'
<?php
namespace Foo;
use Bar\Baz;
$test = new baz();
EOC;

        $parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
        $stmts = $parser->parse($source);

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $traverser->traverse($stmts);
        $stmt = $stmts[0];

        $this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
    }

    public function testSpecialClassNamesAreCaseInsensitive() {
        $source = <<<'EOC'
<?php
namespace Foo;

class Bar
{
    public static function method()
    {
        SELF::method();
        PARENT::method();
        STATIC::method();
    }
}
EOC;

        $parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
        $stmts = $parser->parse($source);

        $traverser = new PhpParser\NodeTraverser;
        $traverser->addVisitor(new NameResolver);

        $stmts = $traverser->traverse($stmts);
        $classStmt = $stmts[0];
        $methodStmt = $classStmt->stmts[0]->stmts[0];

        $this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
        $this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
        $this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
    }
}
<?php

namespace PhpParser;

use PhpParser\Comment;

require_once __DIR__ . '/CodeTestAbstract.php';

class ParserTest extends CodeTestAbstract
{
    /**
     * @dataProvider provideTestParse
     */
    public function testParse($name, $code, $expected) {
        $lexer = new Lexer\Emulative(array('usedAttributes' => array(
            'startLine', 'endLine', 'startFilePos', 'endFilePos'
        )));
        $parser = new Parser($lexer, array(
            'throwOnError' => false,
        ));

        $stmts = $parser->parse($code);
        $errors = $parser->getErrors();

        $output = '';
        foreach ($errors as $error) {
            $output .= $this->formatErrorMessage($error, $code) . "\n";
        }

        if (null !== $stmts) {
            $dumper = new NodeDumper;
            $output .= $dumper->dump($stmts);
        }

        $this->assertSame($this->canonicalize($expected), $this->canonicalize($output), $name);
    }

    public function provideTestParse() {
        return $this->getTests(__DIR__ . '/../code/parser', 'test');
    }

    private function formatErrorMessage(Error $e, $code) {
        if ($e->hasColumnInfo()) {
            return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
                . ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
        } else {
            return $e->getMessage();
        }
    }

    /**
     * @expectedException \PhpParser\Error
     * @expectedExceptionMessage Syntax error, unexpected EOF on line 1
     */
    public function testParserThrowsSyntaxError() {
        $parser = new Parser(new Lexer());
        $parser->parse('<?php foo');
    }

    /**
     * @expectedException \PhpParser\Error
     * @expectedExceptionMessage Cannot use foo as self because 'self' is a special class name on line 1
     */
    public function testParserThrowsSpecialError() {
        $parser = new Parser(new Lexer());
        $parser->parse('<?php use foo as self;');
    }

    public function testAttributeAssignment() {
        $lexer = new Lexer(array(
            'usedAttributes' => array(
                'comments', 'startLine', 'endLine',
                'startTokenPos', 'endTokenPos',
            )
        ));

        $code = <<<'EOC'
<?php
/** Doc comment */
function test($a) {
    // Line
    // Comments
    echo $a;
}
EOC;
        $code = $this->canonicalize($code);

        $parser = new Parser($lexer);
        $stmts = $parser->parse($code);

        /** @var \PhpParser\Node\Stmt\Function_ $fn */
        $fn = $stmts[0];
        $this->assertInstanceOf('PhpParser\Node\Stmt\Function_', $fn);
        $this->assertEquals(array(
            'comments' => array(
                new Comment\Doc('/** Doc comment */', 2),
            ),
            'startLine' => 3,
            'endLine' => 7,
            'startTokenPos' => 3,
            'endTokenPos' => 21,
        ), $fn->getAttributes());

        $param = $fn->params[0];
        $this->assertInstanceOf('PhpParser\Node\Param', $param);
        $this->assertEquals(array(
            'startLine' => 3,
            'endLine' => 3,
            'startTokenPos' => 7,
            'endTokenPos' => 7,
        ), $param->getAttributes());

        /** @var \PhpParser\Node\Stmt\Echo_ $echo */
        $echo = $fn->stmts[0];
        $this->assertInstanceOf('PhpParser\Node\Stmt\Echo_', $echo);
        $this->assertEquals(array(
            'comments' => array(
                new Comment("// Line\n", 4),
                new Comment("// Comments\n", 5),
            ),
            'startLine' => 6,
            'endLine' => 6,
            'startTokenPos' => 16,
            'endTokenPos' => 19,
        ), $echo->getAttributes());

        /** @var \PhpParser\Node\Expr\Variable $var */
        $var = $echo->exprs[0];
        $this->assertInstanceOf('PhpParser\Node\Expr\Variable', $var);
        $this->assertEquals(array(
            'startLine' => 6,
            'endLine' => 6,
            'startTokenPos' => 18,
            'endTokenPos' => 18,
        ), $var->getAttributes());
    }

    /**
     * @expectedException \RangeException
     * @expectedExceptionMessage The lexer returned an invalid token (id=999, value=foobar)
     */
    public function testInvalidToken() {
        $lexer = new InvalidTokenLexer;
        $parser = new Parser($lexer);
        $parser->parse('dummy');
    }
}

class InvalidTokenLexer extends Lexer {
    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $value = 'foobar';
        return 999;
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinter\Standard;

require_once __DIR__ . '/CodeTestAbstract.php';

class PrettyPrinterTest extends CodeTestAbstract
{
    protected function doTestPrettyPrintMethod($method, $name, $code, $dump) {
        $parser = new Parser(new Lexer\Emulative);
        $prettyPrinter = new Standard;

        $stmts = $parser->parse($code);
        $this->assertSame(
            $this->canonicalize($dump),
            $this->canonicalize($prettyPrinter->$method($stmts)),
            $name
        );
    }

    /**
     * @dataProvider provideTestPrettyPrint
     * @covers PhpParser\PrettyPrinter\Standard<extended>
     */
    public function testPrettyPrint($name, $code, $dump) {
        $this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $dump);
    }

    /**
     * @dataProvider provideTestPrettyPrintFile
     * @covers PhpParser\PrettyPrinter\Standard<extended>
     */
    public function testPrettyPrintFile($name, $code, $dump) {
        $this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $dump);
    }

    public function provideTestPrettyPrint() {
        return $this->getTests(__DIR__ . '/../code/prettyPrinter', 'test');
    }

    public function provideTestPrettyPrintFile() {
        return $this->getTests(__DIR__ . '/../code/prettyPrinter', 'file-test');
    }

    public function testPrettyPrintExpr() {
        $prettyPrinter = new Standard;
        $expr = new Expr\BinaryOp\Mul(
            new Expr\BinaryOp\Plus(new Expr\Variable('a'), new Expr\Variable('b')),
            new Expr\Variable('c')
        );
        $this->assertEquals('($a + $b) * $c', $prettyPrinter->prettyPrintExpr($expr));

        $expr = new Expr\Closure(array(
            'stmts' => array(new Stmt\Return_(new String_("a\nb")))
        ));
        $this->assertEquals("function () {\n    return 'a\nb';\n}", $prettyPrinter->prettyPrintExpr($expr));
    }
}
<?php

namespace PhpParser\Serializer;

use PhpParser;

class XMLTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers PhpParser\Serializer\XML<extended>
     */
    public function testSerialize() {
        $code = <<<CODE
<?php
// comment
/** doc comment */
function functionName(&\$a = 0, \$b = 1.0) {
    echo 'Foo';
}
CODE;
        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node" xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode" xmlns:attribute="http://nikic.github.com/PHPParser/XML/attribute" xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
 <scalar:array>
  <node:Stmt_Function>
   <attribute:comments>
    <scalar:array>
     <comment isDocComment="false" line="2">// comment
</comment>
     <comment isDocComment="true" line="3">/** doc comment */</comment>
    </scalar:array>
   </attribute:comments>
   <attribute:startLine>
    <scalar:int>4</scalar:int>
   </attribute:startLine>
   <attribute:endLine>
    <scalar:int>6</scalar:int>
   </attribute:endLine>
   <subNode:byRef>
    <scalar:false/>
   </subNode:byRef>
   <subNode:name>
    <scalar:string>functionName</scalar:string>
   </subNode:name>
   <subNode:params>
    <scalar:array>
     <node:Param>
      <attribute:startLine>
       <scalar:int>4</scalar:int>
      </attribute:startLine>
      <attribute:endLine>
       <scalar:int>4</scalar:int>
      </attribute:endLine>
      <subNode:type>
       <scalar:null/>
      </subNode:type>
      <subNode:byRef>
       <scalar:true/>
      </subNode:byRef>
      <subNode:variadic>
       <scalar:false/>
      </subNode:variadic>
      <subNode:name>
       <scalar:string>a</scalar:string>
      </subNode:name>
      <subNode:default>
       <node:Scalar_LNumber>
        <attribute:startLine>
         <scalar:int>4</scalar:int>
        </attribute:startLine>
        <attribute:endLine>
         <scalar:int>4</scalar:int>
        </attribute:endLine>
        <subNode:value>
         <scalar:int>0</scalar:int>
        </subNode:value>
       </node:Scalar_LNumber>
      </subNode:default>
     </node:Param>
     <node:Param>
      <attribute:startLine>
       <scalar:int>4</scalar:int>
      </attribute:startLine>
      <attribute:endLine>
       <scalar:int>4</scalar:int>
      </attribute:endLine>
      <subNode:type>
       <scalar:null/>
      </subNode:type>
      <subNode:byRef>
       <scalar:false/>
      </subNode:byRef>
      <subNode:variadic>
       <scalar:false/>
      </subNode:variadic>
      <subNode:name>
       <scalar:string>b</scalar:string>
      </subNode:name>
      <subNode:default>
       <node:Scalar_DNumber>
        <attribute:startLine>
         <scalar:int>4</scalar:int>
        </attribute:startLine>
        <attribute:endLine>
         <scalar:int>4</scalar:int>
        </attribute:endLine>
        <subNode:value>
         <scalar:float>1</scalar:float>
        </subNode:value>
       </node:Scalar_DNumber>
      </subNode:default>
     </node:Param>
    </scalar:array>
   </subNode:params>
   <subNode:returnType>
     <scalar:null/>
   </subNode:returnType>
   <subNode:stmts>
    <scalar:array>
     <node:Stmt_Echo>
      <attribute:startLine>
       <scalar:int>5</scalar:int>
      </attribute:startLine>
      <attribute:endLine>
       <scalar:int>5</scalar:int>
      </attribute:endLine>
      <subNode:exprs>
       <scalar:array>
        <node:Scalar_String>
         <attribute:startLine>
          <scalar:int>5</scalar:int>
         </attribute:startLine>
         <attribute:endLine>
          <scalar:int>5</scalar:int>
         </attribute:endLine>
         <subNode:value>
          <scalar:string>Foo</scalar:string>
         </subNode:value>
        </node:Scalar_String>
       </scalar:array>
      </subNode:exprs>
     </node:Stmt_Echo>
    </scalar:array>
   </subNode:stmts>
  </node:Stmt_Function>
 </scalar:array>
</AST>
XML;

        $parser     = new PhpParser\Parser(new PhpParser\Lexer);
        $serializer = new XML;

        $stmts = $parser->parse($code);
        $this->assertXmlStringEqualsXmlString($xml, $serializer->serialize($stmts));
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Unexpected node type
     */
    public function testError() {
        $serializer = new XML;
        $serializer->serialize(array(new \stdClass));
    }
}
<?php

namespace PhpParser\Unserializer;

use PhpParser\Node\Scalar;
use PhpParser\Comment;

class XMLTest extends \PHPUnit_Framework_TestCase
{
    public function testNode() {
        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node" xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode" xmlns:attribute="http://nikic.github.com/PHPParser/XML/attribute" xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
 <node:Scalar_String line="1" docComment="/** doc comment */">
  <attribute:startLine>
   <scalar:int>1</scalar:int>
  </attribute:startLine>
  <attribute:comments>
   <scalar:array>
    <comment isDocComment="false" line="2">// comment
</comment>
    <comment isDocComment="true" line="3">/** doc comment */</comment>
   </scalar:array>
  </attribute:comments>
  <subNode:value>
   <scalar:string>Test</scalar:string>
  </subNode:value>
 </node:Scalar_String>
</AST>
XML;

        $unserializer  = new XML;
        $this->assertEquals(
            new Scalar\String_('Test', array(
                'startLine' => 1,
                'comments'  => array(
                    new Comment('// comment' . "\n", 2),
                    new Comment\Doc('/** doc comment */', 3),
                ),
            )),
            $unserializer->unserialize($xml)
        );
    }

    public function testEmptyNode() {
        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node">
 <node:Scalar_MagicConst_Class />
</AST>
XML;

        $unserializer  = new XML;

        $this->assertEquals(
            new Scalar\MagicConst\Class_,
            $unserializer->unserialize($xml)
        );
    }

    public function testScalars() {
        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
 <scalar:array>
  <scalar:array></scalar:array>
  <scalar:array/>
  <scalar:string>test</scalar:string>
  <scalar:string></scalar:string>
  <scalar:string/>
  <scalar:int>1</scalar:int>
  <scalar:float>1</scalar:float>
  <scalar:float>1.5</scalar:float>
  <scalar:true/>
  <scalar:false/>
  <scalar:null/>
 </scalar:array>
</AST>
XML;
        $result = array(
            array(), array(),
            'test', '', '',
            1,
            1, 1.5,
            true, false, null
        );

        $unserializer  = new XML;
        $this->assertEquals($result, $unserializer->unserialize($xml));
    }

    /**
     * @expectedException        \DomainException
     * @expectedExceptionMessage AST root element not found
     */
    public function testWrongRootElementError() {
        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<notAST/>
XML;

        $unserializer = new XML;
        $unserializer->unserialize($xml);
    }

    /**
     * @dataProvider             provideTestErrors
     */
    public function testErrors($xml, $errorMsg) {
        $this->setExpectedException('DomainException', $errorMsg);

        $xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar"
     xmlns:node="http://nikic.github.com/PHPParser/XML/node"
     xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode"
     xmlns:foo="http://nikic.github.com/PHPParser/XML/foo">
 $xml
</AST>
XML;

        $unserializer = new XML;
        $unserializer->unserialize($xml);
    }

    public function provideTestErrors() {
        return array(
            array('<scalar:true>test</scalar:true>',   '"true" scalar must be empty'),
            array('<scalar:false>test</scalar:false>', '"false" scalar must be empty'),
            array('<scalar:null>test</scalar:null>',   '"null" scalar must be empty'),
            array('<scalar:foo>bar</scalar:foo>',      'Unknown scalar type "foo"'),
            array('<scalar:int>x</scalar:int>',        '"x" is not a valid int'),
            array('<scalar:float>x</scalar:float>',    '"x" is not a valid float'),
            array('',                                  'Expected node or scalar'),
            array('<foo:bar>test</foo:bar>',           'Unexpected node of type "foo:bar"'),
            array(
                '<node:Scalar_String><foo:bar>test</foo:bar></node:Scalar_String>',
                'Expected sub node or attribute, got node of type "foo:bar"'
            ),
            array(
                '<node:Scalar_String><subNode:value/></node:Scalar_String>',
                'Expected node or scalar'
            ),
            array(
                '<node:Foo><subNode:value/></node:Foo>',
                'Unknown node type "Foo"'
            ),
        );
    }
}
Error positions
-----
<?php foo
-----
Syntax error, unexpected EOF from 1:10 to 1:10
array(
)
-----
<?php foo /* bar */
-----
Syntax error, unexpected EOF from 1:20 to 1:20
array(
)
Error recovery
-----
<?php

foo()
bar()
baz()
-----
Syntax error, unexpected T_STRING from 4:1 to 4:3
Syntax error, unexpected T_STRING from 5:1 to 5:3
Syntax error, unexpected EOF from 5:6 to 5:6
array(
)
-----
<?php

foo()
bar();
baz();
-----
Syntax error, unexpected T_STRING from 4:1 to 4:3
array(
    0: Expr_FuncCall(
        name: Name(
            parts: array(
                0: bar
            )
        )
        args: array(
        )
    )
    1: Expr_FuncCall(
        name: Name(
            parts: array(
                0: baz
            )
        )
        args: array(
        )
    )
)
-----
<?php

foo();
bar()
baz();
-----
Syntax error, unexpected T_STRING from 5:1 to 5:3
array(
    0: Expr_FuncCall(
        name: Name(
            parts: array(
                0: foo
            )
        )
        args: array(
        )
    )
    1: Expr_FuncCall(
        name: Name(
            parts: array(
                0: baz
            )
        )
        args: array(
        )
    )
)
-----
<?php
abc;
1 + ;
-----
Syntax error, unexpected ';' from 3:5 to 3:5
array(
    0: Expr_ConstFetch(
        name: Name(
            parts: array(
                0: abc
            )
        )
    )
)
-----
<?php
function test() {
    1 +
}
-----
Syntax error, unexpected '}' from 4:1 to 4:1
array(
    0: Stmt_Function(
        byRef: false
        name: test
        params: array(
        )
        returnType: null
        stmts: array(
        )
    )
)
-----
<?php

$i = 0;
while

$j = 1;
$k = 2;
-----
Syntax error, unexpected T_VARIABLE, expecting '(' from 6:1 to 6:2
array(
    0: Expr_Assign(
        var: Expr_Variable(
            name: i
        )
        expr: Scalar_LNumber(
            value: 0
        )
    )
    1: Expr_Assign(
        var: Expr_Variable(
            name: j
        )
        expr: Scalar_LNumber(
            value: 1
        )
    )
    2: Expr_Assign(
        var: Expr_Variable(
            name: k
        )
        expr: Scalar_LNumber(
            value: 2
        )
    )
)
-----
<?php

$i = 0;
while () {
    $j = 1;
}
$k = 2;
// The output here drops the loop - would require Error node to handle this
-----
Syntax error, unexpected ')' from 4:8 to 4:8
array(
    0: Expr_Assign(
        var: Expr_Variable(
            name: i
        )
        expr: Scalar_LNumber(
            value: 0
        )
    )
    1: Expr_Assign(
        var: Expr_Variable(
            name: j
        )
        expr: Scalar_LNumber(
            value: 1
        )
    )
    2: Expr_Assign(
        var: Expr_Variable(
            name: k
        )
        expr: Scalar_LNumber(
            value: 2
        )
    )
)
-----
<?php
// Can't recover this yet, as the '}' for the inner_statement_list
// is always required.

$i = 0;
while (true) {
    $i = 1;
    $i = 2;
-----
Syntax error, unexpected EOF from 8:12 to 8:12
Array definitions
-----
<?php

array();
array('a');
array('a', );
array('a', 'b');
array('a', &$b, 'c' => 'd', 'e' => &$f);

// short array syntax
[];
[1, 2, 3];
['a' => 'b'];
-----
array(
    0: Expr_Array(
        items: array(
        )
    )
    1: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: null
                value: Scalar_String(
                    value: a
                )
                byRef: false
            )
        )
    )
    2: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: null
                value: Scalar_String(
                    value: a
                )
                byRef: false
            )
        )
    )
    3: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: null
                value: Scalar_String(
                    value: a
                )
                byRef: false
            )
            1: Expr_ArrayItem(
                key: null
                value: Scalar_String(
                    value: b
                )
                byRef: false
            )
        )
    )
    4: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: null
                value: Scalar_String(
                    value: a
                )
                byRef: false
            )
            1: Expr_ArrayItem(
                key: null
                value: Expr_Variable(
                    name: b
                )
                byRef: true
            )
            2: Expr_ArrayItem(
                key: Scalar_String(
                    value: c
                )
                value: Scalar_String(
                    value: d
                )
                byRef: false
            )
            3: Expr_ArrayItem(
                key: Scalar_String(
                    value: e
                )
                value: Expr_Variable(
                    name: f
                )
                byRef: true
            )
        )
    )
    5: Expr_Array(
        items: array(
        )
    )
    6: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: null
                value: Scalar_LNumber(
                    value: 1
                )
                byRef: false
            )
            1: Expr_ArrayItem(
                key: null
                value: Scalar_LNumber(
                    value: 2
                )
                byRef: false
            )
            2: Expr_ArrayItem(
                key: null
                value: Scalar_LNumber(
                    value: 3
                )
                byRef: false
            )
        )
    )
    7: Expr_Array(
        items: array(
            0: Expr_ArrayItem(
                key: Scalar_String(
                    value: a
                )
                value: Scalar_String(
                    value: b
                )
                byRef: false
            )
        )
    )
)Assignments
-----
<?php
// simple assign
$a = $b;

// combined assign
$a &= $b;
$a |= $b;
$a ^= $b;
$a .= $b;
$a /= $b;
$a -= $b;
$a %= $b;
$a *= $b;
$a += $b;
$a <<= $b;
$a >>= $b;
$a **= $b;

// chained assign
$a = $b *= $c **= $d;

// by ref assign
$a =& $b;
$a =& new B;

// list() assign
list($a) = $b;
list($a, , $b) = $c;
list($a, list(, $c), $d) = $e;

// inc/dec
++$a;
$a++;
--$a;
$a--;
-----
array(
    0: Expr_Assign(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    1: Expr_AssignOp_BitwiseAnd(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    2: Expr_AssignOp_BitwiseOr(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    3: Expr_AssignOp_BitwiseXor(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    4: Expr_AssignOp_Concat(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    5: Expr_AssignOp_Div(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    6: Expr_AssignOp_Minus(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    7: Expr_AssignOp_Mod(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    8: Expr_AssignOp_Mul(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    9: Expr_AssignOp_Plus(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    10: Expr_AssignOp_ShiftLeft(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    11: Expr_AssignOp_ShiftRight(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    12: Expr_AssignOp_Pow(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    13: Expr_Assign(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_AssignOp_Mul(
            var: Expr_Variable(
                name: b
            )
            expr: Expr_AssignOp_Pow(
                var: Expr_Variable(
                    name: c
                )
                expr: Expr_Variable(
                    name: d
                )
            )
        )
    )
    14: Expr_AssignRef(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_Variable(
            name: b
        )
    )
    15: Expr_AssignRef(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_New(
            class: Name(
                parts: array(
                    0: B
                )
            )
            args: array(
            )
        )
    )
    16: Expr_Assign(
        var: Expr_List(
            vars: array(
                0: Expr_Variable(
                    name: a
                )
            )
        )
        expr: Expr_Variable(
            name: b
        )
    )
    17: Expr_Assign(
        var: Expr_List(
            vars: array(
                0: Expr_Variable(
                    name: a
                )
                1: null
                2: Expr_Variable(
                    name: b
                )
            )
        )
        expr: Expr_Variable(
            name: c
        )
    )
    18: Expr_Assign(
        var: Expr_List(
            vars: array(
                0: Expr_Variable(
                    name: a
                )
                1: Expr_List(
                    vars: array(
                        0: null
                        1: Expr_Variable(
                            name: c
                        )
                    )
                )
                2: Expr_Variable(
                    name: d
                )
            )
        )
        expr: Expr_Variable(
            name: e
        )
    )
    19: Expr_PreInc(
        var: Expr_Variable(
            name: a
        )
    )
    20: Expr_PostInc(
        var: Expr_Variable(
            name: a
        )
    )
    21: Expr_PreDec(
        var: Expr_Variable(
            name: a
        )
    )
    22: Expr_PostDec(
        var: Expr_Variable(
            name: a
        )
    )
)Casts
-----
<?php
(array)   $a;
(bool)    $a;
(boolean) $a;
(real)    $a;
(double)  $a;
(float)   $a;
(int)     $a;
(integer) $a;
(object)  $a;
(string)  $a;
(unset)   $a;
-----
array(
    0: Expr_Cast_Array(
        expr: Expr_Variable(
            name: a
        )
    )
    1: Expr_Cast_Bool(
        expr: Expr_Variable(
            name: a
        )
    )
    2: Expr_Cast_Bool(
        expr: Expr_Variable(
            name: a
        )
    )
    3: Expr_Cast_Double(
        expr: Expr_Variable(
            name: a
        )
    )
    4: Expr_Cast_Double(
        expr: Expr_Variable(
            name: a
        )
    )
    5: Expr_Cast_Double(
        expr: Expr_Variable(
            name: a
        )
    )
    6: Expr_Cast_Int(
        expr: Expr_Variable(
            name: a
        )
    )
    7: Expr_Cast_Int(
        expr: Expr_Variable(
            name: a
        )
    )
    8: Expr_Cast_Object(
        expr: Expr_Variable(
            name: a
        )
    )
    9: Expr_Cast_String(
        expr: Expr_Variable(
            name: a
        )
    )
    10: Expr_Cast_Unset(
        expr: Expr_Variable(
            name: a
        )
    )
)Clone
-----
<?php

clone $a;
-----
array(
    0: Expr_Clone(
        expr: Expr_Variable(
            name: a
        )
    )
)Closures
-----
<?php
function($a) { $a; };
function($a) use($b) {};
function() use($a, &$b) {};
function &($a) {};
static function() {};
function($a) : array {};
function() use($a) : \Foo\Bar {};
-----
array(
    0: Expr_Closure(
        static: false
        byRef: false
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
        )
        uses: array(
        )
        returnType: null
        stmts: array(
            0: Expr_Variable(
                name: a
            )
        )
    )
    1: Expr_Closure(
        static: false
        byRef: false
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
        )
        uses: array(
            0: Expr_ClosureUse(
                var: b
                byRef: false
            )
        )
        returnType: null
        stmts: array(
        )
    )
    2: Expr_Closure(
        static: false
        byRef: false
        params: array(
        )
        uses: array(
            0: Expr_ClosureUse(
                var: a
                byRef: false
            )
            1: Expr_ClosureUse(
                var: b
                byRef: true
            )
        )
        returnType: null
        stmts: array(
        )
    )
    3: Expr_Closure(
        static: false
        byRef: true
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
        )
        uses: array(
        )
        returnType: null
        stmts: array(
        )
    )
    4: Expr_Closure(
        static: true
        byRef: false
        params: array(
        )
        uses: array(
        )
        returnType: null
        stmts: array(
        )
    )
    5: Expr_Closure(
        static: false
        byRef: false
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
        )
        uses: array(
        )
        returnType: array
        stmts: array(
        )
    )
    6: Expr_Closure(
        static: false
        byRef: false
        params: array(
        )
        uses: array(
            0: Expr_ClosureUse(
                var: a
                byRef: false
            )
        )
        returnType: Name_FullyQualified(
            parts: array(
                0: Foo
                1: Bar
            )
        )
        stmts: array(
        )
    )
)
Comparison operators
-----
<?php
$a < $b;
$a <= $b;
$a > $b;
$a >= $b;
$a == $b;
$a === $b;
$a != $b;
$a !== $b;
$a <=> $b;
$a instanceof B;
$a instanceof $b;
-----
array(
    0: Expr_BinaryOp_Smaller(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    1: Expr_BinaryOp_SmallerOrEqual(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    2: Expr_BinaryOp_Greater(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    3: Expr_BinaryOp_GreaterOrEqual(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    4: Expr_BinaryOp_Equal(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    5: Expr_BinaryOp_Identical(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    6: Expr_BinaryOp_NotEqual(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    7: Expr_BinaryOp_NotIdentical(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    8: Expr_BinaryOp_Spaceship(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    9: Expr_Instanceof(
        expr: Expr_Variable(
            name: a
        )
        class: Name(
            parts: array(
                0: B
            )
        )
    )
    10: Expr_Instanceof(
        expr: Expr_Variable(
            name: a
        )
        class: Expr_Variable(
            name: b
        )
    )
)
Expressions in static scalar context
-----
<?php

const T_1 = 1 << 1;
const T_2 = 1 / 2;
const T_3 = 1.5 + 1.5;
const T_4 = "foo" . "bar";
const T_5 = (1.5 + 1.5) * 2;
const T_6 = "foo" . 2 . 3 . 4.0;
const T_7 = __LINE__;
const T_8 = <<<ENDOFSTRING
This is a test string
ENDOFSTRING;
const T_9 = ~-1;
const T_10 = (-1?:1) + (0?2:3);
const T_11 = 1 && 0;
const T_12 = 1 and 1;
const T_13 = 0 || 0;
const T_14 = 1 or 0;
const T_15 = 1 xor 1;
const T_16 = 1 xor 0;
const T_17 = 1 < 0;
const T_18 = 0 <= 0;
const T_19 = 1 > 0;
const T_20 = 1 >= 0;
const T_21 = 1 === 1;
const T_22 = 1 !== 1;
const T_23 = 0 != "0";
const T_24 = 1 == "1";
const T_25 = 1 + 2 * 3;
const T_26 = "1" + 2 + "3";
const T_27 = 2 ** 3;
const T_28 = [1, 2, 3][1];
const T_29 = 12 - 13;
const T_30 = 12 ^ 13;
const T_31 = 12 & 13;
const T_32 = 12 | 13;
const T_33 = 12 % 3;
const T_34 = 100 >> 4;
const T_35 = !false;
-----
array(
    0: Stmt_Const(
        consts: array(
            0: Const(
                name: T_1
                value: Expr_BinaryOp_ShiftLeft(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    1: Stmt_Const(
        consts: array(
            0: Const(
                name: T_2
                value: Expr_BinaryOp_Div(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 2
                    )
                )
            )
        )
    )
    2: Stmt_Const(
        consts: array(
            0: Const(
                name: T_3
                value: Expr_BinaryOp_Plus(
                    left: Scalar_DNumber(
                        value: 1.5
                    )
                    right: Scalar_DNumber(
                        value: 1.5
                    )
                )
            )
        )
    )
    3: Stmt_Const(
        consts: array(
            0: Const(
                name: T_4
                value: Expr_BinaryOp_Concat(
                    left: Scalar_String(
                        value: foo
                    )
                    right: Scalar_String(
                        value: bar
                    )
                )
            )
        )
    )
    4: Stmt_Const(
        consts: array(
            0: Const(
                name: T_5
                value: Expr_BinaryOp_Mul(
                    left: Expr_BinaryOp_Plus(
                        left: Scalar_DNumber(
                            value: 1.5
                        )
                        right: Scalar_DNumber(
                            value: 1.5
                        )
                    )
                    right: Scalar_LNumber(
                        value: 2
                    )
                )
            )
        )
    )
    5: Stmt_Const(
        consts: array(
            0: Const(
                name: T_6
                value: Expr_BinaryOp_Concat(
                    left: Expr_BinaryOp_Concat(
                        left: Expr_BinaryOp_Concat(
                            left: Scalar_String(
                                value: foo
                            )
                            right: Scalar_LNumber(
                                value: 2
                            )
                        )
                        right: Scalar_LNumber(
                            value: 3
                        )
                    )
                    right: Scalar_DNumber(
                        value: 4
                    )
                )
            )
        )
    )
    6: Stmt_Const(
        consts: array(
            0: Const(
                name: T_7
                value: Scalar_MagicConst_Line(
                )
            )
        )
    )
    7: Stmt_Const(
        consts: array(
            0: Const(
                name: T_8
                value: Scalar_String(
                    value: This is a test string
                )
            )
        )
    )
    8: Stmt_Const(
        consts: array(
            0: Const(
                name: T_9
                value: Expr_BitwiseNot(
                    expr: Expr_UnaryMinus(
                        expr: Scalar_LNumber(
                            value: 1
                        )
                    )
                )
            )
        )
    )
    9: Stmt_Const(
        consts: array(
            0: Const(
                name: T_10
                value: Expr_BinaryOp_Plus(
                    left: Expr_Ternary(
                        cond: Expr_UnaryMinus(
                            expr: Scalar_LNumber(
                                value: 1
                            )
                        )
                        if: null
                        else: Scalar_LNumber(
                            value: 1
                        )
                    )
                    right: Expr_Ternary(
                        cond: Scalar_LNumber(
                            value: 0
                        )
                        if: Scalar_LNumber(
                            value: 2
                        )
                        else: Scalar_LNumber(
                            value: 3
                        )
                    )
                )
            )
        )
    )
    10: Stmt_Const(
        consts: array(
            0: Const(
                name: T_11
                value: Expr_BinaryOp_BooleanAnd(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    11: Stmt_Const(
        consts: array(
            0: Const(
                name: T_12
                value: Expr_BinaryOp_LogicalAnd(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    12: Stmt_Const(
        consts: array(
            0: Const(
                name: T_13
                value: Expr_BinaryOp_BooleanOr(
                    left: Scalar_LNumber(
                        value: 0
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    13: Stmt_Const(
        consts: array(
            0: Const(
                name: T_14
                value: Expr_BinaryOp_LogicalOr(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    14: Stmt_Const(
        consts: array(
            0: Const(
                name: T_15
                value: Expr_BinaryOp_LogicalXor(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    15: Stmt_Const(
        consts: array(
            0: Const(
                name: T_16
                value: Expr_BinaryOp_LogicalXor(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    16: Stmt_Const(
        consts: array(
            0: Const(
                name: T_17
                value: Expr_BinaryOp_Smaller(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    17: Stmt_Const(
        consts: array(
            0: Const(
                name: T_18
                value: Expr_BinaryOp_SmallerOrEqual(
                    left: Scalar_LNumber(
                        value: 0
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    18: Stmt_Const(
        consts: array(
            0: Const(
                name: T_19
                value: Expr_BinaryOp_Greater(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    19: Stmt_Const(
        consts: array(
            0: Const(
                name: T_20
                value: Expr_BinaryOp_GreaterOrEqual(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 0
                    )
                )
            )
        )
    )
    20: Stmt_Const(
        consts: array(
            0: Const(
                name: T_21
                value: Expr_BinaryOp_Identical(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    21: Stmt_Const(
        consts: array(
            0: Const(
                name: T_22
                value: Expr_BinaryOp_NotIdentical(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    22: Stmt_Const(
        consts: array(
            0: Const(
                name: T_23
                value: Expr_BinaryOp_NotEqual(
                    left: Scalar_LNumber(
                        value: 0
                    )
                    right: Scalar_String(
                        value: 0
                    )
                )
            )
        )
    )
    23: Stmt_Const(
        consts: array(
            0: Const(
                name: T_24
                value: Expr_BinaryOp_Equal(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Scalar_String(
                        value: 1
                    )
                )
            )
        )
    )
    24: Stmt_Const(
        consts: array(
            0: Const(
                name: T_25
                value: Expr_BinaryOp_Plus(
                    left: Scalar_LNumber(
                        value: 1
                    )
                    right: Expr_BinaryOp_Mul(
                        left: Scalar_LNumber(
                            value: 2
                        )
                        right: Scalar_LNumber(
                            value: 3
                        )
                    )
                )
            )
        )
    )
    25: Stmt_Const(
        consts: array(
            0: Const(
                name: T_26
                value: Expr_BinaryOp_Plus(
                    left: Expr_BinaryOp_Plus(
                        left: Scalar_String(
                            value: 1
                        )
                        right: Scalar_LNumber(
                            value: 2
                        )
                    )
                    right: Scalar_String(
                        value: 3
                    )
                )
            )
        )
    )
    26: Stmt_Const(
        consts: array(
            0: Const(
                name: T_27
                value: Expr_BinaryOp_Pow(
                    left: Scalar_LNumber(
                        value: 2
                    )
                    right: Scalar_LNumber(
                        value: 3
                    )
                )
            )
        )
    )
    27: Stmt_Const(
        consts: array(
            0: Const(
                name: T_28
                value: Expr_ArrayDimFetch(
                    var: Expr_Array(
                        items: array(
                            0: Expr_ArrayItem(
                                key: null
                                value: Scalar_LNumber(
                                    value: 1
                                )
                                byRef: false
                            )
                            1: Expr_ArrayItem(
                                key: null
                                value: Scalar_LNumber(
                                    value: 2
                                )
                                byRef: false
                            )
                            2: Expr_ArrayItem(
                                key: null
                                value: Scalar_LNumber(
                                    value: 3
                                )
                                byRef: false
                            )
                        )
                    )
                    dim: Scalar_LNumber(
                        value: 1
                    )
                )
            )
        )
    )
    28: Stmt_Const(
        consts: array(
            0: Const(
                name: T_29
                value: Expr_BinaryOp_Minus(
                    left: Scalar_LNumber(
                        value: 12
                    )
                    right: Scalar_LNumber(
                        value: 13
                    )
                )
            )
        )
    )
    29: Stmt_Const(
        consts: array(
            0: Const(
                name: T_30
                value: Expr_BinaryOp_BitwiseXor(
                    left: Scalar_LNumber(
                        value: 12
                    )
                    right: Scalar_LNumber(
                        value: 13
                    )
                )
            )
        )
    )
    30: Stmt_Const(
        consts: array(
            0: Const(
                name: T_31
                value: Expr_BinaryOp_BitwiseAnd(
                    left: Scalar_LNumber(
                        value: 12
                    )
                    right: Scalar_LNumber(
                        value: 13
                    )
                )
            )
        )
    )
    31: Stmt_Const(
        consts: array(
            0: Const(
                name: T_32
                value: Expr_BinaryOp_BitwiseOr(
                    left: Scalar_LNumber(
                        value: 12
                    )
                    right: Scalar_LNumber(
                        value: 13
                    )
                )
            )
        )
    )
    32: Stmt_Const(
        consts: array(
            0: Const(
                name: T_33
                value: Expr_BinaryOp_Mod(
                    left: Scalar_LNumber(
                        value: 12
                    )
                    right: Scalar_LNumber(
                        value: 3
                    )
                )
            )
        )
    )
    33: Stmt_Const(
        consts: array(
            0: Const(
                name: T_34
                value: Expr_BinaryOp_ShiftRight(
                    left: Scalar_LNumber(
                        value: 100
                    )
                    right: Scalar_LNumber(
                        value: 4
                    )
                )
            )
        )
    )
    34: Stmt_Const(
        consts: array(
            0: Const(
                name: T_35
                value: Expr_BooleanNot(
                    expr: Expr_ConstFetch(
                        name: Name(
                            parts: array(
                                0: false
                            )
                        )
                    )
                )
            )
        )
    )
)Error suppression
-----
<?php
@$a;
-----
array(
    0: Expr_ErrorSuppress(
        expr: Expr_Variable(
            name: a
        )
    )
)Exit
-----
<?php
exit;
exit();
exit('Die!');
die;
die();
die('Exit!');
-----
array(
    0: Expr_Exit(
        expr: null
    )
    1: Expr_Exit(
        expr: null
    )
    2: Expr_Exit(
        expr: Scalar_String(
            value: Die!
        )
    )
    3: Expr_Exit(
        expr: null
    )
    4: Expr_Exit(
        expr: null
    )
    5: Expr_Exit(
        expr: Scalar_String(
            value: Exit!
        )
    )
)Arguments
-----
<?php

f();
f($a);
f($a, $b);
f(&$a);
f($a, ...$b);
-----
array(
    0: Expr_FuncCall(
        name: Name(
            parts: array(
                0: f
            )
        )
        args: array(
        )
    )
    1: Expr_FuncCall(
        name: Name(
            parts: array(
                0: f
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: a
                )
                byRef: false
                unpack: false
            )
        )
    )
    2: Expr_FuncCall(
        name: Name(
            parts: array(
                0: f
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: a
                )
                byRef: false
                unpack: false
            )
            1: Arg(
                value: Expr_Variable(
                    name: b
                )
                byRef: false
                unpack: false
            )
        )
    )
    3: Expr_FuncCall(
        name: Name(
            parts: array(
                0: f
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: a
                )
                byRef: true
                unpack: false
            )
        )
    )
    4: Expr_FuncCall(
        name: Name(
            parts: array(
                0: f
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: a
                )
                byRef: false
                unpack: false
            )
            1: Arg(
                value: Expr_Variable(
                    name: b
                )
                byRef: false
                unpack: true
            )
        )
    )
)Constant fetches
-----
<?php

A;
A::B;
A::class;
-----
array(
    0: Expr_ConstFetch(
        name: Name(
            parts: array(
                0: A
            )
        )
    )
    1: Expr_ClassConstFetch(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: B
    )
    2: Expr_ClassConstFetch(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: class
    )
)Array/string dereferencing
-----
<?php

"abc"[2];
"abc"[2][0][0];

[1, 2, 3][2];
[1, 2, 3][2][0][0];

array(1, 2, 3)[2];
array(1, 2, 3)[2][0][0];

FOO[0];
Foo::BAR[1];
$foo::BAR[2][1][0];
-----
array(
    0: Expr_ArrayDimFetch(
        var: Scalar_String(
            value: abc
        )
        dim: Scalar_LNumber(
            value: 2
        )
    )
    1: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_ArrayDimFetch(
                var: Scalar_String(
                    value: abc
                )
                dim: Scalar_LNumber(
                    value: 2
                )
            )
            dim: Scalar_LNumber(
                value: 0
            )
        )
        dim: Scalar_LNumber(
            value: 0
        )
    )
    2: Expr_ArrayDimFetch(
        var: Expr_Array(
            items: array(
                0: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 1
                    )
                    byRef: false
                )
                1: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 2
                    )
                    byRef: false
                )
                2: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 3
                    )
                    byRef: false
                )
            )
        )
        dim: Scalar_LNumber(
            value: 2
        )
    )
    3: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_ArrayDimFetch(
                var: Expr_Array(
                    items: array(
                        0: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 1
                            )
                            byRef: false
                        )
                        1: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 2
                            )
                            byRef: false
                        )
                        2: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 3
                            )
                            byRef: false
                        )
                    )
                )
                dim: Scalar_LNumber(
                    value: 2
                )
            )
            dim: Scalar_LNumber(
                value: 0
            )
        )
        dim: Scalar_LNumber(
            value: 0
        )
    )
    4: Expr_ArrayDimFetch(
        var: Expr_Array(
            items: array(
                0: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 1
                    )
                    byRef: false
                )
                1: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 2
                    )
                    byRef: false
                )
                2: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 3
                    )
                    byRef: false
                )
            )
        )
        dim: Scalar_LNumber(
            value: 2
        )
    )
    5: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_ArrayDimFetch(
                var: Expr_Array(
                    items: array(
                        0: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 1
                            )
                            byRef: false
                        )
                        1: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 2
                            )
                            byRef: false
                        )
                        2: Expr_ArrayItem(
                            key: null
                            value: Scalar_LNumber(
                                value: 3
                            )
                            byRef: false
                        )
                    )
                )
                dim: Scalar_LNumber(
                    value: 2
                )
            )
            dim: Scalar_LNumber(
                value: 0
            )
        )
        dim: Scalar_LNumber(
            value: 0
        )
    )
    6: Expr_ArrayDimFetch(
        var: Expr_ConstFetch(
            name: Name(
                parts: array(
                    0: FOO
                )
            )
        )
        dim: Scalar_LNumber(
            value: 0
        )
    )
    7: Expr_ArrayDimFetch(
        var: Expr_ClassConstFetch(
            class: Name(
                parts: array(
                    0: Foo
                )
            )
            name: BAR
        )
        dim: Scalar_LNumber(
            value: 1
        )
    )
    8: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_ArrayDimFetch(
                var: Expr_ClassConstFetch(
                    class: Expr_Variable(
                        name: foo
                    )
                    name: BAR
                )
                dim: Scalar_LNumber(
                    value: 2
                )
            )
            dim: Scalar_LNumber(
                value: 1
            )
        )
        dim: Scalar_LNumber(
            value: 0
        )
    )
)Function calls
-----
<?php

// function name variations
a();
$a();
${'a'}();
$$a();
$$$a();
$a['b']();
$a{'b'}();
$a->b['c']();

// array dereferencing
a()['b'];
-----
array(
    0: Expr_FuncCall(
        name: Name(
            parts: array(
                0: a
            )
        )
        args: array(
        )
    )
    1: Expr_FuncCall(
        name: Expr_Variable(
            name: a
        )
        args: array(
        )
    )
    2: Expr_FuncCall(
        name: Expr_Variable(
            name: Scalar_String(
                value: a
            )
        )
        args: array(
        )
    )
    3: Expr_FuncCall(
        name: Expr_Variable(
            name: Expr_Variable(
                name: a
            )
        )
        args: array(
        )
    )
    4: Expr_FuncCall(
        name: Expr_Variable(
            name: Expr_Variable(
                name: Expr_Variable(
                    name: a
                )
            )
        )
        args: array(
        )
    )
    5: Expr_FuncCall(
        name: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
        args: array(
        )
    )
    6: Expr_FuncCall(
        name: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
        args: array(
        )
    )
    7: Expr_FuncCall(
        name: Expr_ArrayDimFetch(
            var: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: a
                )
                name: b
            )
            dim: Scalar_String(
                value: c
            )
        )
        args: array(
        )
    )
    8: Expr_ArrayDimFetch(
        var: Expr_FuncCall(
            name: Name(
                parts: array(
                    0: a
                )
            )
            args: array(
            )
        )
        dim: Scalar_String(
            value: b
        )
    )
)New expression dereferencing
-----
<?php

(new A)->b;
(new A)->b();
(new A)['b'];
(new A)['b']['c'];
-----
array(
    0: Expr_PropertyFetch(
        var: Expr_New(
            class: Name(
                parts: array(
                    0: A
                )
            )
            args: array(
            )
        )
        name: b
    )
    1: Expr_MethodCall(
        var: Expr_New(
            class: Name(
                parts: array(
                    0: A
                )
            )
            args: array(
            )
        )
        name: b
        args: array(
        )
    )
    2: Expr_ArrayDimFetch(
        var: Expr_New(
            class: Name(
                parts: array(
                    0: A
                )
            )
            args: array(
            )
        )
        dim: Scalar_String(
            value: b
        )
    )
    3: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_New(
                class: Name(
                    parts: array(
                        0: A
                    )
                )
                args: array(
                )
            )
            dim: Scalar_String(
                value: b
            )
        )
        dim: Scalar_String(
            value: c
        )
    )
)Object access
-----
<?php

// property fetch variations
$a->b;
$a->b['c'];
$a->b{'c'};

// method call variations
$a->b();
$a->{'b'}();
$a->$b();
$a->$b['c']();

// array dereferencing
$a->b()['c'];
$a->b(){'c'}; // invalid PHP: drop Support?
-----
array(
    0: Expr_PropertyFetch(
        var: Expr_Variable(
            name: a
        )
        name: b
    )
    1: Expr_ArrayDimFetch(
        var: Expr_PropertyFetch(
            var: Expr_Variable(
                name: a
            )
            name: b
        )
        dim: Scalar_String(
            value: c
        )
    )
    2: Expr_ArrayDimFetch(
        var: Expr_PropertyFetch(
            var: Expr_Variable(
                name: a
            )
            name: b
        )
        dim: Scalar_String(
            value: c
        )
    )
    3: Expr_MethodCall(
        var: Expr_Variable(
            name: a
        )
        name: b
        args: array(
        )
    )
    4: Expr_MethodCall(
        var: Expr_Variable(
            name: a
        )
        name: Scalar_String(
            value: b
        )
        args: array(
        )
    )
    5: Expr_MethodCall(
        var: Expr_Variable(
            name: a
        )
        name: Expr_Variable(
            name: b
        )
        args: array(
        )
    )
    6: Expr_MethodCall(
        var: Expr_Variable(
            name: a
        )
        name: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: b
            )
            dim: Scalar_String(
                value: c
            )
        )
        args: array(
        )
    )
    7: Expr_ArrayDimFetch(
        var: Expr_MethodCall(
            var: Expr_Variable(
                name: a
            )
            name: b
            args: array(
            )
        )
        dim: Scalar_String(
            value: c
        )
    )
    8: Expr_ArrayDimFetch(
        var: Expr_MethodCall(
            var: Expr_Variable(
                name: a
            )
            name: b
            args: array(
            )
        )
        dim: Scalar_String(
            value: c
        )
    )
)Simple array access
-----
<?php

$a['b'];
$a['b']['c'];
$a[] = $b;
$a{'b'};
${$a}['b'];
-----
array(
    0: Expr_ArrayDimFetch(
        var: Expr_Variable(
            name: a
        )
        dim: Scalar_String(
            value: b
        )
    )
    1: Expr_ArrayDimFetch(
        var: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
        dim: Scalar_String(
            value: c
        )
    )
    2: Expr_Assign(
        var: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: null
        )
        expr: Expr_Variable(
            name: b
        )
    )
    3: Expr_ArrayDimFetch(
        var: Expr_Variable(
            name: a
        )
        dim: Scalar_String(
            value: b
        )
    )
    4: Expr_ArrayDimFetch(
        var: Expr_Variable(
            name: Expr_Variable(
                name: a
            )
        )
        dim: Scalar_String(
            value: b
        )
    )
)Static calls
-----
<?php

// method name variations
A::b();
A::{'b'}();
A::$b();
A::$b['c']();
A::$b['c']['d']();

// array dereferencing
A::b()['c'];

// class name variations
static::b();
$a::b();
${'a'}::b();
$a['b']::c();
-----
array(
    0: Expr_StaticCall(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: b
        args: array(
        )
    )
    1: Expr_StaticCall(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Scalar_String(
            value: b
        )
        args: array(
        )
    )
    2: Expr_StaticCall(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Expr_Variable(
            name: b
        )
        args: array(
        )
    )
    3: Expr_StaticCall(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: b
            )
            dim: Scalar_String(
                value: c
            )
        )
        args: array(
        )
    )
    4: Expr_StaticCall(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Expr_ArrayDimFetch(
            var: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: b
                )
                dim: Scalar_String(
                    value: c
                )
            )
            dim: Scalar_String(
                value: d
            )
        )
        args: array(
        )
    )
    5: Expr_ArrayDimFetch(
        var: Expr_StaticCall(
            class: Name(
                parts: array(
                    0: A
                )
            )
            name: b
            args: array(
            )
        )
        dim: Scalar_String(
            value: c
        )
    )
    6: Expr_StaticCall(
        class: Name(
            parts: array(
                0: static
            )
        )
        name: b
        args: array(
        )
    )
    7: Expr_StaticCall(
        class: Expr_Variable(
            name: a
        )
        name: b
        args: array(
        )
    )
    8: Expr_StaticCall(
        class: Expr_Variable(
            name: Scalar_String(
                value: a
            )
        )
        name: b
        args: array(
        )
    )
    9: Expr_StaticCall(
        class: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
        name: c
        args: array(
        )
    )
)Static property fetches
-----
<?php

// property name variations
A::$b;
A::$$b;
A::${'b'};

// array access
A::$b['c'];
A::$b{'c'};

// class name variations can be found in staticCall.test
-----
array(
    0: Expr_StaticPropertyFetch(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: b
    )
    1: Expr_StaticPropertyFetch(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Expr_Variable(
            name: b
        )
    )
    2: Expr_StaticPropertyFetch(
        class: Name(
            parts: array(
                0: A
            )
        )
        name: Scalar_String(
            value: b
        )
    )
    3: Expr_ArrayDimFetch(
        var: Expr_StaticPropertyFetch(
            class: Name(
                parts: array(
                    0: A
                )
            )
            name: b
        )
        dim: Scalar_String(
            value: c
        )
    )
    4: Expr_ArrayDimFetch(
        var: Expr_StaticPropertyFetch(
            class: Name(
                parts: array(
                    0: A
                )
            )
            name: b
        )
        dim: Scalar_String(
            value: c
        )
    )
)Include and eval
-----
<?php
include 'A.php';
include_once 'A.php';
require 'A.php';
require_once 'A.php';
eval('A');
-----
array(
    0: Expr_Include(
        expr: Scalar_String(
            value: A.php
        )
        type: 1
    )
    1: Expr_Include(
        expr: Scalar_String(
            value: A.php
        )
        type: 2
    )
    2: Expr_Include(
        expr: Scalar_String(
            value: A.php
        )
        type: 3
    )
    3: Expr_Include(
        expr: Scalar_String(
            value: A.php
        )
        type: 4
    )
    4: Expr_Eval(
        expr: Scalar_String(
            value: A
        )
    )
)isset() and empty()
-----
<?php
isset($a);
isset($a, $b, $c);

empty($a);
empty(foo());
empty(array(1, 2, 3));
-----
array(
    0: Expr_Isset(
        vars: array(
            0: Expr_Variable(
                name: a
            )
        )
    )
    1: Expr_Isset(
        vars: array(
            0: Expr_Variable(
                name: a
            )
            1: Expr_Variable(
                name: b
            )
            2: Expr_Variable(
                name: c
            )
        )
    )
    2: Expr_Empty(
        expr: Expr_Variable(
            name: a
        )
    )
    3: Expr_Empty(
        expr: Expr_FuncCall(
            name: Name(
                parts: array(
                    0: foo
                )
            )
            args: array(
            )
        )
    )
    4: Expr_Empty(
        expr: Expr_Array(
            items: array(
                0: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 1
                    )
                    byRef: false
                )
                1: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 2
                    )
                    byRef: false
                )
                2: Expr_ArrayItem(
                    key: null
                    value: Scalar_LNumber(
                        value: 3
                    )
                    byRef: false
                )
            )
        )
    )
)Logical operators
-----
<?php

// boolean ops
$a && $b;
$a || $b;
!$a;
!!$a;

// logical ops
$a and $b;
$a or $b;
$a xor $b;

// precedence
$a && $b || $c && $d;
$a && ($b || $c) && $d;

$a = $b || $c;
$a = $b or $c;
-----
array(
    0: Expr_BinaryOp_BooleanAnd(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    1: Expr_BinaryOp_BooleanOr(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    2: Expr_BooleanNot(
        expr: Expr_Variable(
            name: a
        )
    )
    3: Expr_BooleanNot(
        expr: Expr_BooleanNot(
            expr: Expr_Variable(
                name: a
            )
        )
    )
    4: Expr_BinaryOp_LogicalAnd(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    5: Expr_BinaryOp_LogicalOr(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    6: Expr_BinaryOp_LogicalXor(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    7: Expr_BinaryOp_BooleanOr(
        left: Expr_BinaryOp_BooleanAnd(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        right: Expr_BinaryOp_BooleanAnd(
            left: Expr_Variable(
                name: c
            )
            right: Expr_Variable(
                name: d
            )
        )
    )
    8: Expr_BinaryOp_BooleanAnd(
        left: Expr_BinaryOp_BooleanAnd(
            left: Expr_Variable(
                name: a
            )
            right: Expr_BinaryOp_BooleanOr(
                left: Expr_Variable(
                    name: b
                )
                right: Expr_Variable(
                    name: c
                )
            )
        )
        right: Expr_Variable(
            name: d
        )
    )
    9: Expr_Assign(
        var: Expr_Variable(
            name: a
        )
        expr: Expr_BinaryOp_BooleanOr(
            left: Expr_Variable(
                name: b
            )
            right: Expr_Variable(
                name: c
            )
        )
    )
    10: Expr_BinaryOp_LogicalOr(
        left: Expr_Assign(
            var: Expr_Variable(
                name: a
            )
            expr: Expr_Variable(
                name: b
            )
        )
        right: Expr_Variable(
            name: c
        )
    )
)Mathematical operators
-----
<?php

// unary ops
~$a;
+$a;
-$a;

// binary ops
$a & $b;
$a | $b;
$a ^ $b;
$a . $b;
$a / $b;
$a - $b;
$a % $b;
$a * $b;
$a + $b;
$a << $b;
$a >> $b;
$a ** $b;

// associativity
$a * $b * $c;
$a * ($b * $c);

// precedence
$a + $b * $c;
($a + $b) * $c;

// pow is special
$a ** $b ** $c;
($a ** $b) ** $c;
-----
array(
    0: Expr_BitwiseNot(
        expr: Expr_Variable(
            name: a
        )
    )
    1: Expr_UnaryPlus(
        expr: Expr_Variable(
            name: a
        )
    )
    2: Expr_UnaryMinus(
        expr: Expr_Variable(
            name: a
        )
    )
    3: Expr_BinaryOp_BitwiseAnd(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    4: Expr_BinaryOp_BitwiseOr(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    5: Expr_BinaryOp_BitwiseXor(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    6: Expr_BinaryOp_Concat(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    7: Expr_BinaryOp_Div(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    8: Expr_BinaryOp_Minus(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    9: Expr_BinaryOp_Mod(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    10: Expr_BinaryOp_Mul(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    11: Expr_BinaryOp_Plus(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    12: Expr_BinaryOp_ShiftLeft(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    13: Expr_BinaryOp_ShiftRight(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    14: Expr_BinaryOp_Pow(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    15: Expr_BinaryOp_Mul(
        left: Expr_BinaryOp_Mul(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        right: Expr_Variable(
            name: c
        )
    )
    16: Expr_BinaryOp_Mul(
        left: Expr_Variable(
            name: a
        )
        right: Expr_BinaryOp_Mul(
            left: Expr_Variable(
                name: b
            )
            right: Expr_Variable(
                name: c
            )
        )
    )
    17: Expr_BinaryOp_Plus(
        left: Expr_Variable(
            name: a
        )
        right: Expr_BinaryOp_Mul(
            left: Expr_Variable(
                name: b
            )
            right: Expr_Variable(
                name: c
            )
        )
    )
    18: Expr_BinaryOp_Mul(
        left: Expr_BinaryOp_Plus(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        right: Expr_Variable(
            name: c
        )
    )
    19: Expr_BinaryOp_Pow(
        left: Expr_Variable(
            name: a
        )
        right: Expr_BinaryOp_Pow(
            left: Expr_Variable(
                name: b
            )
            right: Expr_Variable(
                name: c
            )
        )
    )
    20: Expr_BinaryOp_Pow(
        left: Expr_BinaryOp_Pow(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        right: Expr_Variable(
            name: c
        )
    )
)New
-----
<?php

new A;
new A($b);

// class name variations
new $a();
new $a['b']();
new A::$b();
// DNCR object access
new $a->b();
new $a->b->c();
new $a->b['c']();
new $a->b{'c'}();

// test regression introduces by new dereferencing syntax
(new A);
-----
array(
    0: Expr_New(
        class: Name(
            parts: array(
                0: A
            )
        )
        args: array(
        )
    )
    1: Expr_New(
        class: Name(
            parts: array(
                0: A
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: b
                )
                byRef: false
                unpack: false
            )
        )
    )
    2: Expr_New(
        class: Expr_Variable(
            name: a
        )
        args: array(
        )
    )
    3: Expr_New(
        class: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
        args: array(
        )
    )
    4: Expr_New(
        class: Expr_StaticPropertyFetch(
            class: Name(
                parts: array(
                    0: A
                )
            )
            name: b
        )
        args: array(
        )
    )
    5: Expr_New(
        class: Expr_PropertyFetch(
            var: Expr_Variable(
                name: a
            )
            name: b
        )
        args: array(
        )
    )
    6: Expr_New(
        class: Expr_PropertyFetch(
            var: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: a
                )
                name: b
            )
            name: c
        )
        args: array(
        )
    )
    7: Expr_New(
        class: Expr_ArrayDimFetch(
            var: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: a
                )
                name: b
            )
            dim: Scalar_String(
                value: c
            )
        )
        args: array(
        )
    )
    8: Expr_New(
        class: Expr_ArrayDimFetch(
            var: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: a
                )
                name: b
            )
            dim: Scalar_String(
                value: c
            )
        )
        args: array(
        )
    )
    9: Expr_New(
        class: Name(
            parts: array(
                0: A
            )
        )
        args: array(
        )
    )
)New without a class
-----
<?php
new;
-----
Syntax error, unexpected ';' from 2:4 to 2:4
array(
)
Print
-----
<?php
print $a;
-----
array(
    0: Expr_Print(
        expr: Expr_Variable(
            name: a
        )
    )
)Shell execution
-----
<?php
``;
`test`;
`test $A`;
`test \``;
`test \"`;
-----
array(
    0: Expr_ShellExec(
        parts: array(
        )
    )
    1: Expr_ShellExec(
        parts: array(
            0: test
        )
    )
    2: Expr_ShellExec(
        parts: array(
            0: test
            1: Expr_Variable(
                name: A
            )
        )
    )
    3: Expr_ShellExec(
        parts: array(
            0: test `
        )
    )
    4: Expr_ShellExec(
        parts: array(
            0: test \"
        )
    )
)Ternary operator
-----
<?php

// ternary
$a ? $b : $c;
$a ?: $c;

// precedence
$a ? $b : $c ? $d : $e;
$a ? $b : ($c ? $d : $e);

// null coalesce
$a ?? $b;
$a ?? $b ?? $c;
$a ?? $b ? $c : $d;
$a && $b ?? $c;
-----
array(
    0: Expr_Ternary(
        cond: Expr_Variable(
            name: a
        )
        if: Expr_Variable(
            name: b
        )
        else: Expr_Variable(
            name: c
        )
    )
    1: Expr_Ternary(
        cond: Expr_Variable(
            name: a
        )
        if: null
        else: Expr_Variable(
            name: c
        )
    )
    2: Expr_Ternary(
        cond: Expr_Ternary(
            cond: Expr_Variable(
                name: a
            )
            if: Expr_Variable(
                name: b
            )
            else: Expr_Variable(
                name: c
            )
        )
        if: Expr_Variable(
            name: d
        )
        else: Expr_Variable(
            name: e
        )
    )
    3: Expr_Ternary(
        cond: Expr_Variable(
            name: a
        )
        if: Expr_Variable(
            name: b
        )
        else: Expr_Ternary(
            cond: Expr_Variable(
                name: c
            )
            if: Expr_Variable(
                name: d
            )
            else: Expr_Variable(
                name: e
            )
        )
    )
    4: Expr_BinaryOp_Coalesce(
        left: Expr_Variable(
            name: a
        )
        right: Expr_Variable(
            name: b
        )
    )
    5: Expr_BinaryOp_Coalesce(
        left: Expr_Variable(
            name: a
        )
        right: Expr_BinaryOp_Coalesce(
            left: Expr_Variable(
                name: b
            )
            right: Expr_Variable(
                name: c
            )
        )
    )
    6: Expr_Ternary(
        cond: Expr_BinaryOp_Coalesce(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        if: Expr_Variable(
            name: c
        )
        else: Expr_Variable(
            name: d
        )
    )
    7: Expr_BinaryOp_Coalesce(
        left: Expr_BinaryOp_BooleanAnd(
            left: Expr_Variable(
                name: a
            )
            right: Expr_Variable(
                name: b
            )
        )
        right: Expr_Variable(
            name: c
        )
    )
)
Variable syntaxes
-----
<?php

$a;
${'a'};
${foo()};
$$a;
$$$a;
$$a['b'];
-----
array(
    0: Expr_Variable(
        name: a
    )
    1: Expr_Variable(
        name: Scalar_String(
            value: a
        )
    )
    2: Expr_Variable(
        name: Expr_FuncCall(
            name: Name(
                parts: array(
                    0: foo
                )
            )
            args: array(
            )
        )
    )
    3: Expr_Variable(
        name: Expr_Variable(
            name: a
        )
    )
    4: Expr_Variable(
        name: Expr_Variable(
            name: Expr_Variable(
                name: a
            )
        )
    )
    5: Expr_Variable(
        name: Expr_ArrayDimFetch(
            var: Expr_Variable(
                name: a
            )
            dim: Scalar_String(
                value: b
            )
        )
    )
)Constant string syntaxes
-----
<?php

'';
"";
b'';
b"";
'Hi';
b'Hi';
"Hi";
b"Hi";
'!\'!\\!\a!';
"!\"!\\!\$!\n!\r!\t!\f!\v!\e!\a";
"!\xFF!\377!\400!\0!";
-----
array(
    0: Scalar_String(
        value:
    )
    1: Scalar_String(
        value:
    )
    2: Scalar_String(
        value:
    )
    3: Scalar_String(
        value:
    )
    4: Scalar_String(
        value: Hi
    )
    5: Scalar_String(
        value: Hi
    )
    6: Scalar_String(
        value: Hi
    )
    7: Scalar_String(
        value: Hi
    )
    8: Scalar_String(
        value: !'!\!\a!
    )
    9: Scalar_String(
        value: !"!\!$!
    !
!@@{ "\t" }@@!@@{ "\f" }@@!@@{ "\v" }@@!@@{ chr(27) /* "\e" */ }@@!\a
    )
    10: Scalar_String(
        value: !@@{ chr(255) }@@!@@{ chr(255) }@@!@@{ chr(0) }@@!@@{ chr(0) }@@!
    )
)Nowdoc and heredoc strings
-----
<?php

// empty strings
<<<'EOS'
EOS;
<<<EOS
EOS;

// constant encapsed strings
<<<'EOS'
Test '" $a \n
EOS;
<<<EOS
Test '" \$a \n
EOS;

// encapsed strings
<<<EOS
Test $a
EOS;
<<<EOS
Test $a and $b->c test
EOS;

// comment to force line break before EOF
-----
array(
    0: Scalar_String(
        value:
    )
    1: Scalar_String(
        value:
    )
    2: Scalar_String(
        value: Test '" $a \n
    )
    3: Scalar_String(
        value: Test '" $a

    )
    4: Scalar_Encapsed(
        parts: array(
            0: Test
            1: Expr_Variable(
                name: a
            )
        )
    )
    5: Scalar_Encapsed(
        parts: array(
            0: Test
            1: Expr_Variable(
                name: a
            )
            2:  and
            3: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: b
                )
                name: c
            )
            4:  test
        )
    )
)Encapsed strings
-----
<?php

"$A";
"$A->B";
"$A[B]";
"$A[0]";
"$A[0x0]";
"$A[$B]";
"{$A}";
"{$A['B']}";
"${A}";
"${A['B']}";
"${$A}";
"\{$A}";
"\{ $A }";
"\\{$A}";
"\\{ $A }";
"{$$A}[B]";
"$$A[B]";
"A $B C";
b"$A";
-----
array(
    0: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
    1: Scalar_Encapsed(
        parts: array(
            0: Expr_PropertyFetch(
                var: Expr_Variable(
                    name: A
                )
                name: B
            )
        )
    )
    2: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: B
                )
            )
        )
    )
    3: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: 0
                )
            )
        )
    )
    4: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: 0x0
                )
            )
        )
    )
    5: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Expr_Variable(
                    name: B
                )
            )
        )
    )
    6: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
    7: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: B
                )
            )
        )
    )
    8: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
    9: Scalar_Encapsed(
        parts: array(
            0: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: B
                )
            )
        )
    )
    10: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: Expr_Variable(
                    name: A
                )
            )
        )
    )
    11: Scalar_Encapsed(
        parts: array(
            0: \{
            1: Expr_Variable(
                name: A
            )
            2: }
        )
    )
    12: Scalar_Encapsed(
        parts: array(
            0: \{
            1: Expr_Variable(
                name: A
            )
            2:  }
        )
    )
    13: Scalar_Encapsed(
        parts: array(
            0: \
            1: Expr_Variable(
                name: A
            )
        )
    )
    14: Scalar_Encapsed(
        parts: array(
            0: \{
            1: Expr_Variable(
                name: A
            )
            2:  }
        )
    )
    15: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: Expr_Variable(
                    name: A
                )
            )
            1: [B]
        )
    )
    16: Scalar_Encapsed(
        parts: array(
            0: $
            1: Expr_ArrayDimFetch(
                var: Expr_Variable(
                    name: A
                )
                dim: Scalar_String(
                    value: B
                )
            )
        )
    )
    17: Scalar_Encapsed(
        parts: array(
            0: A
            1: Expr_Variable(
                name: B
            )
            2:  C
        )
    )
    18: Scalar_Encapsed(
        parts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
)
Different float syntaxes
-----
<?php

0.0;
0.;
.0;
0e0;
0E0;
0e+0;
0e-0;
30.20e10;
300.200e100;
1e10000;

// various integer -> float overflows
// (all are actually the same number, just in different representations)
18446744073709551615;
0xFFFFFFFFFFFFFFFF;
01777777777777777777777;
0177777777777777777777787;
0b1111111111111111111111111111111111111111111111111111111111111111;
-----
array(
    0: Scalar_DNumber(
        value: 0
    )
    1: Scalar_DNumber(
        value: 0
    )
    2: Scalar_DNumber(
        value: 0
    )
    3: Scalar_DNumber(
        value: 0
    )
    4: Scalar_DNumber(
        value: 0
    )
    5: Scalar_DNumber(
        value: 0
    )
    6: Scalar_DNumber(
        value: 0
    )
    7: Scalar_DNumber(
        value: 302000000000
    )
    8: Scalar_DNumber(
        value: 3.002E+102
    )
    9: Scalar_DNumber(
        value: INF
    )
    10: Scalar_DNumber(
        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    )
    11: Scalar_DNumber(
        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    )
    12: Scalar_DNumber(
        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    )
    13: Scalar_DNumber(
        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    )
    14: Scalar_DNumber(
        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    )
)Different integer syntaxes
-----
<?php

0;
1;
@@{ PHP_INT_MAX     }@@;
@@{ PHP_INT_MAX + 1 }@@;
0xFFF;
0xfff;
0XfFf;
0777;
0787;
0b111000111000;
-----
array(
    0: Scalar_LNumber(
        value: 0
    )
    1: Scalar_LNumber(
        value: 1
    )
    2: Scalar_LNumber(
        value: @@{ PHP_INT_MAX }@@
    )
    3: Scalar_DNumber(
        value: @@{ PHP_INT_MAX + 1 }@@
    )
    4: Scalar_LNumber(
        value: 4095
    )
    5: Scalar_LNumber(
        value: 4095
    )
    6: Scalar_LNumber(
        value: 4095
    )
    7: Scalar_LNumber(
        value: 511
    )
    8: Scalar_LNumber(
        value: 7
    )
    9: Scalar_LNumber(
        value: 3640
    )
)Magic constants
-----
<?php

__CLASS__;
__DIR__;
__FILE__;
__FUNCTION__;
__LINE__;
__METHOD__;
__NAMESPACE__;
__TRAIT__;
-----
array(
    0: Scalar_MagicConst_Class(
    )
    1: Scalar_MagicConst_Dir(
    )
    2: Scalar_MagicConst_File(
    )
    3: Scalar_MagicConst_Function(
    )
    4: Scalar_MagicConst_Line(
    )
    5: Scalar_MagicConst_Method(
    )
    6: Scalar_MagicConst_Namespace(
    )
    7: Scalar_MagicConst_Trait(
    )
)Blockless statements for if/for/etc
-----
<?php

if ($a) $A;
elseif ($b) $B;
else $C;

for (;;) $foo;

foreach ($a as $b) $AB;

while ($a) $A;

do $A; while ($a);

declare (a='b') $C;
-----
array(
    0: Stmt_If(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
            0: Expr_Variable(
                name: A
            )
        )
        elseifs: array(
            0: Stmt_ElseIf(
                cond: Expr_Variable(
                    name: b
                )
                stmts: array(
                    0: Expr_Variable(
                        name: B
                    )
                )
            )
        )
        else: Stmt_Else(
            stmts: array(
                0: Expr_Variable(
                    name: C
                )
            )
        )
    )
    1: Stmt_For(
        init: array(
        )
        cond: array(
        )
        loop: array(
        )
        stmts: array(
            0: Expr_Variable(
                name: foo
            )
        )
    )
    2: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: null
        byRef: false
        valueVar: Expr_Variable(
            name: b
        )
        stmts: array(
            0: Expr_Variable(
                name: AB
            )
        )
    )
    3: Stmt_While(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
    4: Stmt_Do(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
            0: Expr_Variable(
                name: A
            )
        )
    )
    5: Stmt_Declare(
        declares: array(
            0: Stmt_DeclareDeclare(
                key: a
                value: Scalar_String(
                    value: b
                )
            )
        )
        stmts: array(
            0: Expr_Variable(
                name: C
            )
        )
    )
)Abstract class
-----
<?php

abstract class A {
    public function a() {}
    abstract public function b();
}
-----
array(
    0: Stmt_Class(
        type: 16
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: a
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            1: Stmt_ClassMethod(
                type: 17
                byRef: false
                name: b
                params: array(
                )
                returnType: null
                stmts: null
            )
        )
    )
)
Anonymous classes
-----
<?php

new class {
    public function test() {}
};
new class extends A implements B, C {};
new class() {
    public $foo;
};
new class($a, $b) extends A {
    use T;
};

class A {
    public function test() {
        return new class($this) extends A {
            const A = 'B';
        };
    }
}
-----
array(
    0: Expr_New(
        class: Stmt_Class(
            type: 0
            name: null
            extends: null
            implements: array(
            )
            stmts: array(
                0: Stmt_ClassMethod(
                    type: 1
                    byRef: false
                    name: test
                    params: array(
                    )
                    returnType: null
                    stmts: array(
                    )
                )
            )
        )
        args: array(
        )
    )
    1: Expr_New(
        class: Stmt_Class(
            type: 0
            name: null
            extends: Name(
                parts: array(
                    0: A
                )
            )
            implements: array(
                0: Name(
                    parts: array(
                        0: B
                    )
                )
                1: Name(
                    parts: array(
                        0: C
                    )
                )
            )
            stmts: array(
            )
        )
        args: array(
        )
    )
    2: Expr_New(
        class: Stmt_Class(
            type: 0
            name: null
            extends: null
            implements: array(
            )
            stmts: array(
                0: Stmt_Property(
                    type: 1
                    props: array(
                        0: Stmt_PropertyProperty(
                            name: foo
                            default: null
                        )
                    )
                )
            )
        )
        args: array(
        )
    )
    3: Expr_New(
        class: Stmt_Class(
            type: 0
            name: null
            extends: Name(
                parts: array(
                    0: A
                )
            )
            implements: array(
            )
            stmts: array(
                0: Stmt_TraitUse(
                    traits: array(
                        0: Name(
                            parts: array(
                                0: T
                            )
                        )
                    )
                    adaptations: array(
                    )
                )
            )
        )
        args: array(
            0: Arg(
                value: Expr_Variable(
                    name: a
                )
                byRef: false
                unpack: false
            )
            1: Arg(
                value: Expr_Variable(
                    name: b
                )
                byRef: false
                unpack: false
            )
        )
    )
    4: Stmt_Class(
        type: 0
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: test
                params: array(
                )
                returnType: null
                stmts: array(
                    0: Stmt_Return(
                        expr: Expr_New(
                            class: Stmt_Class(
                                type: 0
                                name: null
                                extends: Name(
                                    parts: array(
                                        0: A
                                    )
                                )
                                implements: array(
                                )
                                stmts: array(
                                    0: Stmt_ClassConst(
                                        consts: array(
                                            0: Const(
                                                name: A
                                                value: Scalar_String(
                                                    value: B
                                                )
                                            )
                                        )
                                    )
                                )
                            )
                            args: array(
                                0: Arg(
                                    value: Expr_Variable(
                                        name: this
                                    )
                                    byRef: false
                                    unpack: false
                                )
                            )
                        )
                    )
                )
            )
        )
    )
)
Conditional class definition
-----
<?php

if (true) {
    class A {}
}
-----
array(
    0: Stmt_If(
        cond: Expr_ConstFetch(
            name: Name(
                parts: array(
                    0: true
                )
            )
        )
        stmts: array(
            0: Stmt_Class(
                type: 0
                name: A
                extends: null
                implements: array(
                )
                stmts: array(
                )
            )
        )
        elseifs: array(
        )
        else: null
    )
)Final class
-----
<?php

final class A {}
-----
array(
    0: Stmt_Class(
        type: 32
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
        )
    )
)Implicitly public properties and methods
-----
<?php

abstract class A {
    var $a;
    static $b;
    abstract function c();
    final function d() {}
    static function e() {}
    final static function f() {}
    function g() {}
}
-----
array(
    0: Stmt_Class(
        type: 16
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_Property(
                type: 0
                props: array(
                    0: Stmt_PropertyProperty(
                        name: a
                        default: null
                    )
                )
            )
            1: Stmt_Property(
                type: 8
                props: array(
                    0: Stmt_PropertyProperty(
                        name: b
                        default: null
                    )
                )
            )
            2: Stmt_ClassMethod(
                type: 16
                byRef: false
                name: c
                params: array(
                )
                returnType: null
                stmts: null
            )
            3: Stmt_ClassMethod(
                type: 32
                byRef: false
                name: d
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            4: Stmt_ClassMethod(
                type: 8
                byRef: false
                name: e
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            5: Stmt_ClassMethod(
                type: 40
                byRef: false
                name: f
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            6: Stmt_ClassMethod(
                type: 0
                byRef: false
                name: g
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
        )
    )
)
Interface
-----
<?php

interface A extends C, D {
    public function a();
}
-----
array(
    0: Stmt_Interface(
        name: A
        extends: array(
            0: Name(
                parts: array(
                    0: C
                )
            )
            1: Name(
                parts: array(
                    0: D
                )
            )
        )
        stmts: array(
            0: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: a
                params: array(
                )
                returnType: null
                stmts: null
            )
        )
    )
)
Invalid modifier combination
-----
<?php class A { public public $a; }
-----
Multiple access type modifiers are not allowed on line 1
-----
<?php class A { public protected $a; }
-----
Multiple access type modifiers are not allowed on line 1
-----
<?php class A { abstract abstract a(); }
-----
Multiple abstract modifiers are not allowed on line 1
-----
<?php class A { static static $a; }
-----
Multiple static modifiers are not allowed on line 1
-----
<?php class A { final final a() {} }
-----
Multiple final modifiers are not allowed on line 1
-----
<?php class A { abstract final a(); }
-----
Cannot use the final modifier on an abstract class member on line 1
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array(
    0: Stmt_Class(
        type: 32
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
        )
    )
)
-----
<?php class A { abstract $a; }
-----
Properties cannot be declared abstract on line 1
-----
<?php class A { final $a; }
-----
Properties cannot be declared final on line 1
Invalid class name
-----
<?php class self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php class PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
-----
<?php class static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:13 to 1:18
array(
)
-----
<?php class A extends self {}
-----
Cannot use 'self' as class name as it is reserved from 1:23 to 1:26
-----
<?php class A extends PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28
-----
<?php class A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:23 to 1:28
array(
)
-----
<?php class A implements self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29
-----
<?php class A implements PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31
-----
<?php class A implements static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:26 to 1:31
array(
)
-----
<?php interface self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php interface PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
-----
<?php interface static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22
array(
)
-----
<?php interface A extends self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30
-----
<?php interface A extends PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32
-----
<?php interface A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:27 to 1:32
array(
)
PHP 4 style declarations
-----
<?php

class A {
    var $foo;
    function bar() {}
    static abstract function baz() {}
}
-----
array(
    0: Stmt_Class(
        type: 0
        name: A
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_Property(
                type: 0
                props: array(
                    0: Stmt_PropertyProperty(
                        name: foo
                        default: null
                    )
                )
            )
            1: Stmt_ClassMethod(
                type: 0
                byRef: false
                name: bar
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            2: Stmt_ClassMethod(
                type: 24
                byRef: false
                name: baz
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
        )
    )
)
Class declaration
-----
<?php

class A extends B implements C, D {
    const A = 'B', C = 'D';

    public $a = 'b', $c = 'd';
    protected $e;
    private $f;

    public function a() {}
    public static function b($a) {}
    public final function c() : B {}
    protected function d() {}
    private function e() {}
}
-----
array(
    0: Stmt_Class(
        type: 0
        name: A
        extends: Name(
            parts: array(
                0: B
            )
        )
        implements: array(
            0: Name(
                parts: array(
                    0: C
                )
            )
            1: Name(
                parts: array(
                    0: D
                )
            )
        )
        stmts: array(
            0: Stmt_ClassConst(
                consts: array(
                    0: Const(
                        name: A
                        value: Scalar_String(
                            value: B
                        )
                    )
                    1: Const(
                        name: C
                        value: Scalar_String(
                            value: D
                        )
                    )
                )
            )
            1: Stmt_Property(
                type: 1
                props: array(
                    0: Stmt_PropertyProperty(
                        name: a
                        default: Scalar_String(
                            value: b
                        )
                    )
                    1: Stmt_PropertyProperty(
                        name: c
                        default: Scalar_String(
                            value: d
                        )
                    )
                )
            )
            2: Stmt_Property(
                type: 2
                props: array(
                    0: Stmt_PropertyProperty(
                        name: e
                        default: null
                    )
                )
            )
            3: Stmt_Property(
                type: 4
                props: array(
                    0: Stmt_PropertyProperty(
                        name: f
                        default: null
                    )
                )
            )
            4: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: a
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            5: Stmt_ClassMethod(
                type: 9
                byRef: false
                name: b
                params: array(
                    0: Param(
                        type: null
                        byRef: false
                        variadic: false
                        name: a
                        default: null
                    )
                )
                returnType: null
                stmts: array(
                )
            )
            6: Stmt_ClassMethod(
                type: 33
                byRef: false
                name: c
                params: array(
                )
                returnType: Name(
                    parts: array(
                        0: B
                    )
                )
                stmts: array(
                )
            )
            7: Stmt_ClassMethod(
                type: 2
                byRef: false
                name: d
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
            8: Stmt_ClassMethod(
                type: 4
                byRef: false
                name: e
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
        )
    )
)
Some special methods cannot be static
-----
<?php class A { static function __construct() {} }
-----
Constructor __construct() cannot be static on line 1
-----
<?php class A { static function __destruct() {} }
-----
Destructor __destruct() cannot be static on line 1
-----
<?php class A { static function __clone() {} }
-----
Clone method __clone() cannot be static on line 1
-----
<?php class A { static function __CONSTRUCT() {} }
-----
Constructor __CONSTRUCT() cannot be static on line 1
-----
<?php class A { static function __Destruct() {} }
-----
Destructor __Destruct() cannot be static on line 1
-----
<?php class A { static function __cLoNe() {} }
-----
Clone method __cLoNe() cannot be static on line 1
Traits
-----
<?php

trait A {
    public function a() {}
}

class B {
    use C;
    use D {
        a as protected b;
        c as d;
        e as private;
    }
    use E, F, G {
        E::a insteadof F, G;
        E::b as protected c;
        E::d as e;
        E::f as private;
    }
}
-----
array(
    0: Stmt_Trait(
        name: A
        stmts: array(
            0: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: a
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
        )
    )
    1: Stmt_Class(
        type: 0
        name: B
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_TraitUse(
                traits: array(
                    0: Name(
                        parts: array(
                            0: C
                        )
                    )
                )
                adaptations: array(
                )
            )
            1: Stmt_TraitUse(
                traits: array(
                    0: Name(
                        parts: array(
                            0: D
                        )
                    )
                )
                adaptations: array(
                    0: Stmt_TraitUseAdaptation_Alias(
                        trait: null
                        method: a
                        newModifier: 2
                        newName: b
                    )
                    1: Stmt_TraitUseAdaptation_Alias(
                        trait: null
                        method: c
                        newModifier: null
                        newName: d
                    )
                    2: Stmt_TraitUseAdaptation_Alias(
                        trait: null
                        method: e
                        newModifier: 4
                        newName: null
                    )
                )
            )
            2: Stmt_TraitUse(
                traits: array(
                    0: Name(
                        parts: array(
                            0: E
                        )
                    )
                    1: Name(
                        parts: array(
                            0: F
                        )
                    )
                    2: Name(
                        parts: array(
                            0: G
                        )
                    )
                )
                adaptations: array(
                    0: Stmt_TraitUseAdaptation_Precedence(
                        trait: Name(
                            parts: array(
                                0: E
                            )
                        )
                        method: a
                        insteadof: array(
                            0: Name(
                                parts: array(
                                    0: F
                                )
                            )
                            1: Name(
                                parts: array(
                                    0: G
                                )
                            )
                        )
                    )
                    1: Stmt_TraitUseAdaptation_Alias(
                        trait: Name(
                            parts: array(
                                0: E
                            )
                        )
                        method: b
                        newModifier: 2
                        newName: c
                    )
                    2: Stmt_TraitUseAdaptation_Alias(
                        trait: Name(
                            parts: array(
                                0: E
                            )
                        )
                        method: d
                        newModifier: null
                        newName: e
                    )
                    3: Stmt_TraitUseAdaptation_Alias(
                        trait: Name(
                            parts: array(
                                0: E
                            )
                        )
                        method: f
                        newModifier: 4
                        newName: null
                    )
                )
            )
        )
    )
)
Global constants
-----
<?php

const A = 0, B = 1.0, C = 'A', D = E;
-----
array(
    0: Stmt_Const(
        consts: array(
            0: Const(
                name: A
                value: Scalar_LNumber(
                    value: 0
                )
            )
            1: Const(
                name: B
                value: Scalar_DNumber(
                    value: 1
                )
            )
            2: Const(
                name: C
                value: Scalar_String(
                    value: A
                )
            )
            3: Const(
                name: D
                value: Expr_ConstFetch(
                    name: Name(
                        parts: array(
                            0: E
                        )
                    )
                )
            )
        )
    )
)Control flow statements
-----
<?php

break;
break 2;

continue;
continue 2;

return;
return $a;

throw $e;

label:
goto label;
-----
array(
    0: Stmt_Break(
        num: null
    )
    1: Stmt_Break(
        num: Scalar_LNumber(
            value: 2
        )
    )
    2: Stmt_Continue(
        num: null
    )
    3: Stmt_Continue(
        num: Scalar_LNumber(
            value: 2
        )
    )
    4: Stmt_Return(
        expr: null
    )
    5: Stmt_Return(
        expr: Expr_Variable(
            name: a
        )
    )
    6: Stmt_Throw(
        expr: Expr_Variable(
            name: e
        )
    )
    7: Stmt_Label(
        name: label
    )
    8: Stmt_Goto(
        name: label
    )
)Declare
-----
<?php

declare (A='B', C='D') {}

declare (A='B', C='D'):
enddeclare;
-----
array(
    0: Stmt_Declare(
        declares: array(
            0: Stmt_DeclareDeclare(
                key: A
                value: Scalar_String(
                    value: B
                )
            )
            1: Stmt_DeclareDeclare(
                key: C
                value: Scalar_String(
                    value: D
                )
            )
        )
        stmts: array(
        )
    )
    1: Stmt_Declare(
        declares: array(
            0: Stmt_DeclareDeclare(
                key: A
                value: Scalar_String(
                    value: B
                )
            )
            1: Stmt_DeclareDeclare(
                key: C
                value: Scalar_String(
                    value: D
                )
            )
        )
        stmts: array(
        )
    )
)Echo
-----
<?php

echo 'Hallo World!';
echo 'Hallo', ' ', 'World', '!';
-----
array(
    0: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hallo World!
            )
        )
    )
    1: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hallo
            )
            1: Scalar_String(
                value:
            )
            2: Scalar_String(
                value: World
            )
            3: Scalar_String(
                value: !
            )
        )
    )
)Return and pass by ref
-----
<?php

function a(&$b) {}
function &a($b) {}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: a
        params: array(
            0: Param(
                type: null
                byRef: true
                variadic: false
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
    1: Stmt_Function(
        byRef: true
        name: a
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
)
Conditional function definition
-----
<?php

if (true) {
    function A() {}
}
-----
array(
    0: Stmt_If(
        cond: Expr_ConstFetch(
            name: Name(
                parts: array(
                    0: true
                )
            )
        )
        stmts: array(
            0: Stmt_Function(
                byRef: false
                name: A
                params: array(
                )
                returnType: null
                stmts: array(
                )
            )
        )
        elseifs: array(
        )
        else: null
    )
)
Default values (static scalar tests)
-----
<?php

function a(
    $b = null,
    $c = 'foo',
    $d = A::B,
    $f = +1,
    $g = -1.0,
    $h = array(),
    $i = [],
    $j = ['foo'],
    $k = ['foo', 'bar' => 'baz']
) {}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: a
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: b
                default: Expr_ConstFetch(
                    name: Name(
                        parts: array(
                            0: null
                        )
                    )
                )
            )
            1: Param(
                type: null
                byRef: false
                variadic: false
                name: c
                default: Scalar_String(
                    value: foo
                )
            )
            2: Param(
                type: null
                byRef: false
                variadic: false
                name: d
                default: Expr_ClassConstFetch(
                    class: Name(
                        parts: array(
                            0: A
                        )
                    )
                    name: B
                )
            )
            3: Param(
                type: null
                byRef: false
                variadic: false
                name: f
                default: Expr_UnaryPlus(
                    expr: Scalar_LNumber(
                        value: 1
                    )
                )
            )
            4: Param(
                type: null
                byRef: false
                variadic: false
                name: g
                default: Expr_UnaryMinus(
                    expr: Scalar_DNumber(
                        value: 1
                    )
                )
            )
            5: Param(
                type: null
                byRef: false
                variadic: false
                name: h
                default: Expr_Array(
                    items: array(
                    )
                )
            )
            6: Param(
                type: null
                byRef: false
                variadic: false
                name: i
                default: Expr_Array(
                    items: array(
                    )
                )
            )
            7: Param(
                type: null
                byRef: false
                variadic: false
                name: j
                default: Expr_Array(
                    items: array(
                        0: Expr_ArrayItem(
                            key: null
                            value: Scalar_String(
                                value: foo
                            )
                            byRef: false
                        )
                    )
                )
            )
            8: Param(
                type: null
                byRef: false
                variadic: false
                name: k
                default: Expr_Array(
                    items: array(
                        0: Expr_ArrayItem(
                            key: null
                            value: Scalar_String(
                                value: foo
                            )
                            byRef: false
                        )
                        1: Expr_ArrayItem(
                            key: Scalar_String(
                                value: bar
                            )
                            value: Scalar_String(
                                value: baz
                            )
                            byRef: false
                        )
                    )
                )
            )
        )
        returnType: null
        stmts: array(
        )
    )
)
Generators (yield expression)
-----
<?php

function gen() {
    // statements
    yield;
    yield $value;
    yield $key => $value;

    // expressions
    $data = yield;
    $data = (yield $value);
    $data = (yield $key => $value);

    // yield in language constructs with their own parentheses
    if (yield $foo); elseif (yield $foo);
    if (yield $foo): elseif (yield $foo): endif;
    while (yield $foo);
    do {} while (yield $foo);
    switch (yield $foo) {}
    die(yield $foo);

    // yield in function calls
    func(yield $foo);
    $foo->func(yield $foo);
    new Foo(yield $foo);

    yield from $foo;
    yield from $foo and yield from $bar;
    yield from $foo + $bar;
}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: gen
        params: array(
        )
        returnType: null
        stmts: array(
            0: Expr_Yield(
                key: null
                value: null
            )
            1: Expr_Yield(
                key: null
                value: Expr_Variable(
                    name: value
                )
            )
            2: Expr_Yield(
                key: Expr_Variable(
                    name: key
                )
                value: Expr_Variable(
                    name: value
                )
            )
            3: Expr_Assign(
                var: Expr_Variable(
                    name: data
                )
                expr: Expr_Yield(
                    key: null
                    value: null
                )
            )
            4: Expr_Assign(
                var: Expr_Variable(
                    name: data
                )
                expr: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: value
                    )
                )
            )
            5: Expr_Assign(
                var: Expr_Variable(
                    name: data
                )
                expr: Expr_Yield(
                    key: Expr_Variable(
                        name: key
                    )
                    value: Expr_Variable(
                        name: value
                    )
                )
            )
            6: Stmt_If(
                cond: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
                stmts: array(
                )
                elseifs: array(
                    0: Stmt_ElseIf(
                        cond: Expr_Yield(
                            key: null
                            value: Expr_Variable(
                                name: foo
                            )
                        )
                        stmts: array(
                        )
                    )
                )
                else: null
            )
            7: Stmt_If(
                cond: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
                stmts: array(
                )
                elseifs: array(
                    0: Stmt_ElseIf(
                        cond: Expr_Yield(
                            key: null
                            value: Expr_Variable(
                                name: foo
                            )
                        )
                        stmts: array(
                        )
                    )
                )
                else: null
            )
            8: Stmt_While(
                cond: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
                stmts: array(
                )
            )
            9: Stmt_Do(
                cond: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
                stmts: array(
                )
            )
            10: Stmt_Switch(
                cond: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
                cases: array(
                )
            )
            11: Expr_Exit(
                expr: Expr_Yield(
                    key: null
                    value: Expr_Variable(
                        name: foo
                    )
                )
            )
            12: Expr_FuncCall(
                name: Name(
                    parts: array(
                        0: func
                    )
                )
                args: array(
                    0: Arg(
                        value: Expr_Yield(
                            key: null
                            value: Expr_Variable(
                                name: foo
                            )
                        )
                        byRef: false
                        unpack: false
                    )
                )
            )
            13: Expr_MethodCall(
                var: Expr_Variable(
                    name: foo
                )
                name: func
                args: array(
                    0: Arg(
                        value: Expr_Yield(
                            key: null
                            value: Expr_Variable(
                                name: foo
                            )
                        )
                        byRef: false
                        unpack: false
                    )
                )
            )
            14: Expr_New(
                class: Name(
                    parts: array(
                        0: Foo
                    )
                )
                args: array(
                    0: Arg(
                        value: Expr_Yield(
                            key: null
                            value: Expr_Variable(
                                name: foo
                            )
                        )
                        byRef: false
                        unpack: false
                    )
                )
            )
            15: Expr_YieldFrom(
                expr: Expr_Variable(
                    name: foo
                )
            )
            16: Expr_BinaryOp_LogicalAnd(
                left: Expr_YieldFrom(
                    expr: Expr_Variable(
                        name: foo
                    )
                )
                right: Expr_YieldFrom(
                    expr: Expr_Variable(
                        name: bar
                    )
                )
            )
            17: Expr_YieldFrom(
                expr: Expr_BinaryOp_Plus(
                    left: Expr_Variable(
                        name: foo
                    )
                    right: Expr_Variable(
                        name: bar
                    )
                )
            )
        )
    )
)
Return type declarations
-----
<?php

function test1() {}
function test2() : array {}
function test3() : callable {}
function test4() : Foo\Bar {}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: test1
        params: array(
        )
        returnType: null
        stmts: array(
        )
    )
    1: Stmt_Function(
        byRef: false
        name: test2
        params: array(
        )
        returnType: array
        stmts: array(
        )
    )
    2: Stmt_Function(
        byRef: false
        name: test3
        params: array(
        )
        returnType: callable
        stmts: array(
        )
    )
    3: Stmt_Function(
        byRef: false
        name: test4
        params: array(
        )
        returnType: Name(
            parts: array(
                0: Foo
                1: Bar
            )
        )
        stmts: array(
        )
    )
)
Special function variables
-----
<?php

function a() {
    global $a, ${'b'}, $$c;
    static $c, $d = 'e';
}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: a
        params: array(
        )
        returnType: null
        stmts: array(
            0: Stmt_Global(
                vars: array(
                    0: Expr_Variable(
                        name: a
                    )
                    1: Expr_Variable(
                        name: Scalar_String(
                            value: b
                        )
                    )
                    2: Expr_Variable(
                        name: Expr_Variable(
                            name: c
                        )
                    )
                )
            )
            1: Stmt_Static(
                vars: array(
                    0: Stmt_StaticVar(
                        name: c
                        default: null
                    )
                    1: Stmt_StaticVar(
                        name: d
                        default: Scalar_String(
                            value: e
                        )
                    )
                )
            )
        )
    )
)
Type hints
-----
<?php

function a($b, array $c, callable $d, E $f) {}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: a
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: b
                default: null
            )
            1: Param(
                type: array
                byRef: false
                variadic: false
                name: c
                default: null
            )
            2: Param(
                type: callable
                byRef: false
                variadic: false
                name: d
                default: null
            )
            3: Param(
                type: Name(
                    parts: array(
                        0: E
                    )
                )
                byRef: false
                variadic: false
                name: f
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
)
Variadic functions
-----
<?php
function test($a, ... $b) {}
function test($a, &... $b) {}
function test($a, Type ... $b) {}
function test($a, Type &... $b) {}
-----
array(
    0: Stmt_Function(
        byRef: false
        name: test
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
            1: Param(
                type: null
                byRef: false
                variadic: true
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
    1: Stmt_Function(
        byRef: false
        name: test
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
            1: Param(
                type: null
                byRef: true
                variadic: true
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
    2: Stmt_Function(
        byRef: false
        name: test
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
            1: Param(
                type: Name(
                    parts: array(
                        0: Type
                    )
                )
                byRef: false
                variadic: true
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
    3: Stmt_Function(
        byRef: false
        name: test
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                name: a
                default: null
            )
            1: Param(
                type: Name(
                    parts: array(
                        0: Type
                    )
                )
                byRef: true
                variadic: true
                name: b
                default: null
            )
        )
        returnType: null
        stmts: array(
        )
    )
)
Invalid variadic function
-----
<?php
function foo(...$foo = []) {}
-----
Variadic parameter cannot have a default value from 2:24 to 2:25
__halt_compiler
-----
<?php

$a;
__halt_compiler()
?>
Hallo World!
-----
array(
    0: Expr_Variable(
        name: a
    )
    1: Stmt_HaltCompiler(
        remaining: Hallo World!
    )
)
-----
<?php

$a;
__halt_compiler();Hallo World!
-----
array(
    0: Expr_Variable(
        name: a
    )
    1: Stmt_HaltCompiler(
        remaining: Hallo World!
    )
)
-----
<?php

namespace A;
$a;
__halt_compiler();
-----
array(
    0: Stmt_Namespace(
        name: Name(
            parts: array(
                0: A
            )
        )
        stmts: array(
            0: Expr_Variable(
                name: a
            )
        )
    )
    1: Stmt_HaltCompiler(
        remaining:
    )
)Invalid __halt_compiler() syntax
-----
<?php
__halt_compiler()
-----
__HALT_COMPILER must be followed by "();" on line 2
Use of __HALT_COMPILER_OFFSET__ constant
-----
<?php

var_dump(__HALT_COMPILER_OFFSET__);
__halt_compiler();
Foo
-----
array(
    0: Expr_FuncCall(
        name: Name(
            parts: array(
                0: var_dump
            )
        )
        args: array(
            0: Arg(
                value: Expr_ConstFetch(
                    name: Name(
                        parts: array(
                            0: __HALT_COMPILER_OFFSET__
                        )
                    )
                )
                byRef: false
                unpack: false
            )
        )
    )
    1: Stmt_HaltCompiler(
        remaining:
    Foo
    )
)
__halt_compiler can only be used from outermost scope
-----
<?php
if (true) {
    __halt_compiler();
}
-----
__HALT_COMPILER() can only be used from the outermost scope from 3:5 to 3:19
Hashbang line
-----
#!/usr/bin/env php
<?php

echo "foobar";

?>
#!/usr/bin/env php
-----
array(
    0: Stmt_InlineHTML(
        value: #!/usr/bin/env php

    )
    1: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: foobar
            )
        )
    )
    2: Stmt_InlineHTML(
        value: #!/usr/bin/env php
    )
)
If/Elseif/Else
-----
<?php

if      ($a) {}
elseif  ($b) {}
elseif  ($c) {}
else         {}

if ($a) {} // without else

if      ($a):
elseif  ($b):
elseif  ($c):
else        :
endif;

if ($a): endif; // without else
-----
array(
    0: Stmt_If(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
        elseifs: array(
            0: Stmt_ElseIf(
                cond: Expr_Variable(
                    name: b
                )
                stmts: array(
                )
            )
            1: Stmt_ElseIf(
                cond: Expr_Variable(
                    name: c
                )
                stmts: array(
                )
            )
        )
        else: Stmt_Else(
            stmts: array(
            )
        )
    )
    1: Stmt_If(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
        elseifs: array(
        )
        else: null
    )
    2: Stmt_If(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
        elseifs: array(
            0: Stmt_ElseIf(
                cond: Expr_Variable(
                    name: b
                )
                stmts: array(
                )
            )
            1: Stmt_ElseIf(
                cond: Expr_Variable(
                    name: c
                )
                stmts: array(
                )
            )
        )
        else: Stmt_Else(
            stmts: array(
            )
        )
    )
    3: Stmt_If(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
        elseifs: array(
        )
        else: null
    )
)Inline HTML
-----
<?php
$a;
?>
B
<?php
$c;
?>
<?php
$d;
-----
array(
    0: Expr_Variable(
        name: a
    )
    1: Stmt_InlineHTML(
        value: B

    )
    2: Expr_Variable(
        name: c
    )
    3: Expr_Variable(
        name: d
    )
)Do loop
-----
<?php

do {

} while ($a);
-----
array(
    0: Stmt_Do(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
    )
)For loop
-----
<?php

// "classical" loop
for ($i = 0; $i < $c; ++$i) {}

// multiple expressions
for (;$a,$b;) {}

// infinite loop
for (;;) {}

// alternative syntax
for (;;):
endfor;
-----
array(
    0: Stmt_For(
        init: array(
            0: Expr_Assign(
                var: Expr_Variable(
                    name: i
                )
                expr: Scalar_LNumber(
                    value: 0
                )
            )
        )
        cond: array(
            0: Expr_BinaryOp_Smaller(
                left: Expr_Variable(
                    name: i
                )
                right: Expr_Variable(
                    name: c
                )
            )
        )
        loop: array(
            0: Expr_PreInc(
                var: Expr_Variable(
                    name: i
                )
            )
        )
        stmts: array(
        )
    )
    1: Stmt_For(
        init: array(
        )
        cond: array(
            0: Expr_Variable(
                name: a
            )
            1: Expr_Variable(
                name: b
            )
        )
        loop: array(
        )
        stmts: array(
        )
    )
    2: Stmt_For(
        init: array(
        )
        cond: array(
        )
        loop: array(
        )
        stmts: array(
        )
    )
    3: Stmt_For(
        init: array(
        )
        cond: array(
        )
        loop: array(
        )
        stmts: array(
        )
    )
)Foreach loop
-----
<?php

// foreach on variable
foreach ($a as $b)  {}
foreach ($a as &$b) {}
foreach ($a as $b => $c) {}
foreach ($a as $b => &$c) {}
foreach ($a as list($a, $b)) {}
foreach ($a as $a => list($b, , $c)) {}

// foreach on expression
foreach (array() as $b) {}

// alternative syntax
foreach ($a as $b):
endforeach;
-----
array(
    0: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: null
        byRef: false
        valueVar: Expr_Variable(
            name: b
        )
        stmts: array(
        )
    )
    1: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: null
        byRef: true
        valueVar: Expr_Variable(
            name: b
        )
        stmts: array(
        )
    )
    2: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: Expr_Variable(
            name: b
        )
        byRef: false
        valueVar: Expr_Variable(
            name: c
        )
        stmts: array(
        )
    )
    3: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: Expr_Variable(
            name: b
        )
        byRef: true
        valueVar: Expr_Variable(
            name: c
        )
        stmts: array(
        )
    )
    4: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: null
        byRef: false
        valueVar: Expr_List(
            vars: array(
                0: Expr_Variable(
                    name: a
                )
                1: Expr_Variable(
                    name: b
                )
            )
        )
        stmts: array(
        )
    )
    5: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: Expr_Variable(
            name: a
        )
        byRef: false
        valueVar: Expr_List(
            vars: array(
                0: Expr_Variable(
                    name: b
                )
                1: null
                2: Expr_Variable(
                    name: c
                )
            )
        )
        stmts: array(
        )
    )
    6: Stmt_Foreach(
        expr: Expr_Array(
            items: array(
            )
        )
        keyVar: null
        byRef: false
        valueVar: Expr_Variable(
            name: b
        )
        stmts: array(
        )
    )
    7: Stmt_Foreach(
        expr: Expr_Variable(
            name: a
        )
        keyVar: null
        byRef: false
        valueVar: Expr_Variable(
            name: b
        )
        stmts: array(
        )
    )
)While loop
-----
<?php

while ($a) {}

while ($a):
endwhile;
-----
array(
    0: Stmt_While(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
    )
    1: Stmt_While(
        cond: Expr_Variable(
            name: a
        )
        stmts: array(
        )
    )
)Aliases (use)
-----
<?php

use A\B;
use C\D as E;
use F\G as H, J;

// evil alias notation - Do Not Use!
use \A;
use \A as B;

// function and constant aliases
use function foo\bar;
use function foo\bar as baz;
use const foo\BAR;
use const foo\BAR as BAZ;
-----
array(
    0: Stmt_Use(
        type: 1
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: A
                        1: B
                    )
                )
                alias: B
            )
        )
    )
    1: Stmt_Use(
        type: 1
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: C
                        1: D
                    )
                )
                alias: E
            )
        )
    )
    2: Stmt_Use(
        type: 1
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: F
                        1: G
                    )
                )
                alias: H
            )
            1: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: J
                    )
                )
                alias: J
            )
        )
    )
    3: Stmt_Use(
        type: 1
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: A
                    )
                )
                alias: A
            )
        )
    )
    4: Stmt_Use(
        type: 1
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: A
                    )
                )
                alias: B
            )
        )
    )
    5: Stmt_Use(
        type: 2
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: foo
                        1: bar
                    )
                )
                alias: bar
            )
        )
    )
    6: Stmt_Use(
        type: 2
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: foo
                        1: bar
                    )
                )
                alias: baz
            )
        )
    )
    7: Stmt_Use(
        type: 3
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: foo
                        1: BAR
                    )
                )
                alias: BAR
            )
        )
    )
    8: Stmt_Use(
        type: 3
        uses: array(
            0: Stmt_UseUse(
                name: Name(
                    parts: array(
                        0: foo
                        1: BAR
                    )
                )
                alias: BAZ
            )
        )
    )
)Braced namespaces
-----
<?php

namespace Foo\Bar {
    foo;
}
namespace {
    bar;
}
-----
array(
    0: Stmt_Namespace(
        name: Name(
            parts: array(
                0: Foo
                1: Bar
            )
        )
        stmts: array(
            0: Expr_ConstFetch(
                name: Name(
                    parts: array(
                        0: foo
                    )
                )
            )
        )
    )
    1: Stmt_Namespace(
        name: null
        stmts: array(
            0: Expr_ConstFetch(
                name: Name(
                    parts: array(
                        0: bar
                    )
                )
            )
        )
    )
)Invalid namespace names
-----
<?php namespace self;
-----
Cannot use 'self' as namespace name from 1:17 to 1:20
-----
<?php namespace PARENT;
-----
Cannot use 'PARENT' as namespace name from 1:17 to 1:22
-----
<?php namespace static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NS_SEPARATOR or '{' from 1:17 to 1:22
array(
)
-----
<?php use A as self;
-----
Cannot use A as self because 'self' is a special class name on line 1
-----
<?php use B as PARENT;
-----
Cannot use B as PARENT because 'PARENT' is a special class name on line 1
-----
<?php use C as static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:16 to 1:21
array(
)
Namespace types cannot be mixed
-----
<?php
namespace A;
namespace B {}
-----
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 3
-----
<?php
namespace A {}
namespace B;
-----
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 3Different name types
-----
<?php

A;
A\B;
\A\B;
namespace\A\B;
-----
array(
    0: Expr_ConstFetch(
        name: Name(
            parts: array(
                0: A
            )
        )
    )
    1: Expr_ConstFetch(
        name: Name(
            parts: array(
                0: A
                1: B
            )
        )
    )
    2: Expr_ConstFetch(
        name: Name_FullyQualified(
            parts: array(
                0: A
                1: B
            )
        )
    )
    3: Expr_ConstFetch(
        name: Name_Relative(
            parts: array(
                0: A
                1: B
            )
        )
    )
)Nested namespaces are not allowed
-----
<?php
namespace A {
    namespace B {

    }
}
-----
Namespace declarations cannot be nested from 3:5 to 5:5
Semicolon style namespaces
-----
<?php

namespace Foo\Bar;
foo;

namespace Bar;
bar;
-----
array(
    0: Stmt_Namespace(
        name: Name(
            parts: array(
                0: Foo
                1: Bar
            )
        )
        stmts: array(
            0: Expr_ConstFetch(
                name: Name(
                    parts: array(
                        0: foo
                    )
                )
            )
        )
    )
    1: Stmt_Namespace(
        name: Name(
            parts: array(
                0: Bar
            )
        )
        stmts: array(
            0: Expr_ConstFetch(
                name: Name(
                    parts: array(
                        0: bar
                    )
                )
            )
        )
    )
)Some statements may occur outside of namespaces
-----
<?php
declare(A='B');
namespace B {

}
__halt_compiler()
?>
Hi!
-----
array(
    0: Stmt_Declare(
        declares: array(
            0: Stmt_DeclareDeclare(
                key: A
                value: Scalar_String(
                    value: B
                )
            )
        )
        stmts: array(
        )
    )
    1: Stmt_Namespace(
        name: Name(
            parts: array(
                0: B
            )
        )
        stmts: array(
        )
    )
    2: Stmt_HaltCompiler(
        remaining: Hi!
    )
)There (mostly) can't be statements outside of namespaces
-----
<?php
echo 1;
namespace A;
-----
Namespace declaration statement has to be the very first statement in the script on line 3
-----
<?php
namespace A {}
echo 1;
-----
No code may exist outside of namespace {} on line 3
-----
<?php
namespace A {}
declare(ticks=1);
namespace B {}
-----
No code may exist outside of namespace {} on line 3Switch
-----
<?php

switch ($a) {
    case 0:
    case 1;
    default:
}

// alternative syntax
switch ($a):
endswitch;

// leading semicolon
switch ($a) { ; }
switch ($a): ; endswitch;
-----
array(
    0: Stmt_Switch(
        cond: Expr_Variable(
            name: a
        )
        cases: array(
            0: Stmt_Case(
                cond: Scalar_LNumber(
                    value: 0
                )
                stmts: array(
                )
            )
            1: Stmt_Case(
                cond: Scalar_LNumber(
                    value: 1
                )
                stmts: array(
                )
            )
            2: Stmt_Case(
                cond: null
                stmts: array(
                )
            )
        )
    )
    1: Stmt_Switch(
        cond: Expr_Variable(
            name: a
        )
        cases: array(
        )
    )
    2: Stmt_Switch(
        cond: Expr_Variable(
            name: a
        )
        cases: array(
        )
    )
    3: Stmt_Switch(
        cond: Expr_Variable(
            name: a
        )
        cases: array(
        )
    )
)Try/catch
-----
<?php

try {
    doTry();
} catch (A $b) {
    doCatchA();
} catch (B $c) {
    doCatchB();
} finally {
    doFinally();
}

// no finally
try { }
catch (A $b) { }

// no catch
try { }
finally { }

-----
array(
    0: Stmt_TryCatch(
        stmts: array(
            0: Expr_FuncCall(
                name: Name(
                    parts: array(
                        0: doTry
                    )
                )
                args: array(
                )
            )
        )
        catches: array(
            0: Stmt_Catch(
                type: Name(
                    parts: array(
                        0: A
                    )
                )
                var: b
                stmts: array(
                    0: Expr_FuncCall(
                        name: Name(
                            parts: array(
                                0: doCatchA
                            )
                        )
                        args: array(
                        )
                    )
                )
            )
            1: Stmt_Catch(
                type: Name(
                    parts: array(
                        0: B
                    )
                )
                var: c
                stmts: array(
                    0: Expr_FuncCall(
                        name: Name(
                            parts: array(
                                0: doCatchB
                            )
                        )
                        args: array(
                        )
                    )
                )
            )
        )
        finallyStmts: array(
            0: Expr_FuncCall(
                name: Name(
                    parts: array(
                        0: doFinally
                    )
                )
                args: array(
                )
            )
        )
    )
    1: Stmt_TryCatch(
        stmts: array(
        )
        catches: array(
            0: Stmt_Catch(
                type: Name(
                    parts: array(
                        0: A
                    )
                )
                var: b
                stmts: array(
                )
            )
        )
        finallyStmts: null
    )
    2: Stmt_TryCatch(
        stmts: array(
        )
        catches: array(
        )
        finallyStmts: array(
        )
    )
)Cannot use try without catch or finally
-----
<?php

try { }
-----
Cannot use try without catch or finally on line 3Unset
-----
<?php

unset($a);
unset($b, $c);
-----
array(
    0: Stmt_Unset(
        vars: array(
            0: Expr_Variable(
                name: a
            )
        )
    )
    1: Stmt_Unset(
        vars: array(
            0: Expr_Variable(
                name: b
            )
            1: Expr_Variable(
                name: c
            )
        )
    )
)Aliases (namespacing)
-----
<?php

use A\B;
use C\D as E;
use F\G as H, J;

use function foo\bar;
use function foo\bar as baz;
use const foo\BAR;
use const foo\BAR as BAZ;
-----
use A\B;
use C\D as E;
use F\G as H, J;
use function foo\bar;
use function foo\bar as baz;
use const foo\BAR;
use const foo\BAR as BAZ;Anonymous classes
-----
<?php

new class {};
new class extends A implements B, C {};
new class($a) extends A {
    private $a;
    public function __construct($a) {
        $this->a = $a;
    }
};
-----
new class
{
};
new class extends A implements B, C
{
};
new class($a) extends A
{
    private $a;
    public function __construct($a)
    {
        $this->a = $a;
    }
};
Calls
-----
<?php

f($a);
f(&$a);
f(...$a);
f($a, &$b, ...$c);
-----
f($a);
f(&$a);
f(...$a);
f($a, &$b, ...$c);Class
-----
<?php

class Foo
{
    var $a = 'foo';
    private $b = 'bar';
    static $c = 'baz';
    function test()
    {
        $this->a = 'bar';
        echo 'test';
    }

    protected function baz() {}
    public function foo() {}
    abstract static function bar() {}
}
-----
class Foo
{
    var $a = 'foo';
    private $b = 'bar';
    static $c = 'baz';
    function test()
    {
        $this->a = 'bar';
        echo 'test';
    }
    protected function baz()
    {
    }
    public function foo()
    {
    }
    static abstract function bar()
    {
    }
}Closures
-----
<?php

$closureWithArgs = function ($arg1, $arg2) {
    $comment = 'closure body';
};

$closureWithArgsAndVars = function ($arg1, $arg2) use($var1, $var2) {
    $comment = 'closure body';
};
-----
$closureWithArgs = function ($arg1, $arg2) {
    $comment = 'closure body';
};
$closureWithArgsAndVars = function ($arg1, $arg2) use($var1, $var2) {
    $comment = 'closure body';
};Comments
-----
<?php

function justForIndentation()
{
    // Some text
    # Some text
    /* Some text */
    /** Some text */
    /**
     * Some text.
     * Some more text.
     */
    /*
     * Some text.
     * Some more text.
     */
    /*
        Some text.
        Some more text.
    */
    /* Some text.
       More text. */
    /* Some text.
       More text.
       Even more text. */
    $foo;
}

-----
function justForIndentation()
{
    // Some text
    # Some text
    /* Some text */
    /** Some text */
    /**
     * Some text.
     * Some more text.
     */
    /*
     * Some text.
     * Some more text.
     */
    /*
        Some text.
        Some more text.
    */
    /* Some text.
       More text. */
    /* Some text.
       More text.
       Even more text. */
    $foo;
}Function signatures
-----
<?php

interface A
{
    function f1();
    function f2($a, $b);
    function f3(&$a);
    function f4(A\B $a);
    function f4(array $a);
    function f5(callable $a);
    function f6(&$a);
    function f7(...$a);
    function f8(&...$a);
    function f9(A &$a);
    function f10(A ...$a);
    function f11(A &$a);
    function f12(A &...$a);
    function f13($a) : array;
    function f14($a) : callable;
    function f15($a) : B\C;
}
-----
interface A
{
    function f1();
    function f2($a, $b);
    function f3(&$a);
    function f4(A\B $a);
    function f4(array $a);
    function f5(callable $a);
    function f6(&$a);
    function f7(...$a);
    function f8(&...$a);
    function f9(A &$a);
    function f10(A ...$a);
    function f11(A &$a);
    function f12(A &...$a);
    function f13($a) : array;
    function f14($a) : callable;
    function f15($a) : B\C;
}
Include
-----
<?php

(include $foo) && (include $bar);
-----
(include $foo) && (include $bar);File containing both inline HTML and PHP
-----
HTML
<?php
echo 'PHP';
-----
HTML
<?php
echo 'PHP';
-----
<?php
echo 'PHP';
?>
HTML
-----
<?php

echo 'PHP';
?>
HTML
-----
HTML
<?php
echo 'PHP';
?>
HTML
-----
HTML
<?php
echo 'PHP';
?>
HTML
-----
HTML
<?php
echo 'PHP';
?>
HTML
<?php
echo 'PHP';
?>
HTML
-----
HTML
<?php
echo 'PHP';
?>
HTML
<?php
echo 'PHP';
?>
HTMLlist()
-----
<?php

list() = $a;
list($a) = $b;
list($a, $b, $c) = $d;
list(, $a) = $b;
list(, , $a, , $b) = $c;
list(list($a)) = $b;
list(, list(, list(, $a), $b)) = $c;
-----
list() = $a;
list($a) = $b;
list($a, $b, $c) = $d;
list(, $a) = $b;
list(, , $a, , $b) = $c;
list(list($a)) = $b;
list(, list(, list(, $a), $b)) = $c;
Literals
-----
<?php

// magic constants
__LINE__;
__FILE__;
__DIR__;
__FUNCTION__;
__CLASS__;
__TRAIT__;
__METHOD__;
__NAMESPACE__;

// not actually literals, but close
null;
true;
false;
NULL;
TRUE;
FALSE;

// integers (normalized to decimal)
0;
11;
011;
0x11;
0b11;

// floats (normalized to ... something)
0.;
.0;
0.0;
0e1000;
1.0;
1e100;
1e1000;
1E-100;
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
378282246310005.0;
10000000000000002.0;

// strings (normalized to single quoted)
'a';
'a
b';
"a";
"a\nb";
'a\'b';
"a'b";
"a\b";
<<<'STR'
a\nb$a
{$b}
STR;

// strings (normalized to double quoted)
"$a";
"a$b";
"$a$b";
"$a $b";
"a${b}c";
"a{$b}c";
"a$a[b]c";
"\{$A}";
"\{ $A }";
"\\{$A}";
"\\{ $A }";
"{$$A}[B]";
"$$A[B]";
<<<STR
a\nb$a\n{$b}
STR;

// make sure indentation doesn't mess anything up
function foo()
{
    "a\nb";
    'a
b';
    'a
    b';
}
-----
// magic constants
__LINE__;
__FILE__;
__DIR__;
__FUNCTION__;
__CLASS__;
__TRAIT__;
__METHOD__;
__NAMESPACE__;
// not actually literals, but close
null;
true;
false;
NULL;
TRUE;
FALSE;
// integers (normalized to decimal)
0;
11;
9;
17;
3;
// floats (normalized to ... something)
0.0;
0.0;
0.0;
0.0;
1.0;
1.0E+100;
INF;
1.0E-100;
1.0E+84;
378282246310005.0;
10000000000000002.0;
// strings (normalized to single quoted)
'a';
'a
b';
'a';
'a
b';
'a\'b';
'a\'b';
'a\\b';
'a\\nb$a
{$b}';
// strings (normalized to double quoted)
"{$a}";
"a{$b}";
"{$a}{$b}";
"{$a} {$b}";
"a{$b}c";
"a{$b}c";
"a{$a['b']}c";
"\\{{$A}}";
"\\{ {$A} }";
"\\{$A}";
"\\{ {$A} }";
"{${$A}}[B]";
"\${$A['B']}";
"a\nb{$a}\n{$b}";
// make sure indentation doesn't mess anything up
function foo()
{
    'a
b';
    'a
b';
    'a
    b';
}
Namespaces
-----
<?php

namespace Foo;

function foo()
{
}

namespace Bar;

function bar()
{
}
-----
namespace Foo;

function foo()
{
}
namespace Bar;

function bar()
{
}
-----
<?php

namespace Foo {
    function foo()
    {
    }
}

namespace {
    function glob() {
    }
}
-----
namespace Foo {
    function foo()
    {
    }
}
namespace {
    function glob()
    {
    }
}Number literals
-----
<?php

0;
+0;
-0;
0.0;
-0.0;
42;
-42;
42.0;
-42.0;
42.5;
-42.5;
1e42;
-1e42;
1e1000;
-1e1000;
-----
0;
+0;
-0;
0.0;
-0.0;
42;
-42;
42.0;
-42.0;
42.5;
-42.5;
1.0E+42;
-1.0E+42;
INF;
-INF;File containing only inline HTML
-----
Hallo World
Foo Bar
Bar Foo
World Hallo
-----
Hallo World
Foo Bar
Bar Foo
World HalloFile containing only PHP
-----
<?php

echo 'Foo Bar';
echo 'Bar Foo';
-----
<?php

echo 'Foo Bar';
echo 'Bar Foo';Basic operators
-----
<?php

$a ** $b;

++$a;
--$a;
$a++;
$a--;

@$a;
~$a;
-$a;
+$a;

(int) $a;
(integer) $a;
(float) $a;
(double) $a;
(real) $a;
(string) $a;
(binary) $a;
(array) $a;
(object) $a;
(bool) $a;
(boolean) $a;
(unset) $a;

$a * $b;
$a / $b;
$a % $b;
$a + $b;
$a - $b;
$a . $b;
$a << $b;
$a >> $b;
$a < $b;
$a <= $b;
$a > $b;
$a >= $b;
$a == $b;
$a != $b;
$a <> $b;
$a === $b;
$a !== $b;
$a <=> $b;
$a & $b;
$a ^ $b;
$a | $b;
$a && $b;
$a || $b;
$a ? $b : $c;
$a ?: $c;
$a ?? $c;

$a = $b;
$a **= $b;
$a *= $b;
$a /= $b;
$a %= $b;
$a += $b;
$a -= $b;
$a .= $b;
$a <<= $b;
$a >>= $b;
$a &= $b;
$a ^= $b;
$a |= $b;
$a =& $b;

$a and $b;
$a xor $b;
$a or $b;

$a instanceof Foo;
$a instanceof $b;
-----
$a ** $b;
++$a;
--$a;
$a++;
$a--;
@$a;
~$a;
-$a;
+$a;
(int) $a;
(int) $a;
(double) $a;
(double) $a;
(double) $a;
(string) $a;
(string) $a;
(array) $a;
(object) $a;
(bool) $a;
(bool) $a;
(unset) $a;
$a * $b;
$a / $b;
$a % $b;
$a + $b;
$a - $b;
$a . $b;
$a << $b;
$a >> $b;
$a < $b;
$a <= $b;
$a > $b;
$a >= $b;
$a == $b;
$a != $b;
$a != $b;
$a === $b;
$a !== $b;
$a <=> $b;
$a & $b;
$a ^ $b;
$a | $b;
$a && $b;
$a || $b;
$a ? $b : $c;
$a ?: $c;
$a ?? $c;
$a = $b;
$a **= $b;
$a *= $b;
$a /= $b;
$a %= $b;
$a += $b;
$a -= $b;
$a .= $b;
$a <<= $b;
$a >>= $b;
$a &= $b;
$a ^= $b;
$a |= $b;
$a =& $b;
$a and $b;
$a xor $b;
$a or $b;
$a instanceof Foo;
$a instanceof $b;
Pretty printer generates least-parentheses output
-----
<?php

echo 'abc' . 'cde' . 'fgh';
echo 'abc' . ('cde' . 'fgh');

echo 'abc' . 1 + 2 . 'fgh';
echo 'abc' . (1 + 2) . 'fgh';

echo 1 * 2 + 3 / 4 % 5 . 6;
echo 1 * (2 + 3) / (4 % (5 . 6));

$a = $b = $c = $d = $f && true;
($a = $b = $c = $d = $f) && true;
$a = $b = $c = $d = $f and true;
$a = $b = $c = $d = ($f and true);

$a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f;

$a ?? $b ?? $c;
($a ?? $b) ?? $c;
$a ?? ($b ? $c : $d);
$a || ($b ?? $c);

(1 > 0) > (1 < 0);
++$a + $b;
$a + $b++;

$a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;

yield from $a and yield from $b;
yield from ($a and yield from $b);

print ($a and print $b);

// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!$a = $b;
++$a ** $b;
$a ** $b++;
-----
echo 'abc' . 'cde' . 'fgh';
echo 'abc' . ('cde' . 'fgh');
echo 'abc' . 1 + 2 . 'fgh';
echo 'abc' . (1 + 2) . 'fgh';
echo 1 * 2 + 3 / 4 % 5 . 6;
echo 1 * (2 + 3) / (4 % (5 . 6));
$a = $b = $c = $d = $f && true;
($a = $b = $c = $d = $f) && true;
$a = $b = $c = $d = $f and true;
$a = $b = $c = $d = ($f and true);
$a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f;
$a ?? $b ?? $c;
($a ?? $b) ?? $c;
$a ?? ($b ? $c : $d);
$a || ($b ?? $c);
(1 > 0) > (1 < 0);
++$a + $b;
$a + $b++;
$a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;
yield from $a and yield from $b;
yield from ($a and yield from $b);
print ($a and print $b);
// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!($a = $b);
(++$a) ** $b;
$a ** ($b++);
switch/case/default
-----
<?php

switch ($expr) {
    case 0:
        echo 'First case, with a break';
        break;
    case 1:
        echo 'Second case, which falls through';
    case 2:
    case 3:
    case 4:
        echo 'Third case, return instead of break';
        return;
    default:
        echo 'Default case';
        break;
}
-----
switch ($expr) {
    case 0:
        echo 'First case, with a break';
        break;
    case 1:
        echo 'Second case, which falls through';
    case 2:
    case 3:
    case 4:
        echo 'Third case, return instead of break';
        return;
    default:
        echo 'Default case';
        break;
}Trait uses and adaptations
-----
<?php

class A
{
    use B, C, D {
        f as g;
        f as private;
        f as private g;
        B::f as g;
        B::f insteadof C, D;
    }
}
-----
class A
{
    use B, C, D {
        f as g;
        f as private;
        f as private g;
        B::f as g;
        B::f insteadof C, D;
    }
}Variables
-----
<?php

$a;
$$a;
${$a};
$a->b;
$a->b();
$a->b($c);
$a->$b();
$a->{$b}();
$a->$b[$c]();
$$a->b;
$a[$b];
$a[$b]();
$$a[$b];
$a::B;
$a::$b;
$a::b();
$a::b($c);
$a::$b();
$a::$b[$c];
$a::$b[$c]($d);
$a::{$b[$c]}($d);
$a::{$b->c}();
a();
$a();
$a()[$b];
$a->b()[$c];
$a::$b()[$c];
(new A)->b;
(new A())->b();
(new $$a)[$b];
(new $a->b)->c;

global $a, $$a, $$a[$b], $$a->b;
-----
$a;
${$a};
${$a};
$a->b;
$a->b();
$a->b($c);
$a->{$b}();
$a->{$b}();
$a->{$b[$c]}();
${$a}->b;
$a[$b];
$a[$b]();
${$a[$b]};
$a::B;
$a::$b;
$a::b();
$a::b($c);
$a::$b();
$a::$b[$c];
$a::$b[$c]($d);
$a::$b[$c]($d);
$a::{$b->c}();
a();
$a();
$a()[$b];
$a->b()[$c];
$a::$b()[$c];
(new A())->b;
(new A())->b();
(new ${$a}())[$b];
(new $a->b())->c;
global $a, ${$a}, ${$a[$b]}, ${$a->b};
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('short_open_tag', false);

if ('cli' !== php_sapi_name()) {
    die('This script is designed for running on the command line.');
}

function showHelp($error) {
    die($error . "\n\n" .
<<<OUTPUT
This script has to be called with the following signature:

    php run.php [--no-progress] testType pathToTestFiles

The test type can be either "Symfony" or "PHP".

The following options are available:

    --no-progress    Disables showing which file is currently tested.
OUTPUT
    );
}

$options = array();
$arguments = array();

// remove script name from argv
array_shift($argv);

foreach ($argv as $arg) {
    if ('-' === $arg[0]) {
        $options[] = $arg;
    } else {
        $arguments[] = $arg;
    }
}

if (count($arguments) !== 2) {
    showHelp('Too little arguments passed!');
}

$SHOW_PROGRESS = true;
if (count($options) > 0) {
    if (count($options) === 1 && $options[0] === '--no-progress') {
        $SHOW_PROGRESS = false;
    } else {
        showHelp('Invalid option passed!');
    }
}

$TEST_TYPE = $arguments[0];
$DIR       = $arguments[1];

if ('Symfony' === $TEST_TYPE) {
    function filter_func($path) {
        return preg_match('~\.php(?:\.cache)?$~', $path) && false === strpos($path, 'skeleton');
    };
} elseif ('PHP' === $TEST_TYPE) {
    function filter_func($path) {
        return preg_match('~\.phpt$~', $path);
    };
} else {
    showHelp('Test type must be either "Symfony" or "PHP"!');
}

require_once dirname(__FILE__) . '/../lib/PhpParser/Autoloader.php';
PhpParser\Autoloader::register();

$parser        = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$nodeDumper    = new PhpParser\NodeDumper;

$parseFail = $ppFail = $compareFail = $count = 0;

$readTime = $parseTime = $ppTime = $reparseTime = $compareTime = 0;
$totalStartTime = microtime(true);

foreach (new RecursiveIteratorIterator(
             new RecursiveDirectoryIterator($DIR),
             RecursiveIteratorIterator::LEAVES_ONLY)
         as $file) {
    if (!filter_func($file)) {
        continue;
    }

    $startTime = microtime(true);
    $code = file_get_contents($file);
    $readTime += microtime(true) - $startTime;

    if ('PHP' === $TEST_TYPE) {
        if (preg_match('~(?:
# skeleton files
  ext.gmp.tests.001
| ext.skeleton.tests.001
# multibyte encoded files
| ext.mbstring.tests.zend_multibyte-01
| Zend.tests.multibyte.multibyte_encoding_001
| Zend.tests.multibyte.multibyte_encoding_004
| Zend.tests.multibyte.multibyte_encoding_005
# token_get_all bug (https://bugs.php.net/bug.php?id=60097)
| Zend.tests.bug47516
# pretty print difference due to INF vs 1e1000
| ext.standard.tests.general_functions.bug27678
| tests.lang.bug24640
)\.phpt$~x', $file)) {
            continue;
        }

        if (!preg_match('~--FILE--\s*(.*?)--[A-Z]+--~s', $code, $matches)) {
            continue;
        }
        if (preg_match('~--EXPECT(?:F|REGEX)?--\s*(?:Parse|Fatal) error~', $code)) {
            continue;
        }

        $code = $matches[1];
    }

    set_time_limit(10);

    ++$count;

    if ($SHOW_PROGRESS) {
        echo substr(str_pad('Testing file ' . $count . ': ' . substr($file, strlen($DIR)), 79), 0, 79), "\r";
    }

    try {
        $startTime = microtime(true);
        $stmts = $parser->parse($code);
        $parseTime += microtime(true) - $startTime;

        $startTime = microtime(true);
        $code = '<?php' . "\n" . $prettyPrinter->prettyPrint($stmts);
        $ppTime += microtime(true) - $startTime;

        try {
            $startTime = microtime(true);
            $ppStmts = $parser->parse($code);
            $reparseTime += microtime(true) - $startTime;

            $startTime = microtime(true);
            $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts);
            $compareTime += microtime(true) - $startTime;

            if (!$same) {
                echo $file, ":\n    Result of initial parse and parse after pretty print differ\n";

                ++$compareFail;
            }
        } catch (PhpParser\Error $e) {
            echo $file, ":\n    Parse of pretty print failed with message: {$e->getMessage()}\n";

            ++$ppFail;
        }
    } catch (PhpParser\Error $e) {
        echo $file, ":\n    Parse failed with message: {$e->getMessage()}\n";

        ++$parseFail;
    }
}

if (0 === $parseFail && 0 === $ppFail && 0 === $compareFail) {
    echo "\n\n", 'All tests passed.', "\n";
} else {
    echo "\n\n", '==========', "\n\n", 'There were: ', "\n";
    if (0 !== $parseFail) {
        echo '    ', $parseFail,   ' parse failures.',        "\n";
    }
    if (0 !== $ppFail) {
        echo '    ', $ppFail,      ' pretty print failures.', "\n";
    }
    if (0 !== $compareFail) {
        echo '    ', $compareFail, ' compare failures.',      "\n";
    }
}

echo "\n",
     'Tested files:         ', $count,        "\n",
     "\n",
     'Reading files took:   ', $readTime,    "\n",
     'Parsing took:         ', $parseTime,   "\n",
     'Pretty printing took: ', $ppTime,      "\n",
     'Reparsing took:       ', $reparseTime, "\n",
     'Comparing took:       ', $compareTime, "\n",
     "\n",
     'Total time:           ', microtime(true) - $totalStartTime, "\n",
     'Maximum memory usage: ', memory_get_peak_usage(true), "\n";CHANGELOG
=========

2.3.0
-----

 * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
 * added Process::signal()
 * added Process::getPid()
 * added support for a TTY mode

2.2.0
-----

 * added ProcessBuilder::setArguments() to reset the arguments on a builder
 * added a way to retrieve the standard and error output incrementally
 * added Process:restart()

2.1.0
-----

 * added support for non-blocking processes (start(), wait(), isRunning(), stop())
 * enhanced Windows compatibility
 * added Process::getExitCodeText() that returns a string representation for
   the exit code returned by the process
 * added ProcessBuilder
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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.');
        }

        parent::__construct(
            sprintf(
                'The command "%s" failed.'."\nExit Code: %s(%s)\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
                $process->getCommandLine(),
                $process->getExitCode(),
                $process->getExitCodeText(),
                $process->getOutput(),
                $process->getErrorOutput()
            )
        );

        $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;

/**
 * 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, getenv('open_basedir'));
            $dirs = array();
            foreach ($searchPath as $path) {
                if (is_dir($path)) {
                    $dirs[] = $path;
                } else {
                    $file = str_replace(dirname($path), '', $path);
                    if ($file == $name && is_executable($path)) {
                        return $path;
                    }
                }
            }
        } else {
            $dirs = array_merge(
                explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
                $extraDirs
            );
        }

        $suffixes = array('');
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $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) && (defined('PHP_WINDOWS_VERSION_BUILD') || is_executable($file))) {
                    return $file;
                }
            }
        }

        return $default;
    }
}
Copyright (c) 2004-2013 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\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.
     *
     * @return string|false The PHP executable path or false if it cannot be found
     */
    public function find()
    {
        // PHP_BINARY return the current sapi executable
        if (defined('PHP_BINARY') && PHP_BINARY && ('cli' === PHP_SAPI)) {
            return PHP_BINARY;
        }

        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 (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $dirs[] = 'C:\xampp\php\\';
        }

        return $this->executableFinder->find('php', false, $dirs);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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>
 *
 * @api
 */
class PhpProcess extends Process
{
    private $executableFinder;

    /**
     * Constructor.
     *
     * @param string  $script  The PHP script to run (as a string)
     * @param string  $cwd     The working directory
     * @param array   $env     The environment variables
     * @param integer $timeout The timeout in seconds
     * @param array   $options An array of options for proc_open
     *
     * @api
     */
    public function __construct($script, $cwd = null, array $env = array(), $timeout = 60, array $options = array())
    {
        parent::__construct(null, $cwd, $env, $script, $timeout, $options);

        $this->executableFinder = new PhpExecutableFinder();
    }

    /**
     * Sets the path to the PHP binary to use.
     *
     * @api
     */
    public function setPhpBinary($php)
    {
        $this->setCommandLine($php);
    }

    /**
     * {@inheritdoc}
     */
    public function start($callback = null)
    {
        if (null === $this->getCommandLine()) {
            if (false === $php = $this->executableFinder->find()) {
                throw new RuntimeException('Unable to find the PHP executable.');
            }
            $this->setCommandLine($php);
        }

        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;

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * Process is a thin wrapper around proc_* functions to ease
 * start independent PHP processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
class Process
{
    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;

    private $commandline;
    private $cwd;
    private $env;
    private $stdin;
    private $starttime;
    private $timeout;
    private $options;
    private $exitcode;
    private $fallbackExitcode;
    private $processInformation;
    private $stdout;
    private $stderr;
    private $enhanceWindowsCompatibility;
    private $enhanceSigchildCompatibility;
    private $pipes;
    private $process;
    private $status = self::STATUS_READY;
    private $incrementalOutputOffset;
    private $incrementalErrorOutputOffset;
    private $tty;

    private $fileHandles;
    private $readBytes;

    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  $cwd         The working directory
     * @param array   $env         The environment variables or null to inherit
     * @param string  $stdin       The STDIN content
     * @param integer $timeout     The timeout in seconds
     * @param array   $options     An array of options for proc_open
     *
     * @throws RuntimeException When proc_open is not installed
     *
     * @api
     */
    public function __construct($commandline, $cwd = null, array $env = null, $stdin = 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') || defined('PHP_WINDOWS_VERSION_BUILD'))) {
            $this->cwd = getcwd();
        }
        if (null !== $env) {
            $this->env = array();
            foreach ($env as $key => $value) {
                $this->env[(binary) $key] = (binary) $value;
            }
        } else {
            $this->env = null;
        }
        $this->stdin = $stdin;
        $this->setTimeout($timeout);
        $this->enhanceWindowsCompatibility = true;
        $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled();
        $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
    }

    public function __destruct()
    {
        // stop() will check if we have a process running.
        $this->stop();
    }

    public function __clone()
    {
        $this->exitcode = null;
        $this->fallbackExitcode = null;
        $this->processInformation = null;
        $this->stdout = null;
        $this->stderr = null;
        $this->pipes = null;
        $this->process = null;
        $this->status = self::STATUS_READY;
        $this->fileHandles = null;
        $this->readBytes = null;
    }

    /**
     * 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 callback|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return integer The exit status code
     *
     * @throws RuntimeException When process can't be launch or is stopped
     *
     * @api
     */
    public function run($callback = null)
    {
        $this->start($callback);

        return $this->wait($callback);
    }

    /**
     * Starts the process and returns after sending the 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.
     * If there is no callback passed, the wait() method can be called
     * with true as a second parameter then the callback will get all data occurred
     * in (and since) the start call.
     *
     * @param callback|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws RuntimeException When process can't be launch or is stopped
     * @throws RuntimeException When process is already running
     */
    public function start($callback = null)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running');
        }

        $this->starttime = microtime(true);
        $this->stdout = '';
        $this->stderr = '';
        $this->incrementalOutputOffset = 0;
        $this->incrementalErrorOutputOffset = 0;
        $callback = $this->buildCallback($callback);
        $descriptors = $this->getDescriptors();

        $commandline = $this->commandline;

        if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
            $commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"';
            if (!isset($this->options['bypass_shell'])) {
                $this->options['bypass_shell'] = true;
            }
        }

        $this->process = proc_open($commandline, $descriptors, $this->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;

        foreach ($this->pipes as $pipe) {
            stream_set_blocking($pipe, false);
        }


        if ($this->tty) {
            $this->status = self::STATUS_TERMINATED;
            return;
        }

        if (null === $this->stdin) {
            fclose($this->pipes[0]);
            unset($this->pipes[0]);

            return;
        }

        $writePipes = array($this->pipes[0]);
        unset($this->pipes[0]);
        $stdinLen = strlen($this->stdin);
        $stdinOffset = 0;

        while ($writePipes) {
            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
                $this->processFileHandles($callback);
            }

            $r = $this->pipes;
            $w = $writePipes;
            $e = null;

            $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6));

            if (false === $n) {
                break;
            }
            if ($n === 0) {
                proc_terminate($this->process);

                throw new RuntimeException('The process timed out.');
            }

            if ($w) {
                $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
                if (false !== $written) {
                    $stdinOffset += $written;
                }
                if ($stdinOffset >= $stdinLen) {
                    fclose($writePipes[0]);
                    $writePipes = null;
                }
            }

            foreach ($r as $pipe) {
                $type = array_search($pipe, $this->pipes);
                $data = fread($pipe, 8192);
                if (strlen($data) > 0) {
                    call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
                }
                if (false === $data || feof($pipe)) {
                    fclose($pipe);
                    unset($this->pipes[$type]);
                }
            }

            $this->checkTimeout();
        }

        $this->updateStatus();
    }

    /**
     * Restarts the process.
     *
     * Be warned that the process is cloned before being started.
     *
     * @param callable $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 launch or is stopped
     * @throws \RuntimeException When process is already running
     *
     * @see start()
     */
    public function restart($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 callback|null $callback A valid PHP callback
     *
     * @return integer The exitcode of the process
     *
     * @throws \RuntimeException When process timed out
     * @throws \RuntimeException When process stopped after receiving signal
     */
    public function wait($callback = null)
    {
        $this->updateStatus();
        $callback = $this->buildCallback($callback);
        while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) {
            if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
                $this->processFileHandles($callback, !$this->pipes);
            }
            $this->checkTimeout();

            if ($this->pipes) {
                $r = $this->pipes;
                $w = null;
                $e = null;

                // let's have a look if something changed in streams
                if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
                    $lastError = error_get_last();

                    // stream_select returns false when the `select` system call is interrupted by an incoming signal
                    if (isset($lastError['message']) && false === stripos($lastError['message'], 'interrupted system call')) {
                        $this->pipes = array();
                    }

                    continue;
                }

                // nothing has changed
                if (0 === $n) {
                    continue;
                }

                foreach ($r as $pipe) {
                    $type = array_search($pipe, $this->pipes);
                    $data = fread($pipe, 8192);

                    if (strlen($data) > 0) {
                        // last exit code is output and caught to work around --enable-sigchild
                        if (3 == $type) {
                            $this->fallbackExitcode = (int) $data;
                        } else {
                            call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
                        }
                    }
                    if (false === $data || feof($pipe)) {
                        fclose($pipe);
                        unset($this->pipes[$type]);
                    }
                }
            }
        }
        $this->updateStatus();
        if ($this->processInformation['signaled']) {
            if ($this->isSigchildEnabled()) {
                throw new RuntimeException('The process has been signaled.');
            }

            throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
        }

        $time = 0;
        while ($this->isRunning() && $time < 1000000) {
            $time += 1000;
            usleep(1000);
        }

        $exitcode = proc_close($this->process);

        if ($this->processInformation['signaled']) {
            if ($this->isSigchildEnabled()) {
                throw new RuntimeException('The process has been signaled.');
            }

            throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
        }

        $this->exitcode = $this->processInformation['running'] ? $exitcode : $this->processInformation['exitcode'];

        if (-1 == $this->exitcode && null !== $this->fallbackExitcode) {
            $this->exitcode = $this->fallbackExitcode;
        }

        return $this->exitcode;
    }

    /**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return integer|null The process id if running, null otherwise
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     */
    public function getPid()
    {
        if ($this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
        }

        $this->updateStatus();

        return $this->isRunning() ? $this->processInformation['pid'] : null;
    }

    /**
     * Sends a posix signal to the process.
     *
     * @param  integer $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
     * @throws RuntimeException In case of failure
     */
    public function signal($signal)
    {
        if (!$this->isRunning()) {
            throw new LogicException('Can not send signal on a non running process.');
        }

        if ($this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
        }

        if (true !== @proc_terminate($this->process, $signal)) {
            throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
        }

        return $this;
    }

    /**
     * Returns the current output of the process (STDOUT).
     *
     * @return string The process output
     *
     * @api
     */
    public function getOutput()
    {
        $this->updateOutput();

        return $this->stdout;
    }

    /**
     * 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
     */
    public function getIncrementalOutput()
    {
        $data = $this->getOutput();

        $latest = substr($data, $this->incrementalOutputOffset);
        $this->incrementalOutputOffset = strlen($data);

        return $latest;
    }

    /**
     * Returns the current error output of the process (STDERR).
     *
     * @return string The process error output
     *
     * @api
     */
    public function getErrorOutput()
    {
        $this->updateErrorOutput();

        return $this->stderr;
    }

    /**
     * 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
     */
    public function getIncrementalErrorOutput()
    {
        $data = $this->getErrorOutput();

        $latest = substr($data, $this->incrementalErrorOutputOffset);
        $this->incrementalErrorOutputOffset = strlen($data);

        return $latest;
    }

    /**
     * Returns the exit code returned by the process.
     *
     * @return integer The exit status code
     *
     * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
     *
     * @api
     */
    public function getExitCode()
    {
        if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
        }

        $this->updateStatus();

        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 string A string representation for the exit status code
     *
     * @see http://tldp.org/LDP/abs/html/exitcodes.html
     * @see http://en.wikipedia.org/wiki/Unix_signal
     */
    public function getExitCodeText()
    {
        $exitcode = $this->getExitCode();

        return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
    }

    /**
     * Checks if the process ended successfully.
     *
     * @return Boolean true if the process ended successfully, false otherwise
     *
     * @api
     */
    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 Boolean
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     *
     * @api
     */
    public function hasBeenSignaled()
    {
        if ($this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
        }

        $this->updateStatus();

        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 integer
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     *
     * @api
     */
    public function getTermSignal()
    {
        if ($this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
        }

        $this->updateStatus();

        return $this->processInformation['termsig'];
    }

    /**
     * Returns true if the child process has been stopped by a signal.
     *
     * It always returns false on Windows.
     *
     * @return Boolean
     *
     * @api
     */
    public function hasBeenStopped()
    {
        $this->updateStatus();

        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 integer
     *
     * @api
     */
    public function getStopSignal()
    {
        $this->updateStatus();

        return $this->processInformation['stopsig'];
    }

    /**
     * Checks if the process is currently running.
     *
     * @return Boolean true if the process is currently running, false otherwise
     */
    public function isRunning()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return false;
        }

        $this->updateStatus();

        return $this->processInformation['running'];
    }

    /**
     * Checks if the process has been started with no regard to the current state.
     *
     * @return Boolean true if status is ready, false otherwise
     */
    public function isStarted()
    {
        return $this->status != self::STATUS_READY;
    }

    /**
     * Checks if the process is terminated.
     *
     * @return Boolean true if process is terminated, false otherwise
     */
    public function isTerminated()
    {
        $this->updateStatus();

        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();

        return $this->status;
    }

    /**
     * Stops the process.
     *
     * @param integer|float $timeout The timeout in seconds
     * @param integer       $signal  A posix signal to send in case the process has not stop at timeout, default is SIGKILL
     *
     * @return integer The exit-code of the process
     *
     * @throws RuntimeException if the process got signaled
     */
    public function stop($timeout = 10, $signal = null)
    {
        $timeoutMicro = (int) $timeout*1E6;
        if ($this->isRunning()) {
            proc_terminate($this->process);
            $time = 0;
            while (1 == $this->isRunning() && $time < $timeoutMicro) {
                $time += 1000;
                usleep(1000);
            }

            if ($this->isRunning() && !$this->isSigchildEnabled()) {
                if (null !== $signal || defined('SIGKILL')) {
                    $this->signal($signal ?: SIGKILL);
                }
            }

            foreach ($this->pipes as $pipe) {
                fclose($pipe);
            }
            $this->pipes = array();

            $exitcode = proc_close($this->process);
            $this->exitcode = -1 === $this->processInformation['exitcode'] ? $exitcode : $this->processInformation['exitcode'];

            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
                foreach ($this->fileHandles as $fileHandle) {
                    fclose($fileHandle);
                }
                $this->fileHandles = array();
            }
        }
        $this->status = self::STATUS_TERMINATED;

        return $this->exitcode;
    }

    /**
     * Adds a line to the STDOUT stream.
     *
     * @param string $line The line to append
     */
    public function addOutput($line)
    {
        $this->stdout .= $line;
    }

    /**
     * Adds a line to the STDERR stream.
     *
     * @param string $line The line to append
     */
    public function addErrorOutput($line)
    {
        $this->stderr .= $line;
    }

    /**
     * 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.
     *
     * @return integer|null The timeout in seconds or null if it's disabled
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Sets the process timeout.
     *
     * To disable the timeout, set this value to null.
     *
     * @param float|null $timeout The timeout in seconds
     *
     * @return self The current Process instance
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    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;
    }

    /**
     * Enables or disables the TTY mode.
     *
     * @param boolean $tty True to enabled and false to disable
     *
     * @return self The current Process instance
     */
    public function setTty($tty)
    {
        $this->tty = (Boolean) $tty;

        return $this;
    }

    /**
     * Checks if  the TTY mode is enabled.
     *
     * @return Boolean true if the TTY mode is enabled, false otherwise
     */
    public function isTty()
    {
        return $this->tty;
    }

    /**
     * Gets the working directory.
     *
     * @return string The current working directory
     */
    public function getWorkingDirectory()
    {
        // This is for BC only
        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.
     *
     * @param array $env The new environment variables
     *
     * @return self The current Process instance
     */
    public function setEnv(array $env)
    {
        $this->env = $env;

        return $this;
    }

    /**
     * Gets the contents of STDIN.
     *
     * @return string The current contents
     */
    public function getStdin()
    {
        return $this->stdin;
    }

    /**
     * Sets the contents of STDIN.
     *
     * @param string $stdin The new contents
     *
     * @return self The current Process instance
     */
    public function setStdin($stdin)
    {
        $this->stdin = $stdin;

        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 Boolean
     */
    public function getEnhanceWindowsCompatibility()
    {
        return $this->enhanceWindowsCompatibility;
    }

    /**
     * Sets whether or not Windows compatibility is enabled.
     *
     * @param Boolean $enhance
     *
     * @return self The current Process instance
     */
    public function setEnhanceWindowsCompatibility($enhance)
    {
        $this->enhanceWindowsCompatibility = (Boolean) $enhance;

        return $this;
    }

    /**
     * Returns whether sigchild compatibility mode is activated or not.
     *
     * @return Boolean
     */
    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 Boolean $enhance
     *
     * @return self The current Process instance
     */
    public function setEnhanceSigchildCompatibility($enhance)
    {
        $this->enhanceSigchildCompatibility = (Boolean) $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 RuntimeException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new RuntimeException('The process timed-out.');
        }
    }

    /**
     * Creates the descriptors needed by the proc_open.
     *
     * @return array
     */
    private function getDescriptors()
    {
        //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
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->fileHandles = array(
                self::STDOUT => tmpfile(),
            );
            if (false === $this->fileHandles[self::STDOUT]) {
                throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
            }
            $this->readBytes = array(
                self::STDOUT => 0,
            );
            
            return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w'));
        } 

        if ($this->tty) {
            $descriptors = array(
                array('file', '/dev/tty', 'r'),
                array('file', '/dev/tty', 'w'),
                array('file', '/dev/tty', 'w'),
            );
        } else {
           $descriptors = array(
                array('pipe', 'r'), // stdin
                array('pipe', 'w'), // stdout
                array('pipe', 'w'), // stderr
            );
        }

        if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
            $descriptors = array_merge($descriptors, array(array('pipe', 'w')));

            $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
        }

        return $descriptors;
    }

    /**
     * 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 callback|null $callback The user defined PHP callback
     *
     * @return callback A PHP callable
     */
    protected function buildCallback($callback)
    {
        $that = $this;
        $out = self::OUT;
        $err = self::ERR;
        $callback = function ($type, $data) use ($that, $callback, $out, $err) {
            if ($out == $type) {
                $that->addOutput($data);
            } else {
                $that->addErrorOutput($data);
            }

            if (null !== $callback) {
                call_user_func($callback, $type, $data);
            }
        };

        return $callback;
    }

    /**
     * Updates the status of the process.
     */
    protected function updateStatus()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        $this->processInformation = proc_get_status($this->process);
        if (!$this->processInformation['running']) {
            $this->status = self::STATUS_TERMINATED;
            if (-1 !== $this->processInformation['exitcode']) {
                $this->exitcode = $this->processInformation['exitcode'];
            }
        }
    }

    /**
     * Updates the current error output of the process (STDERR).
     */
    protected function updateErrorOutput()
    {
        if (isset($this->pipes[self::STDERR]) && is_resource($this->pipes[self::STDERR])) {
            $this->addErrorOutput(stream_get_contents($this->pipes[self::STDERR]));
        }
    }

    /**
     * Updates the current output of the process (STDOUT).
     */
    protected function updateOutput()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($this->fileHandles[self::STDOUT]) && is_resource($this->fileHandles[self::STDOUT])) {
            fseek($this->fileHandles[self::STDOUT], $this->readBytes[self::STDOUT]);
            $this->addOutput(stream_get_contents($this->fileHandles[self::STDOUT]));
        } elseif (isset($this->pipes[self::STDOUT]) && is_resource($this->pipes[self::STDOUT])) {
            $this->addOutput(stream_get_contents($this->pipes[self::STDOUT]));
        }
    }

    /**
     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
     *
     * @return Boolean
     */
    protected function isSigchildEnabled()
    {
        if (null !== self::$sigchild) {
            return self::$sigchild;
        }

        ob_start();
        phpinfo(INFO_GENERAL);

        return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
    }

    /**
     * Handles the windows file handles fallbacks.
     *
     * @param callable $callback A valid PHP callback
     * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
     */
    private function processFileHandles($callback, $closeEmptyHandles = false)
    {
        $fh = $this->fileHandles;
        foreach ($fh as $type => $fileHandle) {
            fseek($fileHandle, $this->readBytes[$type]);
            $data = fread($fileHandle, 8192);
            if (strlen($data) > 0) {
                $this->readBytes[$type] += strlen($data);
                call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
            }
            if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
                fclose($fileHandle);
                unset($this->fileHandles[$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\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;
    private $stdin;
    private $timeout;
    private $options;
    private $inheritEnv;
    private $prefix;

    public function __construct(array $arguments = array())
    {
        $this->arguments = $arguments;

        $this->timeout = 60;
        $this->options = array();
        $this->env = array();
        $this->inheritEnv = true;
    }

    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 an unescaped prefix to the command string.
     *
     * The prefix is preserved when reseting arguments.
     *
     * @param string $prefix A command prefix
     *
     * @return ProcessBuilder
     */
    public function setPrefix($prefix)
    {
        $this->prefix = $prefix;

        return $this;
    }

    /**
     * @param array $arguments
     *
     * @return ProcessBuilder
     */
    public function setArguments(array $arguments)
    {
        $this->arguments = $arguments;

        return $this;
    }

    public function setWorkingDirectory($cwd)
    {
        $this->cwd = $cwd;

        return $this;
    }

    public function inheritEnvironmentVariables($inheritEnv = true)
    {
        $this->inheritEnv = $inheritEnv;

        return $this;
    }

    public function setEnv($name, $value)
    {
        $this->env[$name] = $value;

        return $this;
    }

    public function setInput($stdin)
    {
        $this->stdin = $stdin;

        return $this;
    }

    /**
     * Sets the process timeout.
     *
     * To disable the timeout, set this value to null.
     *
     * @param float|null
     *
     * @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;
    }

    public function setOption($name, $value)
    {
        $this->options[$name] = $value;

        return $this;
    }

    public function getProcess()
    {
        if (!$this->prefix && !count($this->arguments)) {
            throw new LogicException('You must add() command arguments before calling getProcess().');
        }

        $options = $this->options;

        $arguments = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments;
        $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));

        if ($this->inheritEnv) {
            $env = $this->env ? $this->env + $_ENV : null;
        } else {
            $env = $this->env;
        }

        return new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $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\Process;

/**
 * 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 dosn`t work on windows
        //@see https://bugs.php.net/bug.php?id=43784
        //@see https://bugs.php.net/bug.php?id=49446
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $escapedArgument = '';
            foreach(preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
                if ('"' == $part) {
                    $escapedArgument .= '\\"';
                } elseif ('%' == $part) {
                    $escapedArgument .= '^%';
                } else {
                    $escapedArgument .= escapeshellarg($part);
                }
            }

            return $escapedArgument;
        }

        return escapeshellarg($argument);
    }
}
Process Component
=================

Process executes commands in sub-processes.

In this example, we run a simple directory listing and get the result back:

    use Symfony\Component\Process\Process;

    $process = new Process('ls -lsa');
    $process->setTimeout(3600);
    $process->run();
    if (!$process->isSuccessful()) {
        throw new RuntimeException($process->getErrorOutput());
    }

    print $process->getOutput();

You can think that this is easy to achieve with plain PHP but it's not especially
if you want to take care of the subtle differences between the different platforms.

And if you want to be able to get some feedback in real-time, just pass an
anonymous function to the ``run()`` method and you will get the output buffer
as it becomes available:

    use Symfony\Component\Process\Process;

    $process = new Process('ls -lsa');
    $process->run(function ($type, $buffer) {
        if ('err' === $type) {
            echo 'ERR > '.$buffer;
        } else {
            echo 'OUT > '.$buffer;
        }
    });

That's great if you want to execute a long running command (like rsync-ing files to a
remote server) and give feedback to the user in real-time.

Resources
---------

You can run the unit tests with the following command:

    $ cd path/to/Symfony/Component/XXX/
    $ composer.phar install --dev
    $ phpunit
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
     */
    public function testNegativeTimeoutFromConstructor()
    {
        $this->getProcess('', null, null, null, -1);
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
     */
    public function testNegativeTimeoutFromSetter()
    {
        $p = $this->getProcess('');
        $p->setTimeout(-1);
    }

    public function testNullTimeout()
    {
        $p = $this->getProcess('');
        $p->setTimeout(10);
        $p->setTimeout(null);

        $this->assertNull($p->getTimeout());
    }

    public function testStopWithTimeoutIsActuallyWorking()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Stop with timeout does not work on windows, it requires posix signals');
        }

        // exec is mandatory here since we send a signal to the process
        // see https://github.com/symfony/symfony/issues/5030 about prepending
        // command with exec
        $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3');
        $p->start();
        usleep(100000);
        $start = microtime(true);
        $p->stop(1.1, SIGKILL);
        while ($p->isRunning()) {
            usleep(1000);
        }
        $duration = microtime(true) - $start;

        $this->assertLessThan(1.3, $duration);
    }

    /**
     * tests results from sub processes
     *
     * @dataProvider responsesCodeProvider
     */
    public function testProcessResponses($expected, $getter, $code)
    {
        $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
        $p->run();

        $this->assertSame($expected, $p->$getter());
    }

    /**
     * tests results from sub processes
     *
     * @dataProvider pipesCodeProvider
     */
    public function testProcessPipes($code, $size)
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Test hangs on Windows & PHP due to https://bugs.php.net/bug.php?id=60120 and https://bugs.php.net/bug.php?id=51800');
        }

        $expected = str_repeat(str_repeat('*', 1024), $size) . '!';
        $expectedLength = (1024 * $size) + 1;

        $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
        $p->setStdin($expected);
        $p->run();

        $this->assertEquals($expectedLength, strlen($p->getOutput()));
        $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
    }

    public function chainedCommandsOutputProvider()
    {
        return array(
            array("1\n1\n", ';', '1'),
            array("2\n2\n", '&&', '2'),
        );
    }

    /**
     *
     * @dataProvider chainedCommandsOutputProvider
     */
    public function testChainedCommandsOutput($expected, $operator, $input)
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Does it work on windows ?');
        }

        $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
        $process->run();
        $this->assertEquals($expected, $process->getOutput());
    }

    public function testCallbackIsExecutedForOutput()
    {
        $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';')));

        $called = false;
        $p->run(function ($type, $buffer) use (&$called) {
            $called = $buffer === 'foo';
        });

        $this->assertTrue($called, 'The callback should be executed with the output');
    }

    public function testGetErrorOutput()
    {
        $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));

        $p->run();
        $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
    }

    public function testGetIncrementalErrorOutput()
    {
        $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(50000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));

        $p->start();
        while ($p->isRunning()) {
            $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches));
            usleep(20000);
        }
    }

    public function testGetOutput()
    {
        $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));

        $p->run();
        $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
    }

    public function testGetIncrementalOutput()
    {
        $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) { echo \' foo \'; usleep(50000); $n++; }')));

        $p->start();
        while ($p->isRunning()) {
            $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches));
            usleep(20000);
        }
    }

    public function testExitCodeCommandFailed()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX exit code');
        }

        // such command run in bash return an exitcode 127
        $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
        $process->run();

        $this->assertGreaterThan(0, $process->getExitCode());
    }

    public function testTTYCommand()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does have /dev/tty support');
        }

        $process = $this->getProcess('echo "foo" >> /dev/null');
        $process->setTTY(true);
        $process->run();

        $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
    }

    public function testExitCodeText()
    {
        $process = $this->getProcess('');
        $r = new \ReflectionObject($process);
        $p = $r->getProperty('exitcode');
        $p->setAccessible(true);

        $p->setValue($process, 2);
        $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
    }

    public function testStartIsNonBlocking()
    {
        $process = $this->getProcess('php -r "sleep(4);"');
        $start = microtime(true);
        $process->start();
        $end = microtime(true);
        $this->assertLessThan(1 , $end-$start);
    }

    public function testUpdateStatus()
    {
        $process = $this->getProcess('php -h');
        $process->run();
        $this->assertTrue(strlen($process->getOutput()) > 0);
    }

    public function testGetExitCode()
    {
        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertEquals(0, $process->getExitCode());
    }

    public function testStatus()
    {
        $process = $this->getProcess('php -r "usleep(500000);"');
        $this->assertFalse($process->isRunning());
        $this->assertFalse($process->isStarted());
        $this->assertFalse($process->isTerminated());
        $this->assertSame(Process::STATUS_READY, $process->getStatus());
        $process->start();
        $this->assertTrue($process->isRunning());
        $this->assertTrue($process->isStarted());
        $this->assertFalse($process->isTerminated());
        $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
        $process->wait();
        $this->assertFalse($process->isRunning());
        $this->assertTrue($process->isStarted());
        $this->assertTrue($process->isTerminated());
        $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
    }

    public function testStop()
    {
        $process = $this->getProcess('php -r "while (true) {}"');
        $process->start();
        $this->assertTrue($process->isRunning());
        $process->stop();
        $this->assertFalse($process->isRunning());
    }

    public function testIsSuccessful()
    {
        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertTrue($process->isSuccessful());
    }

    public function testIsNotSuccessful()
    {
        $process = $this->getProcess('php -r "while (true) {}"');
        $process->start();
        $this->assertTrue($process->isRunning());
        $process->stop();
        $this->assertFalse($process->isSuccessful());
    }

    public function testProcessIsNotSignaled()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertFalse($process->hasBeenSignaled());
    }

    public function testProcessWithoutTermSignalIsNotSignaled()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertFalse($process->hasBeenSignaled());
    }

    public function testProcessWithoutTermSignal()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertEquals(0, $process->getTermSignal());
    }

    public function testProcessIsSignaledIfStopped()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        $process = $this->getProcess('php -r "while (true) {}"');
        $process->start();
        $process->stop();
        $this->assertTrue($process->hasBeenSignaled());
    }

    public function testProcessWithTermSignal()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        // SIGTERM is only defined if pcntl extension is present
        $termSignal = defined('SIGTERM') ? SIGTERM : 15;

        $process = $this->getProcess('php -r "while (true) {}"');
        $process->start();
        $process->stop();

        $this->assertEquals($termSignal, $process->getTermSignal());
    }

    public function testProcessThrowsExceptionWhenExternallySignaled()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('Windows does not support POSIX signals');
        }

        if (!function_exists('posix_kill')) {
            $this->markTestSkipped('posix_kill is required for this test');
        }

        $termSignal = defined('SIGKILL') ? SIGKILL : 9;

        $process = $this->getProcess('exec php -r "while (true) {}"');
        $process->start();
        posix_kill($process->getPid(), $termSignal);

        $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".');
        $process->wait();
    }

    public function testRestart()
    {
        $process1 = $this->getProcess('php -r "echo getmypid();"');
        $process1->run();
        $process2 = $process1->restart();

        usleep(300000); // wait for output

        // Ensure that both processed finished and the output is numeric
        $this->assertFalse($process1->isRunning());
        $this->assertFalse($process2->isRunning());
        $this->assertTrue(is_numeric($process1->getOutput()));
        $this->assertTrue(is_numeric($process2->getOutput()));

        // Ensure that restart returned a new process by check that the output is different
        $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
    }

    public function testPhpDeadlock()
    {
        $this->markTestSkipped('Can course php to hang');

        // Sleep doesn't work as it will allow the process to handle signals and close
        // file handles from the other end.
        $process = $this->getProcess('php -r "while (true) {}"');
        $process->start();

        // PHP will deadlock when it tries to cleanup $process
    }

    public function testRunProcessWithTimeout()
    {
        $timeout = 0.5;
        $process = $this->getProcess('sleep 3');
        $process->setTimeout($timeout);
        $start = microtime(true);
        try {
            $process->run();
            $this->fail('A RuntimeException should have been raised');
        } catch (RuntimeException $e) {

        }
        $duration = microtime(true) - $start;

        $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
    }

    public function testCheckTimeoutOnStartedProcess()
    {
        $timeout = 0.5;
        $precision = 100000;
        $process = $this->getProcess('sleep 3');
        $process->setTimeout($timeout);
        $start = microtime(true);

        $process->start();

        try {
            while ($process->isRunning()) {
                $process->checkTimeout();
                usleep($precision);
            }
            $this->fail('A RuntimeException should have been raised');
        } catch (RuntimeException $e) {

        }
        $duration = microtime(true) - $start;

        $this->assertLessThan($timeout + $precision, $duration);
    }

    public function testGetPid()
    {
        $process = $this->getProcess('php -r "sleep(1);"');
        $process->start();
        $this->assertGreaterThan(0, $process->getPid());
        $process->stop();
    }

    public function testGetPidIsNullBeforeStart()
    {
        $process = $this->getProcess('php -r "sleep(1);"');
        $this->assertNull($process->getPid());
    }

    public function testGetPidIsNullAfterRun()
    {
        $process = $this->getProcess('php -m');
        $process->run();
        $this->assertNull($process->getPid());
    }

    public function testSignal()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('POSIX signals do not work on windows');
        }

        $process = $this->getProcess('exec php -f ' . __DIR__ . '/SignalListener.php');
        $process->start();
        usleep(500000);
        $process->signal(SIGUSR1);

        while ($process->isRunning() && false === strpos($process->getoutput(), 'Caught SIGUSR1')) {
            usleep(10000);
        }

        $this->assertEquals('Caught SIGUSR1', $process->getOutput());
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\LogicException
     */
    public function testSignalProcessNotRunning()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('POSIX signals do not work on windows');
        }

        $process = $this->getProcess('php -m');
        $process->signal(SIGHUP);
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignalWithWrongIntSignal()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('POSIX signals do not work on windows');
        }

        $process = $this->getProcess('php -r "sleep(3);"');
        $process->start();
        $process->signal(-4);
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignalWithWrongNonIntSignal()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('POSIX signals do not work on windows');
        }

        $process = $this->getProcess('php -r "sleep(3);"');
        $process->start();
        $process->signal('Céphalopodes');
    }

    public function responsesCodeProvider()
    {
        return array(
            //expected output / getter / code to execute
            //array(1,'getExitCode','exit(1);'),
            //array(true,'isSuccessful','exit();'),
            array('output', 'getOutput', 'echo \'output\';'),
        );
    }

    public function pipesCodeProvider()
    {
        $variations = array(
            'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
            'include \''.__DIR__.'/ProcessTestHelper.php\';',
        );

        $codes = array();
        foreach (array(1, 16, 64, 1024, 4096) as $size) {
            foreach ($variations as $code) {
                $codes[] = array($code, $size);
            }
        }

        return $codes;
    }

    /**
     * provides default method names for simple getter/setter
     */
    public function methodProvider()
    {
        $defaults = array(
            array('CommandLine'),
            array('Timeout'),
            array('WorkingDirectory'),
            array('Env'),
            array('Stdin'),
            array('Options')
        );

        return $defaults;
    }

    /**
     * @param string  $commandline
     * @param null    $cwd
     * @param array   $env
     * @param null    $stdin
     * @param integer $timeout
     * @param array   $options
     *
     * @return Process
     */
    abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array());
}
<?php

/**
 * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds
 *
 * @args duration Run this script with a custom duration
 *
 * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
 */

function handleSignal($signal)
{
    switch ($signal) {
        case SIGTERM:
            $name = 'SIGTERM';
            break;
        case SIGINT:
            $name = 'SIGINT';
            break;
        default:
            $name = $signal . ' (unknown)';
            break;
    }

    echo "received signal $name\n";
}

declare(ticks=1);
pcntl_signal(SIGTERM, 'handleSignal');
pcntl_signal(SIGINT, 'handleSignal');

$duration = isset($argv[1]) ? (int) $argv[1] : 3;
$start = microtime(true);

while ($duration > (microtime(true) - $start)) {
    usleep(1000);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\PhpExecutableFinder;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase
{
    /**
     * tests find() with the env var PHP_PATH
     */
    public function testFindWithPhpPath()
    {
        if (defined('PHP_BINARY')) {
            $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4');
        }

        $f = new PhpExecutableFinder();

        $current = $f->find();

        //not executable PHP_PATH
        putenv('PHP_PATH=/not/executable/php');
        $this->assertFalse($f->find(), '::find() returns false for not executable php');

        //executable PHP_PATH
        putenv('PHP_PATH='.$current);
        $this->assertEquals($f->find(), $current, '::find() returns the executable php');
    }

    /**
     * tests find() with default executable
     */
    public function testFindWithSuffix()
    {
        if (defined('PHP_BINARY')) {
            $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4');
        }

        putenv('PHP_PATH=');
        putenv('PHP_PEAR_PHP_BIN=');
        $f = new PhpExecutableFinder();

        $current = $f->find();

        //TODO maybe php executable is custom or even windows
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertTrue(is_executable($current));
            $this->assertTrue((bool) preg_match('/'.addSlashes(DIRECTORY_SEPARATOR).'php\.(exe|bat|cmd|com)$/i', $current), '::find() returns the executable php with suffixes');
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\PhpProcess;

class PhpProcessTest extends \PHPUnit_Framework_TestCase
{
    public function testNonBlockingWorks()
    {
        $expected = 'hello world!';
        $process = new PhpProcess(<<<PHP
<?php echo '$expected';
PHP
        );
        $process->start();
        $process->wait();
        $this->assertEquals($expected, $process->getOutput());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\ProcessBuilder;

class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
{
    public function testInheritEnvironmentVars()
    {
        $snapshot = $_ENV;
        $_ENV = $expected = array('foo' => 'bar');

        $pb = new ProcessBuilder();
        $pb->add('foo')->inheritEnvironmentVariables();
        $proc = $pb->getProcess();

        $this->assertNull($proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV');

        $_ENV = $snapshot;
    }

    public function testProcessShouldInheritAndOverrideEnvironmentVars()
    {
        $snapshot = $_ENV;
        $_ENV = array('foo' => 'bar', 'bar' => 'baz');
        $expected = array('foo' => 'foo', 'bar' => 'baz');

        $pb = new ProcessBuilder();
        $pb->add('foo')->inheritEnvironmentVariables()
            ->setEnv('foo', 'foo');
        $proc = $pb->getProcess();

        $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV');

        $_ENV = $snapshot;
    }

    public function testInheritEnvironmentVarsByDefault()
    {
        $pb = new ProcessBuilder();
        $proc = $pb->add('foo')->getProcess();

        $this->assertNull($proc->getEnv());
    }

    public function testNotReplaceExplicitlySetVars()
    {
        $snapshot = $_ENV;
        $_ENV = array('foo' => 'bar');
        $expected = array('foo' => 'baz');

        $pb = new ProcessBuilder();
        $pb
            ->setEnv('foo', 'baz')
            ->inheritEnvironmentVariables()
            ->add('foo')
        ;
        $proc = $pb->getProcess();

        $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV');

        $_ENV = $snapshot;
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
     */
    public function testNegativeTimeoutFromSetter()
    {
        $pb = new ProcessBuilder();
        $pb->setTimeout(-1);
    }

    public function testNullTimeout()
    {
        $pb = new ProcessBuilder();
        $pb->setTimeout(10);
        $pb->setTimeout(null);

        $r = new \ReflectionObject($pb);
        $p = $r->getProperty('timeout');
        $p->setAccessible(true);

        $this->assertNull($p->getValue($pb));
    }

    public function testShouldSetArguments()
    {
        $pb = new ProcessBuilder(array('initial'));
        $pb->setArguments(array('second'));

        $proc = $pb->getProcess();

        $this->assertContains("second", $proc->getCommandLine());
    }

    public function testPrefixIsPrependedToAllGeneratedProcess()
    {
        $pb = new ProcessBuilder();
        $pb->setPrefix('/usr/bin/php');

        $proc = $pb->setArguments(array('-v'))->getProcess();
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine());
        } else {
            $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine());
        }

        $proc = $pb->setArguments(array('-i'))->getProcess();
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine());
        } else {
            $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine());
        }
    }

    public function testShouldEscapeArguments()
    {
        $pb = new ProcessBuilder(array('%path%', 'foo " bar'));
        $proc = $pb->getProcess();

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine());
        } else {
            $this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine());
        }
    }

    public function testShouldEscapeArgumentsAndPrefix()
    {
        $pb = new ProcessBuilder(array('arg'));
        $pb->setPrefix('%prefix%');
        $proc = $pb->getProcess();

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine());
        } else {
            $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine());
        }
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\LogicException
     */
    public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument()
    {
        ProcessBuilder::create()->getProcess();
    }

    public function testShouldNotThrowALogicExceptionIfNoArgument()
    {
        $process = ProcessBuilder::create()
            ->setPrefix('/usr/bin/php')
            ->getProcess();

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
        } else {
            $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
        }
    }

    public function testShouldNotThrowALogicExceptionIfNoPrefix()
    {
        $process = ProcessBuilder::create(array('/usr/bin/php'))
            ->getProcess();

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
        } else {
            $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\Exception\ProcessFailedException;

/**
 * @author Sebastian Marek <proofek@gmail.com>
 */
class ProcessFailedExceptionTest extends \PHPUnit_Framework_TestCase
{
    /**
     * tests ProcessFailedException throws exception if the process was successful
     */
    public function testProcessFailedExceptionThrowsException()
    {
        $process = $this->getMock(
            'Symfony\Component\Process\Process',
            array('isSuccessful'),
            array('php')
        );
        $process->expects($this->once())
            ->method('isSuccessful')
            ->will($this->returnValue(true));

        $this->setExpectedException(
            '\InvalidArgumentException',
            'Expected a failed process, but the given process was successful.'
        );

        new ProcessFailedException($process);
    }

    /**
     * tests ProcessFailedException uses information from process output
     * to generate exception message
     */
    public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
    {
        $cmd = 'php';
        $exitCode = 1;
        $exitText = 'General error';
        $output = "Command output";
        $errorOutput = "FATAL: Unexpected error";

        $process = $this->getMock(
            'Symfony\Component\Process\Process',
            array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText'),
            array($cmd)
        );
        $process->expects($this->once())
            ->method('isSuccessful')
            ->will($this->returnValue(false));
        $process->expects($this->once())
            ->method('getOutput')
            ->will($this->returnValue($output));
        $process->expects($this->once())
            ->method('getErrorOutput')
            ->will($this->returnValue($errorOutput));
        $process->expects($this->once())
            ->method('getExitCode')
            ->will($this->returnValue($exitCode));
        $process->expects($this->once())
            ->method('getExitCodeText')
            ->will($this->returnValue($exitText));

        $exception = new ProcessFailedException($process);

        $this->assertEquals(
            "The command \"$cmd\" failed.\nExit Code: $exitCode($exitText)\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
            $exception->getMessage()
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\Process;

class ProcessInSigchildEnvironment extends Process
{
    protected function isSigchildEnabled()
    {
        return true;
    }
}
<?php

define('ERR_SELECT_FAILED', 1);
define('ERR_TIMEOUT', 2);
define('ERR_READ_FAILED', 3);
define('ERR_WRITE_FAILED', 4);

$read = array(STDIN);
$write = array(STDOUT, STDERR);

stream_set_blocking(STDIN, false);
stream_set_blocking(STDOUT, false);
stream_set_blocking(STDERR, false);

$out = $err = '';
while ($read || $write) {
    $r = $read;
    $w = $write;
    $e = null;
    $n = stream_select($r, $w, $e, 5);

    if (false === $n) {
        die(ERR_SELECT_FAILED);
    } elseif ($n < 1) {
        die(ERR_TIMEOUT);
    }

    if (in_array(STDOUT, $w) && strlen($out) > 0) {
         $written = fwrite(STDOUT, (binary) $out, 1024);
         if (false === $written) {
             die(ERR_WRITE_FAILED);
         }
         $out = (binary) substr($out, $written);
    }
    if (null === $read && strlen($out) < 1) {
        $write = array_diff($write, array(STDOUT));
    }

    if (in_array(STDERR, $w) && strlen($err) > 0) {
         $written = fwrite(STDERR, (binary) $err, 1024);
         if (false === $written) {
             die(ERR_WRITE_FAILED);
         }
         $err = (binary) substr($err, $written);
    }
    if (null === $read && strlen($err) < 1) {
        $write = array_diff($write, array(STDERR));
    }

    if ($r) {
        $str = fread(STDIN, 1024);
        if (false !== $str) {
            $out .= $str;
            $err .= $str;
        }
        if (false === $str || feof(STDIN)) {
            $read = null;
            if (!feof(STDIN)) {
                die(ERR_READ_FAILED);
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Process\ProcessUtils;

class ProcessUtilsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider dataArguments
     */
    public function testEscapeArgument($result, $argument)
    {
        $this->assertSame($result, ProcessUtils::escapeArgument($argument));
    }

    public function dataArguments()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            return array(
                array('"foo bar"', 'foo bar'),
                array('^%"path"^%', '%path%'),
                array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'),
            );
        }

        return array(
            array("'foo bar'", 'foo bar'),
            array("'%path%'", '%path%'),
            array("'<|>\" \"'\\''f'", '<|>" "\'f'),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

class SigchildDisabledProcessTest extends AbstractProcessTest
{
    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetExitCode()
    {
        parent::testGetExitCode();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testExitCodeCommandFailed()
    {
        parent::testExitCodeCommandFailed();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessIsSignaledIfStopped()
    {
        parent::testProcessIsSignaledIfStopped();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithTermSignal()
    {
        parent::testProcessWithTermSignal();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessIsNotSignaled()
    {
        parent::testProcessIsNotSignaled();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithoutTermSignal()
    {
        parent::testProcessWithoutTermSignal();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPid()
    {
        parent::testGetPid();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPidIsNullBeforeStart()
    {
        parent::testGetPidIsNullBeforeStart();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPidIsNullAfterRun()
    {
        parent::testGetPidIsNullAfterRun();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testExitCodeText()
    {
        $process = $this->getProcess('qdfsmfkqsdfmqmsd');
        $process->run();

        $process->getExitCodeText();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testIsSuccessful()
    {
        parent::testIsSuccessful();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testIsNotSuccessful()
    {
        parent::testIsNotSuccessful();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignal()
    {
        parent::testSignal();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithoutTermSignalIsNotSignaled()
    {
        parent::testProcessWithoutTermSignalIsNotSignaled();
    }

    public function testStopWithTimeoutIsActuallyWorking()
    {
        $this->markTestSkipped('Stopping with signal is not supported in sigchild environment');
    }

    public function testProcessThrowsExceptionWhenExternallySignaled()
    {
        $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment');
    }

    /**
     * {@inheritdoc}
     */
    protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
    {
        $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options);
        $process->setEnhanceSigchildCompatibility(false);

        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\Tests;

class SigchildEnabledProcessTest extends AbstractProcessTest
{
    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessIsSignaledIfStopped()
    {
        parent::testProcessIsSignaledIfStopped();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithTermSignal()
    {
        parent::testProcessWithTermSignal();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessIsNotSignaled()
    {
        parent::testProcessIsNotSignaled();
    }

    /**
     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithoutTermSignal()
    {
        parent::testProcessWithoutTermSignal();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPid()
    {
        parent::testGetPid();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPidIsNullBeforeStart()
    {
        parent::testGetPidIsNullBeforeStart();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testGetPidIsNullAfterRun()
    {
        parent::testGetPidIsNullAfterRun();
    }

    public function testExitCodeText()
    {
        $process = $this->getProcess('qdfsmfkqsdfmqmsd');
        $process->run();

        $this->assertInternalType('string', $process->getExitCodeText());
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignal()
    {
        parent::testSignal();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testProcessWithoutTermSignalIsNotSignaled()
    {
        parent::testProcessWithoutTermSignalIsNotSignaled();
    }

    public function testProcessThrowsExceptionWhenExternallySignaled()
    {
        $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment');
    }

    /**
     * {@inheritdoc}
     */
    protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
    {
        $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options);
        $process->setEnhanceSigchildCompatibility(true);

        return $process;
    }
}
<?php

// required for signal handling
declare(ticks = 1);

pcntl_signal(SIGUSR1, function(){echo "Caught SIGUSR1"; exit;});

$n=0;

// ticks require activity to work - sleep(4); does not work
while($n < 400) {
    usleep(10000);
    $n++;
}

return;<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Tests;

use Symfony\Component\Process\Process;

class SimpleProcessTest extends AbstractProcessTest
{
    private $enabledSigchild = false;

    public function setUp()
    {
        ob_start();
        phpinfo(INFO_GENERAL);

        $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
    }

    public function testGetExitCode()
    {
        $this->skipIfPHPSigchild();
        parent::testGetExitCode();
    }

    public function testExitCodeCommandFailed()
    {
        $this->skipIfPHPSigchild();
        parent::testExitCodeCommandFailed();
    }

    public function testProcessIsSignaledIfStopped()
    {
        $this->skipIfPHPSigchild();
        parent::testProcessIsSignaledIfStopped();
    }

    public function testProcessWithTermSignal()
    {
        $this->skipIfPHPSigchild();
        parent::testProcessWithTermSignal();
    }

    public function testProcessIsNotSignaled()
    {
        $this->skipIfPHPSigchild();
        parent::testProcessIsNotSignaled();
    }

    public function testProcessWithoutTermSignal()
    {
        $this->skipIfPHPSigchild();
        parent::testProcessWithoutTermSignal();
    }

    public function testExitCodeText()
    {
        $this->skipIfPHPSigchild();
        parent::testExitCodeText();
    }

    public function testIsSuccessful()
    {
        $this->skipIfPHPSigchild();
        parent::testIsSuccessful();
    }

    public function testIsNotSuccessful()
    {
        $this->skipIfPHPSigchild();
        parent::testIsNotSuccessful();
    }

    public function testGetPid()
    {
        $this->skipIfPHPSigchild();
        parent::testGetPid();
    }

    public function testGetPidIsNullBeforeStart()
    {
        $this->skipIfPHPSigchild();
        parent::testGetPidIsNullBeforeStart();
    }

    public function testGetPidIsNullAfterRun()
    {
        $this->skipIfPHPSigchild();
        parent::testGetPidIsNullAfterRun();
    }

    public function testSignal()
    {
        $this->skipIfPHPSigchild();
        parent::testSignal();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\LogicException
     */
    public function testSignalProcessNotRunning()
    {
        $this->skipIfPHPSigchild();
        parent::testSignalProcessNotRunning();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignalWithWrongIntSignal()
    {
        $this->skipIfPHPSigchild();
        parent::testSignalWithWrongIntSignal();
    }

    /**
     * @expectedException Symfony\Component\Process\Exception\RuntimeException
     */
    public function testSignalWithWrongNonIntSignal()
    {
        $this->skipIfPHPSigchild();
        parent::testSignalWithWrongNonIntSignal();
    }

    /**
     * {@inheritdoc}
     */
    protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
    {
        return new Process($commandline, $cwd, $env, $stdin, $timeout, $options);
    }

    private function skipIfPHPSigchild()
    {
        if ($this->enabledSigchild) {
            $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed');
        }
    }
}
{
    "name": "symfony/process",
    "type": "library",
    "description": "Symfony Process Component",
    "keywords": [],
    "homepage": "http://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "http://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "Symfony\\Component\\Process\\": "" }
    },
    "target-dir": "Symfony/Component/Process",
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "2.3-dev"
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="Symfony Process Component Test Suite">
            <directory>./Tests/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>./</directory>
            <exclude>
                <directory>./Tests</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Adapter;

/**
 * Interface for finder engine implementations.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
abstract class AbstractAdapter implements AdapterInterface
{
    protected $followLinks = false;
    protected $mode        = 0;
    protected $minDepth    = 0;
    protected $maxDepth    = PHP_INT_MAX;
    protected $exclude     = array();
    protected $names       = array();
    protected $notNames    = array();
    protected $contains    = array();
    protected $notContains = array();
    protected $sizes       = array();
    protected $dates       = array();
    protected $filters     = array();
    protected $sort        = false;
    protected $paths       = array();
    protected $notPaths    = array();
    protected $ignoreUnreadableDirs = false;

    private static $areSupported = array();

    /**
     * {@inheritDoc}
     */
    public function isSupported()
    {
        $name = $this->getName();

        if (!array_key_exists($name, self::$areSupported)) {
            self::$areSupported[$name] = $this->canBeUsed();
        }

        return self::$areSupported[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function setFollowLinks($followLinks)
    {
        $this->followLinks = $followLinks;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setMode($mode)
    {
        $this->mode = $mode;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDepths(array $depths)
    {
        $this->minDepth = 0;
        $this->maxDepth = PHP_INT_MAX;

        foreach ($depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $this->minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $this->minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $this->maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $this->maxDepth = $comparator->getTarget();
                    break;
                default:
                    $this->minDepth = $this->maxDepth = $comparator->getTarget();
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setExclude(array $exclude)
    {
        $this->exclude = $exclude;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNames(array $names)
    {
        $this->names = $names;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotNames(array $notNames)
    {
        $this->notNames = $notNames;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setContains(array $contains)
    {
        $this->contains = $contains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotContains(array $notContains)
    {
        $this->notContains = $notContains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSizes(array $sizes)
    {
        $this->sizes = $sizes;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDates(array $dates)
    {
        $this->dates = $dates;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setFilters(array $filters)
    {
        $this->filters = $filters;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSort($sort)
    {
        $this->sort = $sort;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setPath(array $paths)
    {
        $this->paths = $paths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotPath(array $notPaths)
    {
        $this->notPaths = $notPaths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function ignoreUnreadableDirs($ignore = true)
    {
        $this->ignoreUnreadableDirs = (Boolean) $ignore;

        return $this;
    }

    /**
     * Returns whether the adapter is supported in the current environment.
     *
     * This method should be implemented in all adapters. Do not implement
     * isSupported in the adapters as the generic implementation provides a cache
     * layer.
     *
     * @see isSupported
     *
     * @return Boolean Whether the adapter is supported
     */
    abstract protected function canBeUsed();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Adapter;

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Expression\Expression;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Comparator\DateComparator;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
abstract class AbstractFindAdapter extends AbstractAdapter
{
    /**
     * @var Shell
     */
    protected $shell;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->shell = new Shell();
    }

    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        // having "/../" in path make find fail
        $dir = realpath($dir);

        // searching directories containing or not containing strings leads to no result
        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
            return new Iterator\FilePathsIterator(array(), $dir);
        }

        $command = Command::create();
        $find = $this->buildFindCommand($command, $dir);

        if ($this->followLinks) {
            $find->add('-follow');
        }

        $find->add('-mindepth')->add($this->minDepth + 1);

        if (PHP_INT_MAX !== $this->maxDepth) {
            $find->add('-maxdepth')->add($this->maxDepth + 1);
        }

        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
            $find->add('-type d');
        } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
            $find->add('-type f');
        }

        $this->buildNamesFiltering($find, $this->names);
        $this->buildNamesFiltering($find, $this->notNames, true);
        $this->buildPathsFiltering($find, $dir, $this->paths);
        $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
        $this->buildSizesFiltering($find, $this->sizes);
        $this->buildDatesFiltering($find, $this->dates);

        $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
        $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');

        if ($useGrep && ($this->contains || $this->notContains)) {
            $grep = $command->ins('grep');
            $this->buildContentFiltering($grep, $this->contains);
            $this->buildContentFiltering($grep, $this->notContains, true);
        }

        if ($useSort) {
            $this->buildSorting($command, $this->sort);
        }

        $command->setErrorHandler(
            $this->ignoreUnreadableDirs
                // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
                ? function ($stderr) { return; }
                : function ($stderr) { throw new AccessDeniedException($stderr); }
        );

        $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
        $iterator = new Iterator\FilePathsIterator($paths, $dir);

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        if (!$useGrep && ($this->contains || $this->notContains)) {
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->filters) {
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
        }

        if (!$useSort && $this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return $this->shell->testCommand('find');
    }

    /**
     * @param Command $command
     * @param string  $dir
     *
     * @return Command
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        return $command
            ->ins('find')
            ->add('find ')
            ->arg($dir)
            ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
    }

    /**
     * @param Command  $command
     * @param string[] $names
     * @param Boolean  $not
     */
    private function buildNamesFiltering(Command $command, array $names, $not = false)
    {
        if (0 === count($names)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($names as $i => $name) {
            $expr = Expression::create($name);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' and 'full path matching' regex problems.
            // - Jokers '.' are replaced by [^/].
            // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
                    ->setStartFlag(false)
                    ->setStartJoker(true)
                    ->replaceJokers('[^/]');
                if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
                    $regex->setEndJoker(false)->append('[^/]*');
                }
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-name' : '-iname')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command  $command
     * @param string   $dir
     * @param string[] $paths
     * @param Boolean  $not
     */
    private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
    {
        if (0 === count($paths)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($paths as $i => $path) {
            $expr = Expression::create($path);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' regex problems.
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? $dir.DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
            } else {
                $expr->prepend('*')->append('*');
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-path' : '-ipath')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command            $command
     * @param NumberComparator[] $sizes
     */
    private function buildSizesFiltering(Command $command, array $sizes)
    {
        foreach ($sizes as $i => $size) {
            $command->add($i > 0 ? '-and' : null);

            switch ($size->getOperator()) {
                case '<=':
                    $command->add('-size -'.($size->getTarget() + 1).'c');
                    break;
                case '>=':
                    $command->add('-size +'. ($size->getTarget() - 1).'c');
                    break;
                case '>':
                    $command->add('-size +'.$size->getTarget().'c');
                    break;
                case '!=':
                    $command->add('-size -'.$size->getTarget().'c');
                    $command->add('-size +'.$size->getTarget().'c');
                case '<':
                default:
                    $command->add('-size -'.$size->getTarget().'c');
            }
        }
    }

    /**
     * @param Command          $command
     * @param DateComparator[] $dates
     */
    private function buildDatesFiltering(Command $command, array $dates)
    {
        foreach ($dates as $i => $date) {
            $command->add($i > 0 ? '-and' : null);

            $mins = (int) round((time()-$date->getTarget()) / 60);

            if (0 > $mins) {
                // mtime is in the future
                $command->add(' -mmin -0');
                // we will have no result so we don't need to continue
                return;
            }

            switch ($date->getOperator()) {
                case '<=':
                    $command->add('-mmin +'.($mins - 1));
                    break;
                case '>=':
                    $command->add('-mmin -'.($mins + 1));
                    break;
                case '>':
                    $command->add('-mmin -'.$mins);
                    break;
                case '!=':
                    $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
                    break;
                case '<':
                default:
                    $command->add('-mmin +'.$mins);
            }
        }
    }

    /**
     * @param Command $command
     * @param string  $sort
     *
     * @throws \InvalidArgumentException
     */
    private function buildSorting(Command $command, $sort)
    {
        $this->buildFormatSorting($command, $sort);
    }

    /**
     * @param Command $command
     * @param string  $sort
     */
    abstract protected function buildFormatSorting(Command $command, $sort);

    /**
     * @param Command $command
     * @param array   $contains
     * @param Boolean $not
     */
    abstract protected function buildContentFiltering(Command $command, array $contains, $not = 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\Adapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface AdapterInterface
{
    /**
     * @param Boolean $followLinks
     *
     * @return AdapterInterface Current instance
     */
    public function setFollowLinks($followLinks);

    /**
     * @param integer $mode
     *
     * @return AdapterInterface Current instance
     */
    public function setMode($mode);

    /**
     * @param array $exclude
     *
     * @return AdapterInterface Current instance
     */
    public function setExclude(array $exclude);

    /**
     * @param array $depths
     *
     * @return AdapterInterface Current instance
     */
    public function setDepths(array $depths);

    /**
     * @param array $names
     *
     * @return AdapterInterface Current instance
     */
    public function setNames(array $names);

    /**
     * @param array $notNames
     *
     * @return AdapterInterface Current instance
     */
    public function setNotNames(array $notNames);

    /**
     * @param array $contains
     *
     * @return AdapterInterface Current instance
     */
    public function setContains(array $contains);

    /**
     * @param array $notContains
     *
     * @return AdapterInterface Current instance
     */
    public function setNotContains(array $notContains);

    /**
     * @param array $sizes
     *
     * @return AdapterInterface Current instance
     */
    public function setSizes(array $sizes);

    /**
     * @param array $dates
     *
     * @return AdapterInterface Current instance
     */
    public function setDates(array $dates);

    /**
     * @param array $filters
     *
     * @return AdapterInterface Current instance
     */
    public function setFilters(array $filters);

    /**
     * @param \Closure|integer $sort
     *
     * @return AdapterInterface Current instance
     */
    public function setSort($sort);

    /**
     * @param array $paths
     *
     * @return AdapterInterface Current instance
     */
    public function setPath(array $paths);

    /**
     * @param array $notPaths
     *
     * @return AdapterInterface Current instance
     */
    public function setNotPath(array $notPaths);

    /**
     * @param boolean $ignore
     *
     * @return AdapterInterface Current instance
     */
    public function ignoreUnreadableDirs($ignore = true);

    /**
     * @param string $dir
     *
     * @return \Iterator Result iterator
     */
    public function searchInDirectory($dir);

    /**
     * Tests adapter support for current platform.
     *
     * @return Boolean
     */
    public function isSupported();

    /**
     * Returns adapter name.
     *
     * @return string
     */
    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\Finder\Adapter;

use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Expression\Expression;

/**
 * Shell engine implementation using BSD find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BsdFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'bsd_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%HT';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%a';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%c';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%m';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->add('-print0 | xargs -0 stat -f')
            ->arg($format.'%t%N')
            ->add('| sort | cut -f 2');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1);

        return $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| grep -v \'^$\'')
                ->add('| xargs -I{} grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Adapter;

use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Expression\Expression;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class GnuFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'gnu_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%y';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%A@';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%C@';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%T@';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->get('find')
            ->add('-printf')
            ->arg($format.' %h/%f\\n')
            ->add('| sort | cut')
            ->arg('-d ')
            ->arg('-f2-')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return $this->shell->getType() === Shell::TYPE_UNIX && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
      return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| xargs -I{} -r grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Adapter;

use Symfony\Component\Finder\Iterator;

/**
 * PHP finder engine implementation.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class PhpAdapter extends AbstractAdapter
{
    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new \RecursiveIteratorIterator(
            new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs),
            \RecursiveIteratorIterator::SELF_FIRST
        );

        if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) {
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth);
        }

        if ($this->mode) {
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
        }

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        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->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        if ($this->paths || $this->notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'php';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return true;
    }
}
CHANGELOG
=========

2.3.0
-----

 * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs())
 * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception

2.2.0
-----

 * added Finder::path() and Finder::notPath() methods
 * added finder adapters to improve performance on specific platforms
 * added support for wildcard characters (glob patterns) in the paths passed
   to Finder::in()

2.1.0
-----

 * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and
   Finder::sortByModifiedTime()
 * added Countable to Finder
 * added support for an array of directories as an argument to
   Finder::exclude()
 * added searching based on the file content via Finder::contains() and
   Finder::notContains()
 * added support for the != operator in the Comparator
 * [BC BREAK] filter expressions (used for file name and content) are no more
   considered as regexps but glob patterns when they are enclosed in '*' or '?'
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 Boolean
     */
    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;

use Symfony\Component\Finder\Adapter\AdapterInterface;

/**
 * Base exception for all adapter failures.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class AdapterFailureException extends \RuntimeException implements ExceptionInterface
{
    /**
     * @var \Symfony\Component\Finder\Adapter\AdapterInterface
     */
    private $adapter;

    /**
     * @param AdapterInterface $adapter
     * @param string|null      $message
     * @param \Exception|null  $previous
     */
    public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null)
    {
        $this->adapter = $adapter;
        parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous);
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Exception;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OperationNotPermitedException extends AdapterFailureException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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;

use Symfony\Component\Finder\Adapter\AdapterInterface;
use Symfony\Component\Finder\Shell\Command;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class ShellCommandFailureException extends AdapterFailureException
{
    /**
     * @var Command
     */
    private $command;

    /**
     * @param AdapterInterface $adapter
     * @param Command          $command
     * @param \Exception|null  $previous
     */
    public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null)
    {
        $this->command = $command;
        parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous);
    }

    /**
     * @return Command
     */
    public function getCommand()
    {
        return $this->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\Finder\Expression;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Expression implements ValueInterface
{
    const TYPE_REGEX = 1;
    const TYPE_GLOB  = 2;

    /**
     * @var ValueInterface
     */
    private $value;

    /**
     * @param string $expr
     *
     * @return Expression
     */
    public static function create($expr)
    {
        return new self($expr);
    }

    /**
     * @param string $expr
     */
    public function __construct($expr)
    {
        try {
            $this->value = Regex::create($expr);
        } catch (\InvalidArgumentException $e) {
            $this->value = new Glob($expr);
        }
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->value->render();
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->value->renderPattern();
    }

    /**
     * @return bool
     */
    public function isCaseSensitive()
    {
        return $this->value->isCaseSensitive();
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->value->getType();
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->value->prepend($expr);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->value->append($expr);

        return $this;
    }

    /**
     * @return bool
     */
    public function isRegex()
    {
        return self::TYPE_REGEX === $this->value->getType();
    }

    /**
     * @return bool
     */
    public function isGlob()
    {
        return self::TYPE_GLOB === $this->value->getType();
    }

    /**
     * @throws \LogicException
     *
     * @return Glob
     */
    public function getGlob()
    {
        if (self::TYPE_GLOB !== $this->value->getType()) {
            throw new \LogicException('Regex cant be transformed to glob.');
        }

        return $this->value;
    }

    /**
     * @return Regex
     */
    public function getRegex()
    {
        return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Expression;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Glob implements ValueInterface
{
    /**
     * @var string
     */
    private $pattern;

    /**
     * @param string $pattern
     */
    public function __construct($pattern)
    {
        $this->pattern = $pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_GLOB;
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * Tests if glob is expandable ("*.{a,b}" syntax).
     *
     * @return bool
     */
    public function isExpandable()
    {
        return false !== strpos($this->pattern, '{')
            && false !== strpos($this->pattern, '}');
    }

    /**
     * @param bool $strictLeadingDot
     * @param bool $strictWildcardSlash
     *
     * @return Regex
     */
    public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true)
    {
        $firstByte = true;
        $escaping = false;
        $inCurlies = 0;
        $regex = '';
        $sizeGlob = strlen($this->pattern);
        for ($i = 0; $i < $sizeGlob; $i++) {
            $car = $this->pattern[$i];
            if ($firstByte) {
                if ($strictLeadingDot && '.' !== $car) {
                    $regex .= '(?=[^\.])';
                }

                $firstByte = false;
            }

            if ('/' === $car) {
                $firstByte = true;
            }

            if ('.' === $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 new Regex('^'.$regex.'$');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Expression;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Regex implements ValueInterface
{
    const START_FLAG = '^';
    const END_FLAG   = '$';
    const BOUNDARY   = '~';
    const JOKER      = '.*';
    const ESCAPING   = '\\';

    /**
     * @var string
     */
    private $pattern;

    /**
     * @var array
     */
    private $options;

    /**
     * @var bool
     */
    private $startFlag;

    /**
     * @var bool
     */
    private $endFlag;

    /**
     * @var bool
     */
    private $startJoker;

    /**
     * @var bool
     */
    private $endJoker;

    /**
     * @param string $expr
     *
     * @return Regex
     *
     * @throws \InvalidArgumentException
     */
    public static function create($expr)
    {
        if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) {
            $start = substr($m[1], 0, 1);
            $end   = substr($m[1], -1);

            if (($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) {
                return new self(substr($m[1], 1, -1), $m[2], $end);
            }
        }

        throw new \InvalidArgumentException('Given expression is not a regex.');
    }

    /**
     * @param string $pattern
     * @param string $options
     * @param string $delimiter
     */
    public function __construct($pattern, $options = '', $delimiter = null)
    {
        if (null !== $delimiter) {
            // removes delimiter escaping
            $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern);
        }

        $this->parsePattern($pattern);
        $this->options = $options;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return self::BOUNDARY
            .$this->renderPattern()
            .self::BOUNDARY
            .$this->options;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return ($this->startFlag ? self::START_FLAG : '')
            .($this->startJoker ? self::JOKER : '')
            .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern)
            .($this->endJoker ? self::JOKER : '')
            .($this->endFlag ? self::END_FLAG : '');
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return !$this->hasOption('i');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_REGEX;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * @param string $option
     *
     * @return bool
     */
    public function hasOption($option)
    {
        return false !== strpos($this->options, $option);
    }

    /**
     * @param string $option
     *
     * @return Regex
     */
    public function addOption($option)
    {
        if (!$this->hasOption($option)) {
            $this->options.= $option;
        }

        return $this;
    }

    /**
     * @param string $option
     *
     * @return Regex
     */
    public function removeOption($option)
    {
        $this->options = str_replace($option, '', $this->options);

        return $this;
    }

    /**
     * @param bool $startFlag
     *
     * @return Regex
     */
    public function setStartFlag($startFlag)
    {
        $this->startFlag = $startFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartFlag()
    {
        return $this->startFlag;
    }

    /**
     * @param bool $endFlag
     *
     * @return Regex
     */
    public function setEndFlag($endFlag)
    {
        $this->endFlag = (bool) $endFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndFlag()
    {
        return $this->endFlag;
    }

    /**
     * @param bool $startJoker
     *
     * @return Regex
     */
    public function setStartJoker($startJoker)
    {
        $this->startJoker = $startJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartJoker()
    {
        return $this->startJoker;
    }

    /**
     * @param bool $endJoker
     *
     * @return Regex
     */
    public function setEndJoker($endJoker)
    {
        $this->endJoker = (bool) $endJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndJoker()
    {
        return $this->endJoker;
    }

    /**
     * @param array $replacement
     *
     * @return Regex
     */
    public function replaceJokers($replacement)
    {
        $replace = function ($subject) use ($replacement) {
            $subject = $subject[0];
            $replace = 0 === substr_count($subject, '\\') % 2;

            return $replace ? str_replace('.', $replacement, $subject) : $subject;
        };

        $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern);

        return $this;
    }

    /**
     * @param string $pattern
     */
    private function parsePattern($pattern)
    {
        if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) {
            $pattern = substr($pattern, 1);
        }

        if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) {
            $pattern = substr($pattern, 2);
        }

        if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) {
            $pattern = substr($pattern, 0, -1);
        }

        if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) {
            $pattern = substr($pattern, 0, -2);
        }

        $this->pattern = $pattern;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Expression;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface ValueInterface
{
    /**
     * Renders string representation of expression.
     *
     * @return string
     */
    public function render();

    /**
     * Renders string representation of pattern.
     *
     * @return string
     */
    public function renderPattern();

    /**
     * Returns value case sensitivity.
     *
     * @return bool
     */
    public function isCaseSensitive();

    /**
     * Returns expression type.
     *
     * @return int
     */
    public function getType();

    /**
     * @param string $expr
     *
     * @return ValueInterface
     */
    public function prepend($expr);

    /**
     * @param string $expr
     *
     * @return ValueInterface
     */
    public function append($expr);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Adapter\AdapterInterface;
use Symfony\Component\Finder\Adapter\GnuFindAdapter;
use Symfony\Component\Finder\Adapter\BsdFindAdapter;
use Symfony\Component\Finder\Adapter\PhpAdapter;
use Symfony\Component\Finder\Exception\ExceptionInterface;

/**
 * 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>
 *
 * @api
 */
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 $adapters    = 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;

        $this
            ->addAdapter(new GnuFindAdapter())
            ->addAdapter(new BsdFindAdapter())
            ->addAdapter(new PhpAdapter(), -50)
            ->setAdapter('php')
        ;
    }

    /**
     * Creates a new Finder.
     *
     * @return Finder A new Finder instance
     *
     * @api
     */
    public static function create()
    {
        return new static();
    }

    /**
     * Registers a finder engine implementation.
     *
     * @param AdapterInterface $adapter  An adapter instance
     * @param integer          $priority Highest is selected first
     *
     * @return Finder The current Finder instance
     */
    public function addAdapter(Adapter\AdapterInterface $adapter, $priority = 0)
    {
        $this->adapters[$adapter->getName()] = array(
            'adapter'  => $adapter,
            'priority' => $priority,
            'selected' => false,
        );

        return $this->sortAdapters();
    }

    /**
     * Sets the selected adapter to the best one according to the current platform the code is run on.
     *
     * @return Finder The current Finder instance
     */
    public function useBestAdapter()
    {
        $this->resetAdapterSelection();

        return $this->sortAdapters();
    }

    /**
     * Selects the adapter to use.
     *
     * @param string $name
     *
     * @throws \InvalidArgumentException
     *
     * @return Finder The current Finder instance
     */
    public function setAdapter($name)
    {
        if (!isset($this->adapters[$name])) {
            throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name));
        }

        $this->resetAdapterSelection();
        $this->adapters[$name]['selected'] = true;

        return $this->sortAdapters();
    }

    /**
     * Removes all adapters registered in the finder.
     *
     * @return Finder The current Finder instance
     */
    public function removeAdapters()
    {
        $this->adapters = array();

        return $this;
    }

    /**
     * Returns registered adapters ordered by priority without extra information.
     *
     * @return AdapterInterface[]
     */
    public function getAdapters()
    {
        return array_values(array_map(function(array $adapter) {
            return $adapter['adapter'];
        }, $this->adapters));
    }

    /**
     * Restricts the matching to directories only.
     *
     * @return Finder The current Finder instance
     *
     * @api
     */
    public function directories()
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;

        return $this;
    }

    /**
     * Restricts the matching to files only.
     *
     * @return Finder The current Finder instance
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator
     * @see Symfony\Component\Finder\Comparator\NumberComparator
     *
     * @api
     */
    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 rage string
     *
     * @return Finder The current Finder instance
     *
     * @see strtotime
     * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator
     * @see Symfony\Component\Finder\Comparator\DateComparator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator
     * @see Symfony\Component\Finder\Comparator\NumberComparator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
     *
     * @api
     */
    public function exclude($dirs)
    {
        $this->exclude = array_merge($this->exclude, (array) $dirs);

        return $this;
    }

    /**
     * Excludes "hidden" directories and files (starting with a dot).
     *
     * @param Boolean $ignoreDotFiles Whether to exclude "hidden" files or not
     *
     * @return Finder The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
     *
     * @api
     */
    public function ignoreDotFiles($ignoreDotFiles)
    {
        if ($ignoreDotFiles) {
            $this->ignore = $this->ignore | static::IGNORE_DOT_FILES;
        } else {
            $this->ignore = $this->ignore & ~static::IGNORE_DOT_FILES;
        }

        return $this;
    }

    /**
     * Forces the finder to ignore version control directories.
     *
     * @param Boolean $ignoreVCS Whether to exclude VCS files or not
     *
     * @return Finder The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
     *
     * @api
     */
    public function ignoreVCS($ignoreVCS)
    {
        if ($ignoreVCS) {
            $this->ignore = $this->ignore | static::IGNORE_VCS_FILES;
        } else {
            $this->ignore = $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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\SortableIterator
     *
     * @api
     */
    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 The current Finder instance
     *
     * @see Symfony\Component\Finder\Iterator\CustomFilterIterator
     *
     * @api
     */
    public function filter(\Closure $closure)
    {
        $this->filters[] = $closure;

        return $this;
    }

    /**
     * Forces the following of symlinks.
     *
     * @return Finder The current Finder instance
     *
     * @api
     */
    public function followLinks()
    {
        $this->followLinks = true;

        return $this;
    }

    /**
     * Tells finder to ignore unreadable directories.
     *
     * By default, scanning unreadable directories content throws an AccessDeniedException.
     *
     * @param boolean $ignore
     *
     * @return Finder The current Finder instance
     */
    public function ignoreUnreadableDirs($ignore = true)
    {
        $this->ignoreUnreadableDirs = (Boolean) $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 The current Finder instance
     *
     * @throws \InvalidArgumentException if one of the directories does not exist
     *
     * @api
     */
    public function in($dirs)
    {
        $resolvedDirs = array();

        foreach ((array) $dirs as $dir) {
            if (is_dir($dir)) {
                $resolvedDirs[] = $dir;
            } elseif ($glob = glob($dir, 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 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 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());
    }

    /**
     * @return Finder The current Finder instance
     */
    private function sortAdapters()
    {
        uasort($this->adapters, function (array $a, array $b) {
            if ($a['selected'] || $b['selected']) {
                return $a['selected'] ? -1 : 1;
            }

            return $a['priority'] > $b['priority'] ? -1 : 1;
        });

        return $this;
    }

    /**
     * @param $dir
     *
     * @return \Iterator
     *
     * @throws \RuntimeException When none of the adapters are supported
     */
    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[] = '#(^|/)\..+(/|$)#';
        }

        foreach ($this->adapters as $adapter) {
            if ($adapter['adapter']->isSupported()) {
                try {
                    return $this
                        ->buildAdapter($adapter['adapter'])
                        ->searchInDirectory($dir);
                } catch (ExceptionInterface $e) {}
            }
        }

        throw new \RuntimeException('No supported adapter found.');
    }

    /**
     * @param AdapterInterface $adapter
     *
     * @return AdapterInterface
     */
    private function buildAdapter(AdapterInterface $adapter)
    {
        return $adapter
            ->setFollowLinks($this->followLinks)
            ->setDepths($this->depths)
            ->setMode($this->mode)
            ->setExclude($this->exclude)
            ->setNames($this->names)
            ->setNotNames($this->notNames)
            ->setContains($this->contains)
            ->setNotContains($this->notContains)
            ->setSizes($this->sizes)
            ->setDates($this->dates)
            ->setFilters($this->filters)
            ->setSort($this->sort)
            ->setPath($this->paths)
            ->setNotPath($this->notPaths)
            ->ignoreUnreadableDirs($this->ignoreUnreadableDirs);
    }

    /**
     * Unselects all adapters.
     */
    private function resetAdapterSelection()
    {
        $this->adapters = array_map(function (array $properties) {
            $properties['selected'] = false;

            return $properties;
        }, $this->adapters);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 Boolean $strictLeadingDot
     * @param Boolean $strictWildcardSlash
     *
     * @return string regex The regexp
     */
    public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true)
    {
        $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 ('.' === $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 '#^'.$regex.'$#';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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 array     $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 Boolean 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 Boolean true if the value should be kept, false otherwise
     */
    public function accept()
    {
        $fileinfo = $this->current();

        if (!$fileinfo->isFile()) {
            return true;
        }

        $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 Boolean 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
{
    private $patterns;

    /**
     * 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->patterns = array();
        foreach ($directories as $directory) {
            $this->patterns[] = '#(^|/)'.preg_quote($directory, '#').'(/|$)#';
        }

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return Boolean true if the value should be kept, false otherwise
     */
    public function accept()
    {
        $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
        $path = strtr($path, '\\', '/');
        foreach ($this->patterns as $pattern) {
            if (preg_match($pattern, $path)) {
                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\SplFileInfo;

/**
 * Iterate over shell command result.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class FilePathsIterator extends \ArrayIterator
{
    /**
     * @var string
     */
    private $baseDir;

    /**
     * @var int
     */
    private $baseDirLength;

    /**
     * @var string
     */
    private $subPath;

    /**
     * @var string
     */
    private $subPathname;

    /**
     * @var SplFileInfo
     */
    private $current;

    /**
     * @param array  $paths   List of paths returned by shell command
     * @param string $baseDir Base dir for relative path building
     */
    public function __construct(array $paths, $baseDir)
    {
        $this->baseDir       = $baseDir;
        $this->baseDirLength = strlen($baseDir);

        parent::__construct($paths);
    }

    /**
     * @param string $name
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($name, array $arguments)
    {
        return call_user_func_array(array($this->current(), $name), $arguments);
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     *
     * @return SplFileInfo File information
     */
    public function current()
    {
        return $this->current;
    }

    /**
     * @return string
     */
    public function key()
    {
        return $this->current->getPathname();
    }

    public function next()
    {
        parent::next();
        $this->buildProperties();
    }

    public function rewind()
    {
        parent::rewind();
        $this->buildProperties();
    }

    /**
     * @return string
     */
    public function getSubPath()
    {
        return $this->subPath;
    }

    /**
     * @return string
     */
    public function getSubPathname()
    {
        return $this->subPathname;
    }

    private function buildProperties()
    {
        $absolutePath = parent::current();

        if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) {
            $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\');
            $dir = dirname($this->subPathname);
            $this->subPath = '.' === $dir ? '' : $dir;
        } else {
            $this->subPath = $this->subPathname = '';
        }

        $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 integer   $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 Boolean 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;

/**
 * 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 Boolean 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;
        }

        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $content)) {
                return false;
            }
        }

        // should at least match one rule
        $match = true;
        if ($this->matchRegexps) {
            $match = false;
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $content)) {
                    return true;
                }
            }
        }

        return $match;
    }

    /**
     * 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\Expression\Expression;

/**
 * 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 Boolean true if the value should be kept, false otherwise
     */
    public function accept()
    {
        $filename = $this->current()->getFilename();

        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $filename)) {
                return false;
            }
        }

        // should at least match one rule
        $match = true;
        if ($this->matchRegexps) {
            $match = false;
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $filename)) {
                    return true;
                }
            }
        }

        return $match;
    }

    /**
     * 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 Expression::create($str)->getRegex()->render();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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.
 *
 * @see https://bugs.php.net/bug.php?id=49104
 *
 * @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()
    {
        $iterator = $this;
        while ($iterator instanceof \OuterIterator) {
            $innerIterator = $iterator->getInnerIterator();

            if ($innerIterator instanceof RecursiveDirectoryIterator) {
                if ($innerIterator->isRewindable()) {
                    $innerIterator->next();
                    $innerIterator->rewind();
                }
            } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) {
                $iterator->getInnerIterator()->next();
                $iterator->getInnerIterator()->rewind();
            }
            $iterator = $iterator->getInnerIterator();
        }

        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;

use Symfony\Component\Finder\Expression\Expression;

/**
 * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class MultiplePcreFilterIterator extends FilterIterator
{
    protected $matchRegexps;
    protected $noMatchRegexps;

    /**
     * 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)
    {
        $this->matchRegexps = array();
        foreach ($matchPatterns as $pattern) {
            $this->matchRegexps[] = $this->toRegex($pattern);
        }

        $this->noMatchRegexps = array();
        foreach ($noMatchPatterns as $pattern) {
            $this->noMatchRegexps[] = $this->toRegex($pattern);
        }

        parent::__construct($iterator);
    }

    /**
     * Checks whether the string is a regex.
     *
     * @param string $str
     *
     * @return Boolean Whether the given string is a regex
     */
    protected function isRegex($str)
    {
        return Expression::create($str)->isRegex();
    }

    /**
     * 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 Boolean true if the value should be kept, false otherwise
     */
    public function accept()
    {
        $filename = $this->current()->getRelativePathname();

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $filename = strtr($filename, '\\', '/');
        }

        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $filename)) {
                return false;
            }
        }

        // should at least match one rule
        $match = true;
        if ($this->matchRegexps) {
            $match = false;
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $filename)) {
                    return true;
                }
            }
        }

        return $match;
    }

    /**
     * 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 boolean
     */
    private $ignoreUnreadableDirs;

    /**
     * @var Boolean
     */
    private $rewindable;

    /**
     * Constructor.
     *
     * @param string  $path
     * @param int     $flags
     * @param boolean $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;
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths
     *
     * @return SplFileInfo File information
     */
    public function current()
    {
        return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname());
    }

    /**
     * @return \RecursiveIterator
     *
     * @throws AccessDeniedException
     */
    public function getChildren()
    {
        try {
            return parent::getChildren();
        } 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/bug.php?id=49104
        parent::next();

        parent::rewind();
    }

    /**
     * Checks if the stream is rewindable.
     *
     * @return Boolean true when the stream is rewindable, false otherwise
     */
    public function isRewindable()
    {
        if (null !== $this->rewindable) {
            return $this->rewindable;
        }

        if (false !== $stream = @opendir($this->getPath())) {
            $infos = stream_get_meta_data($stream);
            closedir($stream);

            if ($infos['seekable']) {
                return $this->rewindable = true;
            }
        }

        return $this->rewindable = false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

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 Boolean 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 integer|callback $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 callback 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);
    }
}
Copyright (c) 2004-2013 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Finder Component
================

Finder finds files and directories via an intuitive fluent interface.

    use Symfony\Component\Finder\Finder;

    $finder = new Finder();

    $iterator = $finder
      ->files()
      ->name('*.php')
      ->depth(0)
      ->size('>= 1K')
      ->in(__DIR__);

    foreach ($iterator as $file) {
        print $file->getRealpath()."\n";
    }

But you can also use it to find files stored remotely like in this example where
we are looking for files on Amazon S3:

    $s3 = new \Zend_Service_Amazon_S3($key, $secret);
    $s3->registerStreamWrapper("s3");

    $finder = new Finder();
    $finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
    foreach ($finder->in('s3://bucket-name') as $file) {
        print $file->getFilename()."\n";
    }

Resources
---------

You can run the unit tests with the following command:

    $ cd path/to/Symfony/Component/Finder/
    $ composer.phar install --dev
    $ phpunit

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Shell;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Command
{
    /**
     * @var Command|null
     */
    private $parent;

    /**
     * @var array
     */
    private $bits;

    /**
     * @var array
     */
    private $labels;

    /**
     * @var \Closure|null
     */
    private $errorHandler;

    /**
     * Constructor.
     *
     * @param Command $parent Parent command
     */
    public function __construct(Command $parent = null)
    {
        $this->parent = $parent;
        $this->bits   = array();
        $this->labels = array();
    }

    /**
     * Returns command as string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->join();
    }

    /**
     * Creates a new Command instance.
     *
     * @param Command $parent Parent command
     *
     * @return Command New Command instance
     */
    public static function create(Command $parent = null)
    {
        return new self($parent);
    }

    /**
     * Escapes special chars from input.
     *
     * @param string $input A string to escape
     *
     * @return string The escaped string
     */
    public static function escape($input)
    {
        return escapeshellcmd($input);
    }

    /**
     * Quotes input.
     *
     * @param string $input An argument string
     *
     * @return string The quoted string
     */
    public static function quote($input)
    {
        return escapeshellarg($input);
    }

    /**
     * Appends a string or a Command instance.
     *
     * @param string|Command $bit
     *
     * @return Command The current Command instance
     */
    public function add($bit)
    {
        $this->bits[] = $bit;

        return $this;
    }

    /**
     * Prepends a string or a command instance.
     *
     * @param string|Command $bit
     *
     * @return Command The current Command instance
     */
    public function top($bit)
    {
        array_unshift($this->bits, $bit);

        foreach ($this->labels as $label => $index) {
            $this->labels[$label] += 1;
        }

        return $this;
    }

    /**
     * Appends an argument, will be quoted.
     *
     * @param string $arg
     *
     * @return Command The current Command instance
     */
    public function arg($arg)
    {
        $this->bits[] = self::quote($arg);

        return $this;
    }

    /**
     * Appends escaped special command chars.
     *
     * @param string $esc
     *
     * @return Command The current Command instance
     */
    public function cmd($esc)
    {
        $this->bits[] = self::escape($esc);

        return $this;
    }

    /**
     * Inserts a labeled command to feed later.
     *
     * @param string $label The unique label
     *
     * @return Command The current Command instance
     *
     * @throws \RuntimeException If label already exists
     */
    public function ins($label)
    {
        if (isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" already exists.', $label));
        }

        $this->bits[] = self::create($this);
        $this->labels[$label] = count($this->bits)-1;

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Retrieves a previously labeled command.
     *
     * @param string $label
     *
     * @return Command The labeled command
     *
     * @throws \RuntimeException
     */
    public function get($label)
    {
        if (!isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" does not exists.', $label));
        }

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Returns parent command (if any).
     *
     * @return Command Parent command
     *
     * @throws \RuntimeException If command has no parent
     */
    public function end()
    {
        if (null === $this->parent) {
            throw new \RuntimeException('Calling end on root command doesn\'t make sense.');
        }

        return $this->parent;
    }

    /**
     * Counts bits stored in command.
     *
     * @return int The bits count
     */
    public function length()
    {
        return count($this->bits);
    }

    /**
     * @param \Closure $errorHandler
     *
     * @return Command
     */
    public function setErrorHandler(\Closure $errorHandler)
    {
        $this->errorHandler = $errorHandler;

        return $this;
    }

    /**
     * @return callable|null
     */
    public function getErrorHandler()
    {
        return $this->errorHandler;
    }

    /**
     * Executes current command.
     *
     * @return array The command result
     *
     * @throws \RuntimeException
     */
    public function execute()
    {
        if (null === $this->errorHandler) {
            exec($this->join(), $output);
        } else {
            $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes);
            $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY);

            if ($error = stream_get_contents($pipes[2])) {
                call_user_func($this->errorHandler, $error);
            }

            proc_close($process);
        }

        return $output ?: array();
    }

    /**
     * Joins bits.
     *
     * @return string
     */
    public function join()
    {
        return implode(' ', array_filter(
            array_map(function($bit) {
                return $bit instanceof Command ? $bit->join() : ($bit ?: null);
            }, $this->bits),
            function($bit) { return null !== $bit; }
        ));
    }

    /**
     * Insert a string or a Command instance before the bit at given position $index (index starts from 0).
     *
     * @param string|Command $bit
     * @param integer        $index
     *
     * @return Command The current Command instance
     */
    public function addAtIndex($bit, $index)
    {
        array_splice($this->bits, $index, 0, $bit);

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Shell;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Shell
{
    const TYPE_UNIX    = 1;
    const TYPE_DARWIN  = 2;
    const TYPE_CYGWIN  = 3;
    const TYPE_WINDOWS = 4;
    const TYPE_BSD     = 5;

    /**
     * @var string|null
     */
    private $type;

    /**
     * Returns guessed OS type.
     *
     * @return int
     */
    public function getType()
    {
        if (null === $this->type) {
            $this->type = $this->guessType();
        }

        return $this->type;
    }

    /**
     * Tests if a command is available.
     *
     * @param string $command
     *
     * @return bool
     */
    public function testCommand($command)
    {
        if (self::TYPE_WINDOWS === $this->type) {
            // todo: find a way to test if windows command exists
            return false;
        }

        if (!function_exists('exec')) {
            return false;
        }

        // todo: find a better way (command could not be available)
        exec('command -v '.$command, $output, $code);

        return 0 === $code && count($output) > 0;
    }

    /**
     * Guesses OS type.
     *
     * @return int
     */
    private function guessType()
    {
        $os = strtolower(PHP_OS);

        if (false !== strpos($os, 'cygwin')) {
            return self::TYPE_CYGWIN;
        }

        if (false !== strpos($os, 'darwin')) {
            return self::TYPE_DARWIN;
        }

        if (false !== strpos($os, 'bsd')) {
            return self::TYPE_BSD;
        }

        if (0 === strpos($os, 'win')) {
            return self::TYPE_WINDOWS;
        }

        return self::TYPE_UNIX;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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
     *
     * @return string the relative path
     */
    public function getRelativePath()
    {
        return $this->relativePath;
    }

    /**
     * Returns the relative path 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->getRealpath());
        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.
 */

namespace Symfony\Component\Finder\Tests\Comparator;

use Symfony\Component\Finder\Comparator\Comparator;

class ComparatorTest extends \PHPUnit_Framework_TestCase
{
    public function testGetSetOperator()
    {
        $comparator = new Comparator();
        try {
            $comparator->setOperator('foo');
            $this->fail('->setOperator() throws an \InvalidArgumentException if the operator is not valid.');
        } catch (\Exception $e) {
            $this->assertInstanceOf('InvalidArgumentException', $e, '->setOperator() throws an \InvalidArgumentException if the operator is not valid.');
        }

        $comparator = new Comparator();
        $comparator->setOperator('>');
        $this->assertEquals('>', $comparator->getOperator(), '->getOperator() returns the current operator');
    }

    public function testGetSetTarget()
    {
        $comparator = new Comparator();
        $comparator->setTarget(8);
        $this->assertEquals(8, $comparator->getTarget(), '->getTarget() returns the target');
    }

    /**
     * @dataProvider getTestData
     */
    public function testTest($operator, $target, $match, $noMatch)
    {
        $c = new Comparator();
        $c->setOperator($operator);
        $c->setTarget($target);

        foreach ($match as $m) {
            $this->assertTrue($c->test($m), '->test() tests a string against the expression');
        }

        foreach ($noMatch as $m) {
            $this->assertFalse($c->test($m), '->test() tests a string against the expression');
        }
    }

    public function getTestData()
    {
        return array(
            array('<', '1000', array('500', '999'), array('1000', '1500')),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Comparator;

use Symfony\Component\Finder\Comparator\DateComparator;

class DateComparatorTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        try {
            new DateComparator('foobar');
            $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
        } catch (\Exception $e) {
            $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
        }

        try {
            new DateComparator('');
            $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
        } catch (\Exception $e) {
            $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
        }
    }

    /**
     * @dataProvider getTestData
     */
    public function testTest($test, $match, $noMatch)
    {
        $c = new DateComparator($test);

        foreach ($match as $m) {
            $this->assertTrue($c->test($m), '->test() tests a string against the expression');
        }

        foreach ($noMatch as $m) {
            $this->assertFalse($c->test($m), '->test() tests a string against the expression');
        }
    }

    public function getTestData()
    {
        return array(
            array('< 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
            array('until 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
            array('before 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
            array('> 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
            array('after 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
            array('since 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
            array('!= 2005-10-10', array(strtotime('2005-10-11')), array(strtotime('2005-10-10'))),
        );

    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Comparator;

use Symfony\Component\Finder\Comparator\NumberComparator;

class NumberComparatorTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @dataProvider getConstructorTestData
     */
    public function testConstructor($successes, $failures)
    {
        foreach ($successes as $s) {
            new NumberComparator($s);
        }

        foreach ($failures as $f) {
            try {
                new NumberComparator($f);
                $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
            } catch (\Exception $e) {
                $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
            }
        }
    }

    /**
     * @dataProvider getTestData
     */
    public function testTest($test, $match, $noMatch)
    {
        $c = new NumberComparator($test);

        foreach ($match as $m) {
            $this->assertTrue($c->test($m), '->test() tests a string against the expression');
        }

        foreach ($noMatch as $m) {
            $this->assertFalse($c->test($m), '->test() tests a string against the expression');
        }
    }

    public function getTestData()
    {
        return array(
            array('< 1000', array('500', '999'), array('1000', '1500')),

            array('< 1K', array('500', '999'), array('1000', '1500')),
            array('<1k', array('500', '999'), array('1000', '1500')),
            array('  < 1 K ', array('500', '999'), array('1000', '1500')),
            array('<= 1K', array('1000'), array('1001')),
            array('> 1K', array('1001'), array('1000')),
            array('>= 1K', array('1000'), array('999')),

            array('< 1KI', array('500', '1023'), array('1024', '1500')),
            array('<= 1KI', array('1024'), array('1025')),
            array('> 1KI', array('1025'), array('1024')),
            array('>= 1KI', array('1024'), array('1023')),

            array('1KI', array('1024'), array('1023', '1025')),
            array('==1KI', array('1024'), array('1023', '1025')),

            array('==1m', array('1000000'), array('999999', '1000001')),
            array('==1mi', array(1024*1024), array(1024*1024-1, 1024*1024+1)),

            array('==1g', array('1000000000'), array('999999999', '1000000001')),
            array('==1gi', array(1024*1024*1024), array(1024*1024*1024-1, 1024*1024*1024+1)),

            array('!= 1000', array('500', '999'), array('1000')),
        );
    }

    public function getConstructorTestData()
    {
        return array(
            array(
                array(
                    '1', '0',
                    '3.5', '33.55', '123.456', '123456.78',
                    '.1', '.123',
                    '.0', '0.0',
                    '1.', '0.', '123.',
                    '==1', '!=1', '<1', '>1', '<=1', '>=1',
                    '==1k', '==1ki', '==1m', '==1mi', '==1g', '==1gi',
                    '1k', '1ki', '1m', '1mi', '1g', '1gi',
                ),
                array(
                    false, null, '',
                    ' ', 'foobar',
                    '=1', '===1',
                    '0 . 1', '123 .45', '234. 567',
                    '..', '.0.', '0.1.2',
                )
            ),
        );
    }

}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests;

use Symfony\Component\Finder\Expression\Expression;

class ExpressionTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getTypeGuesserData
     */
    public function testTypeGuesser($expr, $type)
    {
        $this->assertEquals($type, Expression::create($expr)->getType());
    }

    /**
     * @dataProvider getCaseSensitiveData
     */
    public function testCaseSensitive($expr, $isCaseSensitive)
    {
        $this->assertEquals($isCaseSensitive, Expression::create($expr)->isCaseSensitive());
    }

    /**
     * @dataProvider getRegexRenderingData
     */
    public function testRegexRendering($expr, $body)
    {
        $this->assertEquals($body, Expression::create($expr)->renderPattern());
    }

    public function getTypeGuesserData()
    {
        return array(
            array('{foo}', Expression::TYPE_REGEX),
            array('/foo/', Expression::TYPE_REGEX),
            array('foo',   Expression::TYPE_GLOB),
            array('foo*',  Expression::TYPE_GLOB),
        );
    }

    public function getCaseSensitiveData()
    {
        return array(
            array('{foo}m', true),
            array('/foo/i', false),
            array('foo*',   true),
        );
    }

    public function getRegexRenderingData()
    {
        return array(
            array('{foo}m', 'foo'),
            array('/foo/i', 'foo'),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Tests;

use Symfony\Component\Finder\Expression\Expression;

class GlobTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getToRegexData
     */
    public function testGlobToRegex($glob, $match, $noMatch)
    {
        foreach ($match as $m) {
            $this->assertRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp');
        }

        foreach ($noMatch as $m) {
            $this->assertNotRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp');
        }
    }

    public function getToRegexData()
    {
        return array(
            array('', array(''), array('f', '/')),
            array('*', array('foo'), array('foo/', '/foo')),
            array('foo.*', array('foo.php', 'foo.a', 'foo.'), array('fooo.php', 'foo.php/foo')),
            array('fo?', array('foo', 'fot'), array('fooo', 'ffoo', 'fo/')),
            array('fo{o,t}', array('foo', 'fot'), array('fob', 'fo/')),
            array('foo(bar|foo)', array('foo(bar|foo)'), array('foobar', 'foofoo')),
            array('foo,bar', array('foo,bar'), array('foo', 'bar')),
            array('fo{o,\\,}', array('foo', 'fo,'), array()),
            array('fo{o,\\\\}', array('foo', 'fo\\'), array()),
            array('/foo', array('/foo'), array('foo')),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Tests;

use Symfony\Component\Finder\Expression\Expression;

class RegexTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getHasFlagsData
     */
    public function testHasFlags($regex, $start, $end)
    {
        $expr = new Expression($regex);

        $this->assertEquals($start, $expr->getRegex()->hasStartFlag());
        $this->assertEquals($end,   $expr->getRegex()->hasEndFlag());
    }

    /**
     * @dataProvider getHasJokersData
     */
    public function testHasJokers($regex, $start, $end)
    {
        $expr = new Expression($regex);

        $this->assertEquals($start, $expr->getRegex()->hasStartJoker());
        $this->assertEquals($end,   $expr->getRegex()->hasEndJoker());
    }

    /**
     * @dataProvider getSetFlagsData
     */
    public function testSetFlags($regex, $start, $end, $expected)
    {
        $expr = new Expression($regex);
        $expr->getRegex()->setStartFlag($start)->setEndFlag($end);

        $this->assertEquals($expected, $expr->render());
    }

    /**
     * @dataProvider getSetJokersData
     */
    public function testSetJokers($regex, $start, $end, $expected)
    {
        $expr = new Expression($regex);
        $expr->getRegex()->setStartJoker($start)->setEndJoker($end);

        $this->assertEquals($expected, $expr->render());
    }

    public function testOptions()
    {
        $expr = new Expression('~abc~is');
        $expr->getRegex()->removeOption('i')->addOption('m');

        $this->assertEquals('~abc~sm', $expr->render());
    }

    public function testMixFlagsAndJokers()
    {
        $expr = new Expression('~^.*abc.*$~is');

        $expr->getRegex()->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false);
        $this->assertEquals('~abc~is', $expr->render());

        $expr->getRegex()->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true);
        $this->assertEquals('~^.*abc.*$~is', $expr->render());
    }

    /**
     * @dataProvider getReplaceJokersTestData
     */
    public function testReplaceJokers($regex, $expected)
    {
        $expr = new Expression($regex);
        $expr = $expr->getRegex()->replaceJokers('@');

        $this->assertEquals($expected, $expr->renderPattern());
    }

    public function getHasFlagsData()
    {
        return array(
            array('~^abc~', true, false),
            array('~abc$~', false, true),
            array('~abc~', false, false),
            array('~^abc$~', true, true),
            array('~^abc\\$~', true, false),
        );
    }

    public function getHasJokersData()
    {
        return array(
            array('~.*abc~', true, false),
            array('~abc.*~', false, true),
            array('~abc~', false, false),
            array('~.*abc.*~', true, true),
            array('~.*abc\\.*~', true, false),
        );
    }

    public function getSetFlagsData()
    {
        return array(
            array('~abc~', true, false, '~^abc~'),
            array('~abc~', false, true, '~abc$~'),
            array('~abc~', false, false, '~abc~'),
            array('~abc~', true, true, '~^abc$~'),
        );
    }

    public function getSetJokersData()
    {
        return array(
            array('~abc~', true, false, '~.*abc~'),
            array('~abc~', false, true, '~abc.*~'),
            array('~abc~', false, false, '~abc~'),
            array('~abc~', true, true, '~.*abc.*~'),
        );
    }

    public function getReplaceJokersTestData()
    {
        return array(
            array('~.abc~', '@abc'),
            array('~\\.abc~', '\\.abc'),
            array('~\\\\.abc~', '\\\\@abc'),
            array('~\\\\\\.abc~', '\\\\\\.abc'),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\FakeAdapter;

use Symfony\Component\Finder\Adapter\AbstractAdapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DummyAdapter extends AbstractAdapter
{
    /**
     * @var \Iterator
     */
    private $iterator;

    /**
     * @param \Iterator $iterator
     */
    public function __construct(\Iterator $iterator)
    {
        $this->iterator = $iterator;
    }

    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        return $this->iterator;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'yes';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        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\Tests\FakeAdapter;

use Symfony\Component\Finder\Adapter\AbstractAdapter;
use Symfony\Component\Finder\Exception\AdapterFailureException;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class FailingAdapter extends AbstractAdapter
{
    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        throw new AdapterFailureException($this);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'failing';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        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\Tests\FakeAdapter;

use Symfony\Component\Finder\Adapter\AbstractAdapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class NamedAdapter extends AbstractAdapter
{
    /**
     * @var string
     */
    private $name;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        return new \ArrayIterator(array());
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        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\Tests\FakeAdapter;

use Symfony\Component\Finder\Adapter\AbstractAdapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class UnsupportedAdapter extends AbstractAdapter
{
    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        return new \ArrayIterator(array());
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'unsupported';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        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\Finder\Tests;

use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Adapter;
use Symfony\Component\Finder\Tests\FakeAdapter;

class FinderTest extends Iterator\RealIteratorTestCase
{

    public function testCreate()
    {
        $this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testDirectories($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->directories());
        $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->directories();
        $finder->files();
        $finder->directories();
        $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testFiles($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->files());
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->files();
        $finder->directories();
        $finder->files();
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testDepth($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->depth('< 1'));
        $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->depth('<= 0'));
        $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->depth('>= 1'));
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->depth('< 1')->depth('>= 1');
        $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testName($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->name('*.php'));
        $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('test.ph*');
        $finder->name('test.py');
        $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('~^test~i');
        $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('~\\.php$~i');
        $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('test.p{hp,y}');
        $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testNotName($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->notName('*.php'));
        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->notName('*.php');
        $finder->notName('*.py');
        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('test.ph*');
        $finder->name('test.py');
        $finder->notName('*.php');
        $finder->notName('*.py');
        $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->name('test.ph*');
        $finder->name('test.py');
        $finder->notName('*.p{hp,y}');
        $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getRegexNameTestData
     *
     * @group regexName
     */
    public function testRegexName($adapter, $regex)
    {
        $finder = $this->buildFinder($adapter);
        $finder->name($regex);
        $this->assertIterator($this->toAbsolute(array('test.py', 'test.php')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSize($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500'));
        $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testDate($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->files()->date('until last month'));
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testExclude($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->exclude('foo'));
        $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testIgnoreVCS($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
        $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
        $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testIgnoreDotFiles($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
        $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
        $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());

        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false));
        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSortByName($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sortByName());
        $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSortByType($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sortByType());
        $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSortByAccessedTime($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sortByAccessedTime());
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSortByChangedTime($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sortByChangedTime());
        $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSortByModifiedTime($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sortByModifiedTime());
        $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testSort($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }));
        $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testFilter($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; }));
        $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testFollowLinks($adapter)
    {
        if ('\\' == DIRECTORY_SEPARATOR) {
            return;
        }

        $finder = $this->buildFinder($adapter);
        $this->assertSame($finder, $finder->followLinks());
        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testIn($adapter)
    {
        $finder = $this->buildFinder($adapter);
        try {
            $finder->in('foobar');
            $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist');
        }

        $finder = $this->buildFinder($adapter);
        $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator();

        $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php'), $iterator);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testInWithGlob($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(array(__DIR__.'/Fixtures/*/B/C', __DIR__.'/Fixtures/*/*/B/C'))->getIterator();

        $this->assertIterator($this->toAbsoluteFixtures(array('A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy')), $finder);
    }

    /**
     * @dataProvider getAdaptersTestData
     * @expectedException \InvalidArgumentException
     */
    public function testInWithNonDirectoryGlob($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(__DIR__.'/Fixtures/A/a*');
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testGetIterator($adapter)
    {
        $finder = $this->buildFinder($adapter);
        try {
            $finder->getIterator();
            $this->fail('->getIterator() throws a \LogicException if the in() method has not been called');
        } catch (\Exception $e) {
            $this->assertInstanceOf('LogicException', $e, '->getIterator() throws a \LogicException if the in() method has not been called');
        }

        $finder = $this->buildFinder($adapter);
        $dirs = array();
        foreach ($finder->directories()->in(self::$tmpDir) as $dir) {
            $dirs[] = (string) $dir;
        }

        $expected = $this->toAbsolute(array('foo', 'toto'));

        sort($dirs);
        sort($expected);

        $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface');

        $finder = $this->buildFinder($adapter);
        $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface');

        $finder = $this->buildFinder($adapter);
        $a = iterator_to_array($finder->directories()->in(self::$tmpDir));
        $a = array_values(array_map(function ($a) { return (string) $a; }, $a));
        sort($a);
        $this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface');
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testRelativePath($adapter)
    {
        $finder = $this->buildFinder($adapter)->in(self::$tmpDir);

        $paths = array();

        foreach ($finder as $file) {
            $paths[] = $file->getRelativePath();
        }

        $ref = array("", "", "", "", "foo", "");

        sort($ref);
        sort($paths);

        $this->assertEquals($ref, $paths);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testRelativePathname($adapter)
    {
        $finder = $this->buildFinder($adapter)->in(self::$tmpDir)->sortByName();

        $paths = array();

        foreach ($finder as $file) {
            $paths[] = $file->getRelativePathname();
        }

        $ref = array("test.php", "toto", "test.py", "foo", "foo".DIRECTORY_SEPARATOR."bar.tmp", "foo bar");

        sort($paths);
        sort($ref);

        $this->assertEquals($ref, $paths);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testAppendWithAFinder($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');

        $finder1 = $this->buildFinder($adapter);
        $finder1->directories()->in(self::$tmpDir);

        $finder = $finder->append($finder1);

        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testAppendWithAnArray($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');

        $finder->append($this->toAbsolute(array('foo', 'toto')));

        $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testAppendReturnsAFinder($adapter)
    {
        $this->assertInstanceOf('Symfony\\Component\\Finder\\Finder', $this->buildFinder($adapter)->append(array()));
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testAppendDoesNotRequireIn($adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');

        $finder1 = Finder::create()->append($finder);

        $this->assertIterator(iterator_to_array($finder->getIterator()), $finder1->getIterator());
    }

    public function testCountDirectories()
    {
        $directory = Finder::create()->directories()->in(self::$tmpDir);
        $i = 0;

        foreach ($directory as $dir) {
            $i++;
        }

        $this->assertCount($i, $directory);
    }

    public function testCountFiles()
    {
        $files = Finder::create()->files()->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures');
        $i = 0;

        foreach ($files as $file) {
            $i++;
        }

        $this->assertCount($i, $files);
    }

    /**
     * @expectedException \LogicException
     */
    public function testCountWithoutIn()
    {
        $finder = Finder::create()->files();
        count($finder);
    }

    /**
     * @dataProvider getContainsTestData
     * @group grep
     */
    public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
            ->name('*.txt')->sortByName()
            ->contains($matchPatterns)
            ->notContains($noMatchPatterns);

        $this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testContainsOnDirectory(Adapter\AdapterInterface $adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(__DIR__)
            ->directories()
            ->name('Fixtures')
            ->contains('abc');
        $this->assertIterator(array(), $finder);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(__DIR__)
            ->directories()
            ->name('Fixtures')
            ->notContains('abc');
        $this->assertIterator(array(), $finder);
    }

    /**
     * Searching in multiple locations involves AppendIterator which does an unnecessary rewind which leaves FilterIterator
     * with inner FilesystemIterator in an invalid state.
     *
     * @see https://bugs.php.net/bug.php?id=49104
     *
     * @dataProvider getAdaptersTestData
     */
    public function testMultipleLocations(Adapter\AdapterInterface $adapter)
    {
        $locations = array(
            self::$tmpDir.'/',
            self::$tmpDir.'/toto/',
        );

        // it is expected that there are test.py test.php in the tmpDir
        $finder = $this->buildFinder($adapter);
        $finder->in($locations)->depth('< 1')->name('test.php');

        $this->assertEquals(1, count($finder));
    }

    /**
     * Iterator keys must be the file pathname.
     *
     * @dataProvider getAdaptersTestData
     */
    public function testIteratorKeys(Adapter\AdapterInterface $adapter)
    {
        $finder = $this->buildFinder($adapter)->in(self::$tmpDir);
        foreach ($finder as $key => $file) {
            $this->assertEquals($file->getPathname(), $key);
        }
    }

    public function testAdaptersOrdering()
    {
        $finder = Finder::create()
            ->removeAdapters()
            ->addAdapter(new FakeAdapter\NamedAdapter('a'), 0)
            ->addAdapter(new FakeAdapter\NamedAdapter('b'), -50)
            ->addAdapter(new FakeAdapter\NamedAdapter('c'), 50)
            ->addAdapter(new FakeAdapter\NamedAdapter('d'), -25)
            ->addAdapter(new FakeAdapter\NamedAdapter('e'), 25);

        $this->assertEquals(
            array('c', 'e', 'a', 'd', 'b'),
            array_map(function(Adapter\AdapterInterface $adapter) {
                return $adapter->getName();
            }, $finder->getAdapters())
        );
    }

    public function testAdaptersChaining()
    {
        $iterator  = new \ArrayIterator(array());
        $filenames = $this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto'));
        foreach ($filenames as $file) {
            $iterator->append(new \Symfony\Component\Finder\SplFileInfo($file, null, null));
        }

        $finder = Finder::create()
            ->removeAdapters()
            ->addAdapter(new FakeAdapter\UnsupportedAdapter(), 3)
            ->addAdapter(new FakeAdapter\FailingAdapter(), 2)
            ->addAdapter(new FakeAdapter\DummyAdapter($iterator), 1);

        $this->assertIterator($filenames, $finder->in(sys_get_temp_dir())->getIterator());
    }

    public function getAdaptersTestData()
    {
        return array_map(
            function ($adapter)  { return array($adapter); },
            $this->getValidAdapters()
        );
    }

    public function getContainsTestData()
    {
        $tests = array(
            array('', '', array()),
            array('foo', 'bar', array()),
            array('', 'foobar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
            array('lorem ipsum dolor sit amet', 'foobar', array('lorem.txt')),
            array('sit', 'bar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
            array('dolor sit amet', '@^L@m', array('dolor.txt', 'ipsum.txt')),
            array('/^lorem ipsum dolor sit amet$/m', 'foobar', array('lorem.txt')),
            array('lorem', 'foobar', array('lorem.txt')),
            array('', 'lorem', array('dolor.txt', 'ipsum.txt')),
            array('ipsum dolor sit amet', '/^IPSUM/m', array('lorem.txt')),
        );

        return $this->buildTestData($tests);
    }

    public function getRegexNameTestData()
    {
        $tests = array(
            array('~.+\\.p.+~i'),
            array('~t.*s~i'),
        );

        return $this->buildTestData($tests);
    }

    /**
     * @dataProvider getTestPathData
     */
    public function testPath(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, array $expected)
    {
        $finder = $this->buildFinder($adapter);
        $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
            ->path($matchPatterns)
            ->notPath($noMatchPatterns);

        $this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
    }

    public function testAdapterSelection()
    {
        // test that by default, PhpAdapter is selected
        $adapters = Finder::create()->getAdapters();
        $this->assertTrue($adapters[0] instanceof Adapter\PhpAdapter);

        // test another adapter selection
        $adapters = Finder::create()->setAdapter('gnu_find')->getAdapters();
        $this->assertTrue($adapters[0] instanceof Adapter\GnuFindAdapter);

        // test that useBestAdapter method removes selection
        $adapters = Finder::create()->useBestAdapter()->getAdapters();
        $this->assertFalse($adapters[0] instanceof Adapter\PhpAdapter);
    }

    public function getTestPathData()
    {
        $tests = array(
            array('', '', array()),
            array('/^A\/B\/C/', '/C$/',
                array('A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat')
            ),
            array('/^A\/B/', 'foobar',
                array(
                    'A'.DIRECTORY_SEPARATOR.'B',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
                )
            ),
            array('A/B/C', 'foobar',
                array(
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy',
                )
            ),
            array('A/B', 'foobar',
                array(
                    //dirs
                    'A'.DIRECTORY_SEPARATOR.'B',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
                    //files
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
                    'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat.copy',
                    'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy',
                )
            ),
            array('/^with space\//', 'foobar',
                array(
                    'with space'.DIRECTORY_SEPARATOR.'foo.txt',
                )
            ),
        );

        return $this->buildTestData($tests);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testAccessDeniedException(Adapter\AdapterInterface $adapter)
    {
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $this->markTestSkipped('chmod is not supported on windows');
        }

        $finder = $this->buildFinder($adapter);
        $finder->files()->in(self::$tmpDir);

        // make 'foo' directory non-readable
        chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333);

        try {
            $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
            $this->fail('Finder should throw an exception when opening a non-readable directory.');
        } catch (\Exception $e) {
            $this->assertEquals('Symfony\\Component\\Finder\\Exception\\AccessDeniedException', get_class($e));
        }

        // restore original permissions
        chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777);
    }

    /**
     * @dataProvider getAdaptersTestData
     */
    public function testIgnoredAccessDeniedException(Adapter\AdapterInterface $adapter)
    {
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $this->markTestSkipped('chmod is not supported on windows');
        }

        $finder = $this->buildFinder($adapter);
        $finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir);

        // make 'foo' directory non-readable
        chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333);

        $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());

        // restore original permissions
        chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777);
    }

    private function buildTestData(array $tests)
    {
        $data = array();
        foreach ($this->getValidAdapters() as $adapter) {
            foreach ($tests as $test) {
                $data[] = array_merge(array($adapter), $test);
            }
        }

        return $data;
    }

    private function buildFinder(Adapter\AdapterInterface $adapter)
    {
        return Finder::create()
            ->removeAdapters()
            ->addAdapter($adapter);
    }

    private function getValidAdapters()
    {
        return array_filter(
            array(
                new Adapter\BsdFindAdapter(),
                new Adapter\GnuFindAdapter(),
                new Adapter\PhpAdapter()
            ),
            function (Adapter\AdapterInterface $adapter)  {
                return $adapter->isSupported();
            }
        );
    }

   /**
     * Searching in multiple locations with sub directories involves
     * AppendIterator which does an unnecessary rewind which leaves
     * FilterIterator with inner FilesystemIterator in an ivalid state.
     *
     * @see https://bugs.php.net/bug.php?id=49104
     */
    public function testMultipleLocationsWithSubDirectories()
    {
        $locations = array(
            __DIR__.'/Fixtures/one',
            self::$tmpDir.DIRECTORY_SEPARATOR.'toto',
        );

        $finder = new Finder();
        $finder->in($locations)->depth('< 10')->name('*.neon');

        $expected = array(
            __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'c.neon',
            __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'d.neon',
        );

        $this->assertIterator($expected, $finder);
        $this->assertIteratorInForeach($expected, $finder);
    }

    public function testNonSeekableStream()
    {
        try {
            $i = Finder::create()->in('ftp://ftp.mozilla.org/')->depth(0)->getIterator();
        } catch (\UnexpectedValueException $e) {
            $this->markTestSkipped(sprintf('Unsupported stream "%s".', 'ftp'));
        }

        $contains = array(
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README',
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html',
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub',
        );

        $this->assertIteratorInForeach($contains, $i);
    }
}
dolor sit amet
DOLOR SIT AMETipsum dolor sit amet
IPSUM DOLOR SIT AMETlorem ipsum dolor sit amet
LOREM IPSUM DOLOR SIT AMET<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\CustomFilterIterator;

class CustomFilterIteratorTest extends IteratorTestCase
{
    /**
     * @expectedException \InvalidArgumentException
     */
    public function testWithInvalidFilter()
    {
        new CustomFilterIterator(new Iterator(), array('foo'));
    }

    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($filters, $expected)
    {
        $inner = new Iterator(array('test.php', 'test.py', 'foo.php'));

        $iterator = new CustomFilterIterator($inner, $filters);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        return array(
            array(array(function (\SplFileInfo $fileinfo) { return false; }), array()),
            array(array(function (\SplFileInfo $fileinfo) { return preg_match('/^test/', $fileinfo) > 0; }), array('test.php', 'test.py')),
            array(array('is_dir'), 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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Comparator\DateComparator;

class DateRangeFilterIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($size, $expected)
    {
        $inner = new Iterator(self::$files);

        $iterator = new DateRangeFilterIterator($inner, $size);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        $since20YearsAgo = array(
            '.git',
            'test.py',
            'foo',
            'foo/bar.tmp',
            'test.php',
            'toto',
            '.bar',
            '.foo',
            '.foo/.bar',
            'foo bar',
            '.foo/bar',
        );

        $since2MonthsAgo = array(
            '.git',
            'test.py',
            'foo',
            'toto',
            '.bar',
            '.foo',
            '.foo/.bar',
            'foo bar',
            '.foo/bar',
        );

        $untilLastMonth = array(
            '.git',
            'foo',
            'foo/bar.tmp',
            'test.php',
            'toto',
            '.foo',
        );

        return array(
            array(array(new DateComparator('since 20 years ago')), $this->toAbsolute($since20YearsAgo)),
            array(array(new DateComparator('since 2 months ago')), $this->toAbsolute($since2MonthsAgo)),
            array(array(new DateComparator('until last month')), $this->toAbsolute($untilLastMonth)),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;

class DepthRangeFilterIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($minDepth, $maxDepth, $expected)
    {
        $inner = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);

        $iterator = new DepthRangeFilterIterator($inner, $minDepth, $maxDepth);

        $actual = array_keys(iterator_to_array($iterator));
        sort($expected);
        sort($actual);
        $this->assertEquals($expected, $actual);
    }

    public function getAcceptData()
    {
        $lessThan1 = array(
            '.git',
            'test.py',
            'foo',
            'test.php',
            'toto',
            '.foo',
            '.bar',
            'foo bar',
        );

        $lessThanOrEqualTo1 = array(
            '.git',
            'test.py',
            'foo',
            'foo/bar.tmp',
            'test.php',
            'toto',
            '.foo',
            '.foo/.bar',
            '.bar',
            'foo bar',
            '.foo/bar',
        );

        $graterThanOrEqualTo1 = array(
            'foo/bar.tmp',
            '.foo/.bar',
            '.foo/bar',
        );

        $equalTo1 = array(
            'foo/bar.tmp',
            '.foo/.bar',
            '.foo/bar',
        );

        return array(
            array(0, 0, $this->toAbsolute($lessThan1)),
            array(0, 1, $this->toAbsolute($lessThanOrEqualTo1)),
            array(2, PHP_INT_MAX, array()),
            array(1, PHP_INT_MAX, $this->toAbsolute($graterThanOrEqualTo1)),
            array(1, 1, $this->toAbsolute($equalTo1)),
        );
    }

}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;

class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($directories, $expected)
    {
        $inner = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);

        $iterator = new ExcludeDirectoryFilterIterator($inner, $directories);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        $foo = array(
            '.bar',
            '.foo',
            '.foo/.bar',
            '.foo/bar',
            '.git',
            'test.py',
            'test.php',
            'toto',
            'foo bar'
        );

        $fo = array(
            '.bar',
            '.foo',
            '.foo/.bar',
            '.foo/bar',
            '.git',
            'test.py',
            'foo',
            'foo/bar.tmp',
            'test.php',
            'toto',
            'foo bar'
        );

        return array(
            array(array('foo'), $this->toAbsolute($foo)),
            array(array('fo'), $this->toAbsolute($fo)),
        );
    }

}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\FilePathsIterator;

class FilePathsIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getSubPathData
     */
    public function testSubPath($baseDir, array $paths, array $subPaths, array $subPathnames)
    {
        $iterator = new FilePathsIterator($paths, $baseDir);

        foreach ($iterator as $index => $file) {
            $this->assertEquals($paths[$index], $file->getPathname());
            $this->assertEquals($subPaths[$index], $iterator->getSubPath());
            $this->assertEquals($subPathnames[$index], $iterator->getSubPathname());
        }
    }

    public function getSubPathData()
    {
        $tmpDir = sys_get_temp_dir().'/symfony2_finder';

        return array(
            array(
                $tmpDir,
                array( // paths
                    $tmpDir.DIRECTORY_SEPARATOR.'.git' => $tmpDir.DIRECTORY_SEPARATOR.'.git',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.py' => $tmpDir.DIRECTORY_SEPARATOR.'test.py',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo' => $tmpDir.DIRECTORY_SEPARATOR.'foo',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.php' => $tmpDir.DIRECTORY_SEPARATOR.'test.php',
                    $tmpDir.DIRECTORY_SEPARATOR.'toto' => $tmpDir.DIRECTORY_SEPARATOR.'toto'
                ),
                array( // subPaths
                    $tmpDir.DIRECTORY_SEPARATOR.'.git' => '',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.py' => '',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo' => '',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.php' => '',
                    $tmpDir.DIRECTORY_SEPARATOR.'toto' => ''
                ),
                array( // subPathnames
                    $tmpDir.DIRECTORY_SEPARATOR.'.git' => '.git',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.py' => 'test.py',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo' => 'foo',
                    $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo'.DIRECTORY_SEPARATOR.'bar.tmp',
                    $tmpDir.DIRECTORY_SEPARATOR.'test.php' => 'test.php',
                    $tmpDir.DIRECTORY_SEPARATOR.'toto' => 'toto'
                ),
            ),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\FileTypeFilterIterator;

class FileTypeFilterIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($mode, $expected)
    {
        $inner = new InnerTypeIterator(self::$files);

        $iterator = new FileTypeFilterIterator($inner, $mode);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        $onlyFiles = array(
            'test.py',
            'foo/bar.tmp',
            'test.php',
            '.bar',
            '.foo/.bar',
            '.foo/bar',
            'foo bar',
        );

        $onlyDirectories = array(
            '.git',
            'foo',
            'toto',
            '.foo',
        );

        return array(
            array(FileTypeFilterIterator::ONLY_FILES, $this->toAbsolute($onlyFiles)),
            array(FileTypeFilterIterator::ONLY_DIRECTORIES, $this->toAbsolute($onlyDirectories)),
        );
    }
}

class InnerTypeIterator extends \ArrayIterator
{
   public function current()
    {
        return new \SplFileInfo(parent::current());
    }

    public function isFile()
    {
        return $this->current()->isFile();
    }

    public function isDir()
    {
        return $this->current()->isDir();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Tests\Iterator\MockSplFileInfo;
use Symfony\Component\Finder\Tests\Iterator\MockFileListIterator;

class FilecontentFilterIteratorTest extends IteratorTestCase
{

    public function testAccept()
    {
        $inner = new MockFileListIterator(array('test.txt'));
        $iterator = new FilecontentFilterIterator($inner, array(), array());
        $this->assertIterator(array('test.txt'), $iterator);
    }

    public function testDirectory()
    {
        $inner = new MockFileListIterator(array('directory'));
        $iterator = new FilecontentFilterIterator($inner, array('directory'), array());
        $this->assertIterator(array(), $iterator);
    }

    public function testUnreadableFile()
    {
        $inner = new MockFileListIterator(array('file r-'));
        $iterator = new FilecontentFilterIterator($inner, array('file r-'), array());
        $this->assertIterator(array(), $iterator);
    }

    /**
     * @dataProvider getTestFilterData
     */
    public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
    {
        $iterator = new FilecontentFilterIterator($inner, $matchPatterns, $noMatchPatterns);
        $this->assertIterator($resultArray, $iterator);
    }

    public function getTestFilterData()
    {
        $inner = new MockFileListIterator();

        $inner[] = new MockSplFileInfo(array(
            'name'     => 'a.txt',
            'contents' => 'Lorem ipsum...',
            'type'     => 'file',
            'mode'     => 'r+')
        );

        $inner[] = new MockSplFileInfo(array(
            'name'     => 'b.yml',
            'contents' => 'dolor sit...',
            'type'     => 'file',
            'mode'     => 'r+')
        );

        $inner[] = new MockSplFileInfo(array(
            'name'     => 'some/other/dir/third.php',
            'contents' => 'amet...',
            'type'     => 'file',
            'mode'     => 'r+')
        );

        $inner[] = new MockSplFileInfo(array(
            'name'     => 'unreadable-file.txt',
            'contents' => false,
            'type'     => 'file',
            'mode'     => 'r+')
        );

        return array(
            array($inner, array('.'), array(), array('a.txt', 'b.yml', 'some/other/dir/third.php')),
            array($inner, array('ipsum'), array(), array('a.txt')),
            array($inner, array('i', 'amet'), array('Lorem', 'amet'), array('b.yml')),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\FilenameFilterIterator;

class FilenameFilterIteratorTest extends IteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($matchPatterns, $noMatchPatterns, $expected)
    {
        $inner = new InnerNameIterator(array('test.php', 'test.py', 'foo.php'));

        $iterator = new FilenameFilterIterator($inner, $matchPatterns, $noMatchPatterns);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        return array(
            array(array('test.*'), array(), array('test.php', 'test.py')),
            array(array(), array('test.*'), array('foo.php')),
            array(array('*.php'), array('test.*'), array('foo.php')),
            array(array('*.php', '*.py'), array('foo.*'), array('test.php', 'test.py')),
            array(array('/\.php$/'), array(), array('test.php', 'foo.php')),
            array(array(), array('/\.php$/'), array('test.py')),
        );
    }
}

class InnerNameIterator extends \ArrayIterator
{
    public function current()
    {
        return new \SplFileInfo(parent::current());
    }

    public function getFilename()
    {
        return parent::current();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

/**
 * @author Alex Bogomazov
 */
class FilterIteratorTest extends RealIteratorTestCase
{
    public function testFilterFilesystemIterators()
    {
        $i = new \FilesystemIterator($this->toAbsolute());

        // it is expected that there are test.py test.php in the tmpDir
        $i = $this->getMockForAbstractClass('Symfony\Component\Finder\Iterator\FilterIterator', array($i));
        $i->expects($this->any())
            ->method('accept')
            ->will($this->returnCallback(function () use ($i) {
                return (bool) preg_match('/\.php/', (string) $i->current());
            })
        );

        $c = 0;
        foreach ($i as $item) {
            $c++;
        }

        $this->assertEquals(1, $c);

        $i->rewind();

        $c = 0;
        foreach ($i as $item) {
            $c++;
        }

        // This would fail with \FilterIterator but works with Symfony\Component\Finder\Iterator\FilterIterator
        // see https://bugs.php.net/bug.php?id=49104
        $this->assertEquals(1, $c);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

class Iterator implements \Iterator
{
    protected $values;

    public function __construct(array $values = array())
    {
        $this->values = array();
        foreach ($values as $value) {
            $this->attach(new \SplFileInfo($value));
        }
        $this->rewind();
    }

    public function attach(\SplFileInfo $fileinfo)
    {
        $this->values[] = $fileinfo;
    }

    public function rewind()
    {
        reset($this->values);
    }

    public function valid()
    {
        return false !== $this->current();
    }

    public function next()
    {
        next($this->values);
    }

    public function current()
    {
        return current($this->values);
    }

    public function key()
    {
        return key($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\Finder\Tests\Iterator;

abstract class IteratorTestCase extends \PHPUnit_Framework_TestCase
{
    protected function assertIterator($expected, \Traversable $iterator)
    {
        // set iterator_to_array $use_key to false to avoid values merge
        // this made FinderTest::testAppendWithAnArray() failed with GnuFinderAdapter
        $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator, false));

        $expected = array_map(function ($path) { return str_replace('/', DIRECTORY_SEPARATOR, $path); }, $expected);

        sort($values);
        sort($expected);

        $this->assertEquals($expected, array_values($values));
    }

    protected function assertOrderedIterator($expected, \Traversable $iterator)
    {
        $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator));

        $this->assertEquals($expected, array_values($values));
    }

    /**
     * Same as IteratorTestCase::assertIterator with foreach usage
     *
     * @param array $expected
     * @param \Traversable $iterator
     */
    protected function assertIteratorInForeach($expected, \Traversable $iterator)
    {
        $values = array();
        foreach ($iterator as $file) {
            $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file);
            $values[] = $file->getPathname();
        }

        sort($values);
        sort($expected);

        $this->assertEquals($expected, array_values($values));
    }

    /**
     * Same as IteratorTestCase::assertOrderedIterator with foreach usage
     *
     * @param array $expected
     * @param \Traversable $iterator
     */
    protected function assertOrderedIteratorInForeach($expected, \Traversable $iterator)
    {
        $values = array();
        foreach ($iterator as $file) {
            $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file);
            $values[] = $file->getPathname();
        }

        $this->assertEquals($expected, array_values($values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Tests\Iterator;

class MockFileListIterator extends \ArrayIterator
{
    public function __construct(array $filesArray = array())
    {
        $files = array_map(function($file){ return new MockSplFileInfo($file); }, $filesArray);
        parent::__construct($files);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

class MockSplFileInfo extends \SplFileInfo
{
    const   TYPE_DIRECTORY = 1;
    const   TYPE_FILE      = 2;
    const   TYPE_UNKNOWN   = 3;

    private $contents         = null;
    private $mode             = null;
    private $type             = null;
    private $relativePath     = null;
    private $relativePathname = null;

    public function __construct($param)
    {
        if (is_string($param)) {
            parent::__construct($param);
        } elseif (is_array($param)) {
            $defaults = array(
              'name'             => 'file.txt',
              'contents'         => null,
              'mode'             => null,
              'type'             => null,
              'relativePath'     => null,
              'relativePathname' => null,
            );
            $defaults = array_merge($defaults, $param);
            parent::__construct($defaults['name']);
            $this->setContents($defaults['contents']);
            $this->setMode($defaults['mode']);
            $this->setType($defaults['type']);
            $this->setRelativePath($defaults['relativePath']);
            $this->setRelativePathname($defaults['relativePathname']);
        } else {
            throw new \RuntimeException(sprintf('Incorrect parameter "%s"', $param));
        }
    }

    public function isFile()
    {
        if ($this->type === null) {
            return preg_match('/file/', $this->getFilename());
        };

        return self::TYPE_FILE === $this->type;
    }

    public function isDir()
    {
        if ($this->type === null) {
            return preg_match('/directory/', $this->getFilename());
        }

        return self::TYPE_DIRECTORY === $this->type;
    }

    public function isReadable()
    {
        if ($this->mode === null) {
            return preg_match('/r\+/', $this->getFilename());
        }

        return preg_match('/r\+/', $this->mode);
    }

    public function getContents()
    {
        return $this->contents;
    }

    public function setContents($contents)
    {
        $this->contents = $contents;
    }

    public function setMode($mode)
    {
        $this->mode = $mode;
    }

    public function setType($type)
    {
        if (is_string($type)) {
            switch ($type) {
                case 'directory':
                    $this->type = self::TYPE_DIRECTORY;
                case 'd':
                    $this->type = self::TYPE_DIRECTORY;
                    break;
                case 'file':
                    $this->type = self::TYPE_FILE;
                case 'f':
                    $this->type = self::TYPE_FILE;
                    break;
                default:
                    $this->type = self::TYPE_UNKNOWN;
            }
        } else {
            $this->type = $type;
        }
    }

    public function setRelativePath($relativePath)
    {
        $this->relativePath = $relativePath;
    }

    public function setRelativePathname($relativePathname)
    {
        $this->relativePathname = $relativePathname;
    }

    public function getRelativePath()
    {
        return $this->relativePath;
    }

    public function getRelativePathname()
    {
        return $this->relativePathname;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\MultiplePcreFilterIterator;

class MultiplePcreFilterIteratorTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getIsRegexFixtures
     */
    public function testIsRegex($string, $isRegex, $message)
    {
        $testIterator = new TestMultiplePcreFilterIterator();
        $this->assertEquals($isRegex, $testIterator->isRegex($string), $message);
    }

    public function getIsRegexFixtures()
    {
        return array(
            array('foo', false, 'string'),
            array(' foo ', false, '" " is not a valid delimiter'),
            array('\\foo\\', false, '"\\" is not a valid delimiter'),
            array('afooa', false, '"a" is not a valid delimiter'),
            array('//', false, 'the pattern should contain at least 1 character'),
            array('/a/', true, 'valid regex'),
            array('/foo/', true, 'valid regex'),
            array('/foo/i', true, 'valid regex with a single modifier'),
            array('/foo/imsxu', true, 'valid regex with multiple modifiers'),
            array('#foo#', true, '"#" is a valid delimiter'),
            array('{foo}', true, '"{,}" is a valid delimiter pair'),
            array('*foo.*', false, '"*" is not considered as a valid delimiter'),
            array('?foo.?', false, '"?" is not considered as a valid delimiter'),
        );
    }
}

class TestMultiplePcreFilterIterator extends MultiplePcreFilterIterator
{
    public function __construct()
    {
    }

    public function accept()
    {
        throw new \BadFunctionCallException('Not implemented');
    }

    public function isRegex($str)
    {
        return parent::isRegex($str);
    }

    public function toRegex($str)
    {
        throw new \BadFunctionCallException('Not implemented');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\PathFilterIterator;

class PathFilterIteratorTest extends IteratorTestCase
{

    /**
     * @dataProvider getTestFilterData
     */
    public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
    {
        $iterator = new PathFilterIterator($inner, $matchPatterns, $noMatchPatterns);
        $this->assertIterator($resultArray, $iterator);
    }

    public function getTestFilterData()
    {
        $inner = new MockFileListIterator();

        //PATH:   A/B/C/abc.dat
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'abc.dat',
            'relativePathname'  => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
        ));

        //PATH:   A/B/ab.dat
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'ab.dat',
            'relativePathname'  => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
        ));

        //PATH:   A/a.dat
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'a.dat',
            'relativePathname'  => 'A'.DIRECTORY_SEPARATOR.'a.dat',
        ));

        //PATH:   copy/A/B/C/abc.dat.copy
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'abc.dat.copy',
            'relativePathname'  => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
        ));

        //PATH:   copy/A/B/ab.dat.copy
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'ab.dat.copy',
            'relativePathname'  => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
        ));

        //PATH:   copy/A/a.dat.copy
        $inner[] = new MockSplFileInfo(array(
            'name'              => 'a.dat.copy',
            'relativePathname'  => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'a.dat',
        ));

        return array(
            array($inner, array('/^A/'),       array(), array('abc.dat', 'ab.dat', 'a.dat')),
            array($inner, array('/^A\/B/'),    array(), array('abc.dat', 'ab.dat')),
            array($inner, array('/^A\/B\/C/'), array(), array('abc.dat')),
            array($inner, array('/A\/B\/C/'),  array(), array('abc.dat', 'abc.dat.copy')),

            array($inner, array('A'),      array(), array('abc.dat', 'ab.dat', 'a.dat', 'abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')),
            array($inner, array('A/B'),    array(), array('abc.dat', 'ab.dat', 'abc.dat.copy', 'ab.dat.copy')),
            array($inner, array('A/B/C'),  array(), array('abc.dat', 'abc.dat.copy')),

            array($inner, array('copy/A'),      array(), array('abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')),
            array($inner, array('copy/A/B'),    array(), array('abc.dat.copy', 'ab.dat.copy')),
            array($inner, array('copy/A/B/C'),  array(), array('abc.dat.copy')),

        );
    }

}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

abstract class RealIteratorTestCase extends IteratorTestCase
{

    protected static $tmpDir;
    protected static $files;

    public static function setUpBeforeClass()
    {
        self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony2_finder';

        self::$files = array(
            '.git/',
            '.foo/',
            '.foo/.bar',
            '.foo/bar',
            '.bar',
            'test.py',
            'foo/',
            'foo/bar.tmp',
            'test.php',
            'toto/',
            'foo bar'
        );

        self::$files = self::toAbsolute(self::$files);

        if (is_dir(self::$tmpDir)) {
            self::tearDownAfterClass();
        } else {
            mkdir(self::$tmpDir);
        }

        foreach (self::$files as $file) {
            if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) {
                mkdir($file);
            } else {
                touch($file);
            }
        }

        file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800));
        file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000));

        touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
        touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));
    }

    public static function tearDownAfterClass()
    {
        foreach (array_reverse(self::$files) as $file) {
            if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) {
                @rmdir($file);
            } else {
                @unlink($file);
            }
        }
    }

    protected static function toAbsolute($files = null)
    {
        /*
         * Without the call to setUpBeforeClass() property can be null.
         */
        if (!self::$tmpDir) {
            self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony2_finder';
        }

        if (is_array($files)) {
            $f = array();
            foreach ($files as $file) {
                $f[] = self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $file);
            }

            return $f;
        }

        if (is_string($files)) {

            return self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $files);
        }

        return self::$tmpDir;
    }

    protected static function toAbsoluteFixtures($files)
    {
        $f = array();
        foreach ($files as $file) {
            $f[] = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.$file);
        }

        return $f;
    }

}
<?php

/*
 * This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;

class RecursiveDirectoryIteratorTest extends IteratorTestCase
{
    /**
     * @dataProvider getPaths
     *
     * @param string  $path
     * @param Boolean $seekable
     * @param Boolean $supports
     * @param string  $message
     */
    public function testRewind($path, $seekable, $contains, $message = null)
    {
        try {
            $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path));
        }

        $i->rewind();

        $this->assertTrue(true, $message);
    }

    /**
     * @dataProvider getPaths
     *
     * @param string  $path
     * @param Boolean $seekable
     * @param Boolean $supports
     * @param string  $message
     */
    public function testSeek($path, $seekable, $contains, $message = null)
    {
        try {
            $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path));
        }

        $actual = array();

        $i->seek(0);
        $actual[] = $i->getPathname();

        $i->seek(1);
        $actual[] = $i->getPathname();

        $i->seek(2);
        $actual[] = $i->getPathname();

        $this->assertEquals($contains, $actual);
    }

    public function getPaths()
    {
        $data = array();

        // ftp
        $contains = array(
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README',
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html',
            'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub',
        );
        $data[] = array('ftp://ftp.mozilla.org/', false, $contains);

        return $data;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Comparator\NumberComparator;

class SizeRangeFilterIteratorTest extends RealIteratorTestCase
{
    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($size, $expected)
    {
        $inner = new InnerSizeIterator(self::$files);

        $iterator = new SizeRangeFilterIterator($inner, $size);

        $this->assertIterator($expected, $iterator);
    }

    public function getAcceptData()
    {
        $lessThan1KGreaterThan05K = array(
            '.foo',
            '.git',
            'foo',
            'test.php',
            'toto',
        );

        return array(
            array(array(new NumberComparator('< 1K'), new NumberComparator('> 0.5K')), $this->toAbsolute($lessThan1KGreaterThan05K)),
        );
    }
}

class InnerSizeIterator extends \ArrayIterator
{
   public function current()
    {
        return new \SplFileInfo(parent::current());
    }

    public function getFilename()
    {
        return parent::current();
    }

    public function isFile()
    {
        return $this->current()->isFile();
    }

    public function getSize()
    {
        return $this->current()->getSize();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Iterator;

use Symfony\Component\Finder\Iterator\SortableIterator;

class SortableIteratorTest extends RealIteratorTestCase
{
    public function testConstructor()
    {
        try {
            new SortableIterator(new Iterator(array()), 'foobar');
            $this->fail('__construct() throws an \InvalidArgumentException exception if the mode is not valid');
        } catch (\Exception $e) {
            $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException exception if the mode is not valid');
        }
    }

    /**
     * @dataProvider getAcceptData
     */
    public function testAccept($mode, $expected)
    {
        $inner = new Iterator(self::$files);

        $iterator = new SortableIterator($inner, $mode);

        $this->assertOrderedIterator($expected, $iterator);
    }

    public function getAcceptData()
    {

        $sortByName = array(
            '.bar',
            '.foo',
            '.foo/.bar',
            '.foo/bar',
            '.git',
            'foo',
            'foo bar',
            'foo/bar.tmp',
            'test.php',
            'test.py',
            'toto',
        );

        $sortByType = array(
            '.foo',
            '.git',
            'foo',
            'toto',
            '.bar',
            '.foo/.bar',
            '.foo/bar',
            'foo bar',
            'foo/bar.tmp',
            'test.php',
            'test.py',
        );

        $customComparison = array(
            '.bar',
            '.foo',
            '.foo/.bar',
            '.foo/bar',
            '.git',
            'foo',
            'foo bar',
            'foo/bar.tmp',
            'test.php',
            'test.py',
            'toto',
        );

        return array(
            array(SortableIterator::SORT_BY_NAME, $this->toAbsolute($sortByName)),
            array(SortableIterator::SORT_BY_TYPE, $this->toAbsolute($sortByType)),
            array(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }, $this->toAbsolute($customComparison)),
        );
    }
}
{
    "name": "symfony/finder",
    "type": "library",
    "description": "Symfony Finder Component",
    "keywords": [],
    "homepage": "http://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "http://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "Symfony\\Component\\Finder\\": "" }
    },
    "target-dir": "Symfony/Component/Finder",
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "2.3-dev"
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="Symfony Finder Component Test Suite">
            <directory>./Tests/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>./</directory>
            <exclude>
                <directory>./Tests</directory>
                <directory>./vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
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\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\Helper\DialogHelper;
use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
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>
 *
 * @api
 */
class Application
{
    private $commands;
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $catchExceptions;
    private $autoExit;
    private $definition;
    private $helperSet;
    private $dispatcher;

    /**
     * Constructor.
     *
     * @param string $name    The name of the application
     * @param string $version The version of the application
     *
     * @api
     */
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->catchExceptions = true;
        $this->autoExit = true;
        $this->commands = array();
        $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 integer 0 if everything went fine, or an error code
     *
     * @throws \Exception When doRun returns Exception
     *
     * @api
     */
    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();

            $exitCode = $exitCode ? (is_numeric($exitCode) ? (int) $exitCode : 1) : 0;
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }
            // @codeCoverageIgnoreStart
            exit($exitCode);
            // @codeCoverageIgnoreEnd
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @return integer 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--version', '-V'))) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(array('--help', '-h'))) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(array('command' => 'help'));
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = 'list';
            $input = new ArrayInput(array('command' => 'list'));
        }

        // 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
     *
     * @api
     */
    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
     *
     * @api
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Set an input definition set to be used with this application
     *
     * @param InputDefinition $definition The input definition
     *
     * @api
     */
    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()
    {
        $messages = array(
            $this->getLongVersion(),
            '',
            '<comment>Usage:</comment>',
            '  [options] command [arguments]',
            '',
            '<comment>Options:</comment>',
        );

        foreach ($this->getDefinition()->getOptions() as $option) {
            $messages[] = sprintf('  %-29s %s %s',
                '<info>--'.$option->getName().'</info>',
                $option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : '  ',
                $option->getDescription()
            );
        }

        return implode(PHP_EOL, $messages);
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @param Boolean $boolean Whether to catch exceptions or not during commands execution
     *
     * @api
     */
    public function setCatchExceptions($boolean)
    {
        $this->catchExceptions = (Boolean) $boolean;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @param Boolean $boolean Whether to automatically exit after a command execution or not
     *
     * @api
     */
    public function setAutoExit($boolean)
    {
        $this->autoExit = (Boolean) $boolean;
    }

    /**
     * Gets the name of the application.
     *
     * @return string The application name
     *
     * @api
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @param string $name The application name
     *
     * @api
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     *
     * @return string The application version
     *
     * @api
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @param string $version The application version
     *
     * @api
     */
    public function setVersion($version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string The long application version
     *
     * @api
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
            return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
        }

        return '<info>Console Tool</info>';
    }

    /**
     * Registers a new command.
     *
     * @param string $name The command name
     *
     * @return Command The newly created command
     *
     * @api
     */
    public function register($name)
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * @param Command[] $commands An array of commands
     *
     * @api
     */
    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.
     *
     * @param Command $command A Command object
     *
     * @return Command The registered command
     *
     * @api
     */
    public function add(Command $command)
    {
        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return;
        }

        $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 \InvalidArgumentException When command name given does not exist
     *
     * @api
     */
    public function get($name)
    {
        if (!isset($this->commands[$name])) {
            throw new \InvalidArgumentException(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 Boolean true if the command exists, false otherwise
     *
     * @api
     */
    public function has($name)
    {
        return isset($this->commands[$name]);
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not returns the global namespace which always exists.
     *
     * @return array An array of namespaces
     */
    public function getNamespaces()
    {
        $namespaces = array();
        foreach ($this->commands as $command) {
            $namespaces[] = $this->extractNamespace($command->getName());

            foreach ($command->getAliases() as $alias) {
                $namespaces[] = $this->extractNamespace($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 \InvalidArgumentException When namespace is incorrect or ambiguous
     */
    public function findNamespace($namespace)
    {
        $allNamespaces = $this->getNamespaces();
        $found = '';
        foreach (explode(':', $namespace) as $i => $part) {
            // select sub-namespaces matching the current namespace we found
            $namespaces = array();
            foreach ($allNamespaces as $n) {
                if ('' === $found || 0 === strpos($n, $found)) {
                    $namespaces[$n] = explode(':', $n);
                }
            }

            $abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $namespaces)))));

            if (!isset($abbrevs[$part])) {
                $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);

                if (1 <= $i) {
                    $part = $found.':'.$part;
                }

                if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) {
                    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 \InvalidArgumentException($message);
            }

            // there are multiple matches, but $part is an exact match of one of them so we select it
            if (in_array($part, $abbrevs[$part])) {
                $abbrevs[$part] = array($part);
            }

            if (count($abbrevs[$part]) > 1) {
                throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$part])));
            }

            $found .= $found ? ':' . $abbrevs[$part][0] : $abbrevs[$part][0];
        }

        return $found;
    }

    /**
     * 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 \InvalidArgumentException When command name is incorrect or ambiguous
     *
     * @api
     */
    public function find($name)
    {
        // namespace
        $namespace = '';
        $searchName = $name;
        if (false !== $pos = strrpos($name, ':')) {
            $namespace = $this->findNamespace(substr($name, 0, $pos));
            $searchName = $namespace.substr($name, $pos);
        }

        // name
        $commands = array();
        foreach ($this->commands as $command) {
            $extractedNamespace = $this->extractNamespace($command->getName());
            if ($extractedNamespace === $namespace
               || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
            ) {
                $commands[] = $command->getName();
            }
        }

        $abbrevs = static::getAbbreviations(array_unique($commands));
        if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) {
            return $this->get($abbrevs[$searchName][0]);
        }

        if (isset($abbrevs[$searchName]) && in_array($searchName, $abbrevs[$searchName])) {
            return $this->get($searchName);
        }

        if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) {
            $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]);

            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
        }

        // aliases
        $aliases = array();
        foreach ($this->commands as $command) {
            foreach ($command->getAliases() as $alias) {
                $extractedNamespace = $this->extractNamespace($alias);
                if ($extractedNamespace === $namespace
                   || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
                ) {
                    $aliases[] = $alias;
                }
            }
        }

        $aliases = static::getAbbreviations(array_unique($aliases));
        if (!isset($aliases[$searchName])) {
            $message = sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternativeCommands($searchName, $abbrevs)) {
                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 \InvalidArgumentException($message);
        }

        if (count($aliases[$searchName]) > 1) {
            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($aliases[$searchName])));
        }

        return $this->get($aliases[$searchName][0]);
    }

    /**
     * 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
     *
     * @api
     */
    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;
    }

    /**
     * Returns a text representation of the Application.
     *
     * @param string  $namespace An optional namespace name
     * @param boolean $raw       Whether to return raw command list
     *
     * @return string A string representing the Application
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText($namespace = null, $raw = false)
    {
        $descriptor = new TextDescriptor();

        return $descriptor->describe($this, array('namespace' => $namespace, 'raw_text' => $raw));
    }

    /**
     * Returns an XML representation of the Application.
     *
     * @param string  $namespace An optional namespace name
     * @param Boolean $asDom     Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the Application
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($namespace = null, $asDom = false)
    {
        $descriptor = new XmlDescriptor();

        return $descriptor->describe($this, array('namespace' => $namespace, 'as_dom' => $asDom));
    }

    /**
     * Renders a caught exception.
     *
     * @param Exception       $e      An exception instance
     * @param OutputInterface $output An OutputInterface instance
     */
    public function renderException($e, $output)
    {
        $strlen = function ($string) {
            if (!function_exists('mb_strlen')) {
                return strlen($string);
            }

            if (false === $encoding = mb_detect_encoding($string)) {
                return strlen($string);
            }

            return mb_strlen($string, $encoding);
        };

        do {
            $title = sprintf('  [%s]  ', get_class($e));
            $len = $strlen($title);
            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
            $lines = array();
            foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
                foreach (str_split($line, $width - 4) as $line) {
                    $lines[] = sprintf('  %s  ', $line);
                    $len = max($strlen($line) + 4, $len);
                }
            }

            $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', max(0, $len - $strlen($title))));

            foreach ($lines as $line) {
                $messages[] = $line.str_repeat(' ', $len - $strlen($line));
            }

            $messages[] = str_repeat(' ', $len);

            $output->writeln("");
            $output->writeln("");
            foreach ($messages as $message) {
                $output->writeln('<error>'.$message.'</error>');
            }
            $output->writeln("");
            $output->writeln("");

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>');

                // 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));
                }

                $output->writeln("");
                $output->writeln("");
            }
        } while ($e = $e->getPrevious());

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
            $output->writeln("");
            $output->writeln("");
        }
    }

    /**
     * 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 (defined('PHP_WINDOWS_VERSION_BUILD')) {
            // 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);
    }

    /**
     * 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'))) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
            $input->setInteractive(false);
        }

        if (function_exists('posix_isatty') && $this->getHelperSet()->has('dialog')) {
            $inputStream = $this->getHelperSet()->get('dialog')->getInputStream();
            if (!posix_isatty($inputStream)) {
                $input->setInteractive(false);
            }
        }

        if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
        } else {
            if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
            } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
            } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
                $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 integer 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        if (null === $this->dispatcher) {
            return $command->run($input, $output);
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

        try {
            $exitCode = $command->run($input, $output);
        } catch (\Exception $e) {
            $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
            $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

            $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode());
            $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

            throw $event->getException();
        }

        $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 DialogHelper(),
            new ProgressHelper(),
            new TableHelper(),
        ));
    }

    /**
     * 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 <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 commands of $name
     *
     * @param string $name    The full name of the command
     * @param array  $abbrevs The abbreviations
     *
     * @return array A sorted array of similar commands
     */
    private function findAlternativeCommands($name, $abbrevs)
    {
        $callback = function($item) {
            return $item->getName();
        };

        return $this->findAlternatives($name, $this->commands, $abbrevs, $callback);
    }

    /**
     * Finds alternative namespace of $name
     *
     * @param string $name    The full name of the namespace
     * @param array  $abbrevs The abbreviations
     *
     * @return array A sorted array of similar namespace
     */
    private function findAlternativeNamespace($name, $abbrevs)
    {
        return $this->findAlternatives($name, $this->getNamespaces(), $abbrevs);
    }

    /**
     * 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
     * @param array                $abbrevs    The abbreviations
     * @param Closure|string|array $callback   The callable to transform collection item before comparison
     *
     * @return array A sorted array of similar string
     */
    private function findAlternatives($name, $collection, $abbrevs, $callback = null)
    {
        $alternatives = array();

        foreach ($collection as $item) {
            if (null !== $callback) {
                $item = call_user_func($callback, $item);
            }

            $lev = levenshtein($name, $item);
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
                $alternatives[$item] = $lev;
            }
        }

        if (!$alternatives) {
            foreach ($abbrevs as $key => $values) {
                $lev = levenshtein($name, $key);
                if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) {
                    foreach ($values as $value) {
                        $alternatives[$value] = $lev;
                    }
                }
            }
        }

        asort($alternatives);

        return array_keys($alternatives);
    }
}
CHANGELOG
=========

2.3.0
-----

 * added multiselect support to the select dialog helper
 * added Table Helper for tabular data rendering
 * added support for events in `Application`
 * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
 * added a way to set the progress bar progress via the `setCurrent` method
 * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'`
 * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG

2.2.0
-----

 * added support for colorization on Windows via ConEmu
 * add a method to Dialog Helper to ask for a question and hide the response
 * added support for interactive selections in console (DialogHelper::select())
 * added support for autocompletion as you type in Dialog Helper

2.1.0
-----

 * added ConsoleOutputInterface
 * added the possibility to disable a command (Command::isEnabled())
 * added suggestions when a command does not exist
 * added a --raw option to the list command
 * added support for STDERR in the console output class (errors are now sent
   to STDERR)
 * made the defaults (helper set, commands, input definition) in Application
   more easily customizable
 * added support for the shell even if readline is not available
 * added support for process isolation in Symfony shell via
   `--process-isolation` switch
 * added support for `--`, which disables options parsing after that point
   (tokens will be parsed as 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\Console\Command;

use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
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;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
class Command
{
    private $application;
    private $name;
    private $aliases;
    private $definition;
    private $help;
    private $description;
    private $ignoreValidationErrors;
    private $applicationDefinitionMerged;
    private $applicationDefinitionMergedWithArgs;
    private $code;
    private $synopsis;
    private $helperSet;

    /**
     * Constructor.
     *
     * @param string $name The name of the command
     *
     * @throws \LogicException When the command name is empty
     *
     * @api
     */
    public function __construct($name = null)
    {
        $this->definition = new InputDefinition();
        $this->ignoreValidationErrors = false;
        $this->applicationDefinitionMerged = false;
        $this->applicationDefinitionMergedWithArgs = false;
        $this->aliases = array();

        if (null !== $name) {
            $this->setName($name);
        }

        $this->configure();

        if (!$this->name) {
            throw new \LogicException('The command name cannot be empty.');
        }
    }

    /**
     * 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
     *
     * @api
     */
    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
     *
     * @api
     */
    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 Boolean
     */
    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|integer 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.
     *
     * @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 integer The command exit code
     *
     * @throws \Exception
     *
     * @see setCode()
     * @see execute()
     *
     * @api
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        // force the creation of the synopsis before the merge with the app definition
        $this->getSynopsis();

        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->definition);
        } catch (\Exception $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        $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()
     *
     * @api
     */
    public function setCode($code)
    {
        if (!is_callable($code)) {
            throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
        }

        $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 Boolean $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;
        }

        if ($mergeArgs) {
            $currentArguments = $this->definition->getArguments();
            $this->definition->setArguments($this->application->getDefinition()->getArguments());
            $this->definition->addArguments($currentArguments);
        }

        $this->definition->addOptions($this->application->getDefinition()->getOptions());

        $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
     *
     * @api
     */
    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
     *
     * @api
     */
    public function getDefinition()
    {
        return $this->definition;
    }

    /**
     * Gets the InputDefinition to be used to create XML and Text 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 integer $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
     *
     * @api
     */
    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 integer $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_REQUIRED or InputOption::VALUE_NONE)
     *
     * @return Command The current instance
     *
     * @api
     */
    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 command name given is empty
     *
     * @api
     */
    public function setName($name)
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Returns the command name.
     *
     * @return string The command name
     *
     * @api
     */
    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
     *
     * @api
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     *
     * @return string The description for the command
     *
     * @api
     */
    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
     *
     * @api
     */
    public function setHelp($help)
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     *
     * @return string The help for the command
     *
     * @api
     */
    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());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param array $aliases An array of aliases for the command
     *
     * @return Command The current instance
     *
     * @api
     */
    public function setAliases($aliases)
    {
        foreach ($aliases as $alias) {
            $this->validateName($alias);
        }

        $this->aliases = $aliases;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     *
     * @return array An array of aliases for the command
     *
     * @api
     */
    public function getAliases()
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @return string The synopsis
     */
    public function getSynopsis()
    {
        if (null === $this->synopsis) {
            $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis()));
        }

        return $this->synopsis;
    }

    /**
     * Gets a helper instance by name.
     *
     * @param string $name The helper name
     *
     * @return mixed The helper value
     *
     * @throws \InvalidArgumentException if the helper is not defined
     *
     * @api
     */
    public function getHelper($name)
    {
        return $this->helperSet->get($name);
    }

    /**
     * Returns a text representation of the command.
     *
     * @return string A string representing the command
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        $descriptor = new TextDescriptor();

        return $descriptor->describe($this);
    }

    /**
     * Returns an XML representation of the command.
     *
     * @param Boolean $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the command
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        $descriptor = new XmlDescriptor();

        return $descriptor->describe($this, array('as_dom' => $asDom));
    }

    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('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats'),
                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'));
        }

        if ($input->getOption('xml')) {
            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, $input->getOption('format'), $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)
    {
        if ($input->getOption('xml')) {
            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), $input->getOption('format'), $input->getOption('raw'), $input->getArgument('namespace'));
    }

    /**
     * {@inheritdoc}
     */
    private function createDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
            new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'),
            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
            new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats'),
        ));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent
     * instance.
     *
     * @var string
     */
    const COMMAND = 'console.command';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent
     * instance.
     *
     * @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. The event listener method receives
     * a Symfony\Component\Console\Event\ConsoleExceptionEvent
     * instance.
     *
     * @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;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 */
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 \InvalidArgumentException
     */
    public function getCommand($name)
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new \InvalidArgumentException(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();
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (!$key) {
                $key = '_global';
            }

            $namespacedCommands[$key][$name] = $command;
        }
        ksort($namespacedCommands);

        foreach ($namespacedCommands as &$commands) {
            ksort($commands);
        }

        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;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 */
abstract class Descriptor implements DescriptorInterface
{
    public function describe($object, array $options = array())
    {
        switch (true) {
            case $object instanceof InputArgument:
                return $this->describeInputArgument($object, $options);
            case $object instanceof InputOption:
                return $this->describeInputOption($object, $options);
            case $object instanceof InputDefinition:
                return $this->describeInputDefinition($object, $options);
            case $object instanceof Command:
                return $this->describeCommand($object, $options);
            case $object instanceof Application:
                return $this->describeApplication($object, $options);
        }

        throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
    }

    /**
     * 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;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * Describes an InputArgument instance.
     *
     * @param object $object
     * @param array  $options
     *
     * @return string|mixed
     */
    public function describe($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>
 */
class JsonDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        return $this->output(array(
            'name'        => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array'    => $argument->isArray(),
            'description' => $argument->getDescription(),
            'default'     => $argument->getDefault(),
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        return $this->output(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'       => $option->getDescription(),
            'default'           => $option->getDefault(),
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $inputArguments = array();
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->describeInputArgument($argument, array('as_array' => true));
        }

        $inputOptions = array();
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->describeInputOption($option, array('as_array' => true));
        }

        return $this->output(array('arguments' => $inputArguments, 'options' => $inputOptions), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        return $this->output(array(
            'name'        => $command->getName(),
            'usage'       => $command->getSynopsis(),
            'description' => $command->getDescription(),
            'help'        => $command->getProcessedHelp(),
            'aliases'     => $command->getAliases(),
            'definition'  => $this->describeInputDefinition($command->getNativeDefinition(), array('as_array' => true)),
        ), $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->describeCommand($command, array('as_array' => true));
        }

        $data = $describedNamespace
            ? array('commands' => $commands, 'namespace' => $describedNamespace)
            : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces()));

        return $this->output($data, $options);
    }

    /**
     * Outputs data as array or string according to options.
     *
     * @param array $data
     * @param array $options
     *
     * @return array|string
     */
    private function output(array $data, array $options)
    {
        if (isset($options['as_array']) && $options['as_array']) {
            return $data;
        }

        return json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 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\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>
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        return '**'.$argument->getName().':**'."\n\n"
            .'* Name: '.($argument->getName() ?: '<none>')."\n"
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Description: '.($argument->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`';
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        return '**'.$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: '.($option->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`';
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $blocks = array();

        if (count($definition->getArguments()) > 0) {
            $blocks[] = '### Arguments:';
            foreach ($definition->getArguments() as $argument) {
                $blocks[] = $this->describeInputArgument($argument);
            }
        }

        if (count($definition->getOptions()) > 0) {
            $blocks[] = '### Options:';
            foreach ($definition->getOptions() as $option) {
                $blocks[] = $this->describeInputOption($option);
            }
        }

        return implode("\n\n", $blocks);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $markdown = $command->getName()."\n"
            .str_repeat('-', strlen($command->getName()))."\n\n"
            .'* Description: '.($command->getDescription() ?: '<none>')."\n"
            .'* Usage: `'.$command->getSynopsis().'`'."\n"
            .'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : '<none>');

        if ($help = $command->getProcessedHelp()) {
            $markdown .= "\n\n".$help;
        }

        if ($definitionMarkdown = $this->describeInputDefinition($command->getNativeDefinition())) {
            $markdown .= "\n\n".$definitionMarkdown;
        }

        return $markdown;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $blocks = array($application->getName()."\n".str_repeat('=', strlen($application->getName())));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $blocks[] = '**'.$namespace['id'].':**';
            }

            $blocks[] = implode("\n", array_map(function ($commandName) {
                return '* '.$commandName;
            } , $namespace['commands']));
        }

        foreach ($description->getCommands() as $command) {
            $blocks[] = $this->describeCommand($command);
        }

        return implode("\n\n", $blocks);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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>
 */
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 = '';
        }

        $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($argument->getName());
        $output = str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $argument->getDescription());
        $output = sprintf(" <info>%-${nameWidth}s</info> %s%s", $argument->getName(), $output, $default);

        return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
    }

    /**
     * {@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 = '';
        }

        $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($option->getName());
        $nameWithShortcutWidth = $nameWidth - strlen($option->getName()) - 2;

        $output = sprintf(" <info>%s</info> %-${nameWithShortcutWidth}s%s%s%s",
            '--'.$option->getName(),
            $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
            str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        );

        return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $nameWidth = 0;
        foreach ($definition->getOptions() as $option) {
            $nameLength = strlen($option->getName()) + 2;
            if ($option->getShortcut()) {
                $nameLength += strlen($option->getShortcut()) + 3;
            }
            $nameWidth = max($nameWidth, $nameLength);
        }
        foreach ($definition->getArguments() as $argument) {
            $nameWidth = max($nameWidth, strlen($argument->getName()));
        }
        ++$nameWidth;

        $messages = array();

        if ($definition->getArguments()) {
            $messages[] = '<comment>Arguments:</comment>';
            foreach ($definition->getArguments() as $argument) {
                $messages[] = $this->describeInputArgument($argument, array('name_width' => $nameWidth));
            }
            $messages[] = '';
        }

        if ($definition->getOptions()) {
            $messages[] = '<comment>Options:</comment>';
            foreach ($definition->getOptions() as $option) {
                $messages[] = $this->describeInputOption($option, array('name_width' => $nameWidth));
            }
            $messages[] = '';
        }

        $output = implode("\n", $messages);

        return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);
        $messages = array('<comment>Usage:</comment>', ' '.$command->getSynopsis(), '');

        if ($command->getAliases()) {
            $messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $command->getAliases()).'</info>';
        }

        $messages[] = $this->describeInputDefinition($command->getNativeDefinition());

        if ($help = $command->getProcessedHelp()) {
            $messages[] = '<comment>Help:</comment>';
            $messages[] = ' '.str_replace("\n", "\n ", $help)."\n";
        }

        $output = implode("\n", $messages);

        return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $messages = array();

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $messages[] = sprintf("%-${width}s %s", $command->getName(), $command->getDescription());
            }
        } else {
            $width = $this->getColumnWidth($description->getCommands());

            $messages[] = $application->getHelp();
            $messages[] = '';

            if ($describedNamespace) {
                $messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $describedNamespace);
            } else {
                $messages[] = '<comment>Available commands:</comment>';
            }

            // add commands by namespace
            foreach ($description->getNamespaces() as $namespace) {
                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $messages[] = '<comment>'.$namespace['id'].'</comment>';
                }

                foreach ($namespace['commands'] as $name) {
                    $messages[] = sprintf("  <info>%-${width}s</info> %s", $name, $description->getCommand($name)->getDescription());
                }
            }
        }

        $output = implode("\n", $messages);

        return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
    }

    /**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     *
     * @return string
     */
    private function formatDefaultValue($default)
    {
        if (version_compare(PHP_VERSION, '5.4', '<')) {
            return str_replace('\/', '/', json_encode($default));
        }

        return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }

    /**
     * @param Command[] $commands
     *
     * @return int
     */
    private function getColumnWidth(array $commands)
    {
        $width = 0;
        foreach ($commands as $command) {
            $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
        }

        return $width + 2;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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>
 */
class XmlDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $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 $this->output($dom, $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $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 $this->output($dom, $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $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->describeInputArgument($argument, array('as_dom' => true)));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->describeInputOption($option, array('as_dom' => true)));
        }

        return $this->output($dom, $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $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($usageXML = $dom->createElement('usage'));
        $usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), '')));

        $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())));

        $commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
        foreach ($command->getAliases() as $alias) {
            $aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
            $aliasXML->appendChild($dom->createTextNode($alias));
        }

        $definitionXML = $this->describeInputDefinition($command->getNativeDefinition(), array('as_dom' => true));
        $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));

        return $this->output($dom, $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));
        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if ($describedNamespace) {
            $commandsXML->setAttribute('namespace', $describedNamespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->describeCommand($command, array('as_dom' => true)));
        }

        if (!$describedNamespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespace) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespace['id']);

                foreach ($namespace['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $this->output($dom, $options);
    }

    /**
     * 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));
        }
    }

    /**
     * Outputs document as DOMDocument or string according to options.
     *
     * @param \DOMDocument $dom
     * @param array        $options
     *
     * @return \DOMDocument|string
     */
    private function output(\DOMDocument $dom, array $options)
    {
        if (isset($options['as_dom']) && $options['as_dom']) {
            return $dom;
        }

        $dom->formatOutput = true;

        return $dom->saveXML();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 do things before the command is executed.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleCommandEvent extends ConsoleEvent
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 = $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 integer 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 integer
     */
    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 integer $exitCode The command exit code
     */
    public function setExitCode($exitCode)
    {
        $this->exitCode = $exitCode;
    }

    /**
     * Gets the exit code.
     *
     * @return integer 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\Formatter;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 *
 * @api
 */
class OutputFormatter implements OutputFormatterInterface
{
    /**
     * The pattern to phrase the format.
     */
    const FORMAT_PATTERN = '#(\\\\?)<(/?)([a-z][a-z0-9_=;-]+)?>((?: [^<\\\\]+ | (?!<(?:/?[a-z]|/>)). | .(?<=\\\\<) )*)#isx';

    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)
    {
        return preg_replace('/([^\\\\]?)</is', '$1\\<', $text);
    }

    /**
     * Initializes console output formatter.
     *
     * @param Boolean          $decorated Whether this formatter should actually decorate strings
     * @param FormatterStyle[] $styles    Array of "name => FormatterStyle" instances
     *
     * @api
     */
    public function __construct($decorated = null, array $styles = array())
    {
        $this->decorated = (Boolean) $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 Boolean $decorated Whether to decorate the messages or not
     *
     * @api
     */
    public function setDecorated($decorated)
    {
        $this->decorated = (Boolean) $decorated;
    }

    /**
     * Gets the decorated flag.
     *
     * @return Boolean true if the output will decorate messages, false otherwise
     *
     * @api
     */
    public function isDecorated()
    {
        return $this->decorated;
    }

    /**
     * Sets a new style.
     *
     * @param string                        $name  The style name
     * @param OutputFormatterStyleInterface $style The style instance
     *
     * @api
     */
    public function setStyle($name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    /**
     * Checks if output formatter has style with specified name.
     *
     * @param string $name
     *
     * @return Boolean
     *
     * @api
     */
    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
     *
     * @api
     */
    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
     *
     * @api
     */
    public function format($message)
    {
        $message = preg_replace_callback(self::FORMAT_PATTERN, array($this, 'replaceStyle'), $message);

        return str_replace('\\<', '<', $message);
    }

    /**
     * @return OutputFormatterStyleStack
     */
    public function getStyleStack()
    {
        return $this->styleStack;
    }

    /**
     * Replaces style of the output.
     *
     * @param array $match
     *
     * @return string The replaced style
     */
    private function replaceStyle($match)
    {
        // we got "\<" escaped char
        if ('\\' === $match[1]) {
            return $this->applyCurrentStyle($match[0]);
        }

        if ('' === $match[3]) {
            if ('/' === $match[2]) {
                // we got "</>" tag
                $this->styleStack->pop();

                return $this->applyCurrentStyle($match[4]);
            }

            // we got "<>" tag
            return '<>'.$this->applyCurrentStyle($match[4]);
        }

        if (isset($this->styles[strtolower($match[3])])) {
            $style = $this->styles[strtolower($match[3])];
        } else {
            $style = $this->createStyleFromString($match[3]);

            if (false === $style) {
                return $this->applyCurrentStyle($match[0]);
            }
        }

        if ('/' === $match[2]) {
            $this->styleStack->pop($style);
        } else {
            $this->styleStack->push($style);
        }

        return $this->applyCurrentStyle($match[4]);
    }

    /**
     * Tries to create new style instance from string.
     *
     * @param string $string
     *
     * @return OutputFormatterStyle|Boolean false if string is not format string
     */
    private function createStyleFromString($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 {
                $style->setOption($match[1]);
            }
        }

        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>
 *
 * @api
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @param Boolean $decorated Whether to decorate the messages or not
     *
     * @api
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return Boolean true if the output will decorate messages, false otherwise
     *
     * @api
     */
    public function isDecorated();

    /**
     * Sets a new style.
     *
     * @param string                        $name  The style name
     * @param OutputFormatterStyleInterface $style The style instance
     *
     * @api
     */
    public function setStyle($name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     *
     * @param string $name
     *
     * @return Boolean
     *
     * @api
     */
    public function hasStyle($name);

    /**
     * Gets style options from style with specified name.
     *
     * @param string $name
     *
     * @return OutputFormatterStyleInterface
     *
     * @api
     */
    public function getStyle($name);

    /**
     * Formats a message according to the given styles.
     *
     * @param string $message The message to style
     *
     * @return string The styled message
     *
     * @api
     */
    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;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 *
 * @api
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static $availableForegroundColors = array(
        'black'     => 30,
        'red'       => 31,
        'green'     => 32,
        'yellow'    => 33,
        'blue'      => 34,
        'magenta'   => 35,
        'cyan'      => 36,
        'white'     => 37
    );
    private static $availableBackgroundColors = array(
        'black'     => 40,
        'red'       => 41,
        'green'     => 42,
        'yellow'    => 43,
        'blue'      => 44,
        'magenta'   => 45,
        'cyan'      => 46,
        'white'     => 47
    );
    private static $availableOptions = array(
        'bold'          => 1,
        'underscore'    => 4,
        'blink'         => 5,
        'reverse'       => 7,
        'conceal'       => 8
    );

    private $foreground;
    private $background;
    private $options = array();

    /**
     * Initializes output formatter style.
     *
     * @param string $foreground The style foreground color name
     * @param string $background The style background color name
     * @param array  $options    The style options
     *
     * @api
     */
    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 $color The color name
     *
     * @throws \InvalidArgumentException When the color name isn't defined
     *
     * @api
     */
    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 $color The color name
     *
     * @throws \InvalidArgumentException When the color name isn't defined
     *
     * @api
     */
    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
     *
     * @api
     */
    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 (false === array_search(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)
    {
        $codes = array();

        if (null !== $this->foreground) {
            $codes[] = $this->foreground;
        }
        if (null !== $this->background) {
            $codes[] = $this->background;
        }
        if (count($this->options)) {
            $codes = array_merge($codes, $this->options);
        }

        if (0 === count($codes)) {
            return $text;
        }

        return sprintf("\033[%sm%s\033[0m", implode(';', $codes), $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 style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 *
 * @api
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @param string $color The color name
     *
     * @api
     */
    public function setForeground($color = null);

    /**
     * Sets style background color.
     *
     * @param string $color The color name
     *
     * @api
     */
    public function setBackground($color = null);

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     *
     * @api
     */
    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;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private $styles;

    /**
     * @var OutputFormatterStyleInterface
     */
    private $emptyStyle;

    /**
     * Constructor.
     *
     * @param OutputFormatterStyleInterface $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 $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;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
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\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private $descriptors = 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.
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param string          $format
     * @param boolean         $raw
     */
    public function describe(OutputInterface $output, $object, $format = null, $raw = false, $namespace = null)
    {
        $options = array('raw_text' => $raw, 'format' => $format ?: 'txt', 'namespace' => $namespace);
        $type = !$raw && 'txt' === $options['format'] ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW;

        if (!isset($this->descriptors[$options['format']])) {
            throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];

        $output->writeln($descriptor->describe($object, $options), $type);
    }

    /**
     * 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\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * The Dialog class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DialogHelper extends Helper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    /**
     * Asks the user to select a value.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param array           $choices      List of choices to pick from
     * @param Boolean         $default      The default answer if the user enters nothing
     * @param Boolean|integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
     * @param string          $errorMessage Message which will be shown if invalid value from choice list would be picked
     * @param Boolean         $multiselect  Select more than one value separated by comma
     *
     * @return integer|string|array The selected value or values (the key of the choices array)
     *
     * @throws \InvalidArgumentException
     */
    public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        $width = max(array_map('strlen', array_keys($choices)));

        $messages = (array) $question;
        foreach ($choices as $key => $value) {
            $messages[] = sprintf("  [<info>%-${width}s</info>] %s", $key, $value);
        }

        $output->writeln($messages);

        $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {
            // Collapse all spaces.
            $selectedChoices = str_replace(" ", "", $picked);

            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, $picked));
                }
                $selectedChoices = explode(",", $selectedChoices);
            } else {
                $selectedChoices = array($picked);
            }

            $multiselectChoices = array();

            foreach ($selectedChoices as $value) {
                if (empty($choices[$value])) {
                    throw new \InvalidArgumentException(sprintf($errorMessage, $value));
                }
                array_push($multiselectChoices, $value);
            }

            if ($multiselect){
                return $multiselectChoices;
            } 
            
            return $picked;
        }, $attempts, $default);

        return $result;
    }

    /**
     * Asks a question to the user.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return string The user answer
     *
     * @throws \RuntimeException If there is no data to read in the input stream
     */
    public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
    {
        $output->write($question);

        $inputStream = $this->inputStream ?: STDIN;

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = fgets($inputStream, 4096);
            if (false === $ret) {
                throw new \RuntimeException('Aborted');
            }
            $ret = trim($ret);
        } else {
            $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 ('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 strlen($ret) > 0 ? $ret : $default;
    }

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $question The question to ask
     * @param Boolean         $default  The default answer if the user enters nothing
     *
     * @return Boolean true if the user has confirmed, false otherwise
     */
    public function askConfirmation(OutputInterface $output, $question, $default = true)
    {
        $answer = 'z';
        while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
            $answer = $this->ask($output, $question);
        }

        if (false === $default) {
            return $answer && 'y' == strtolower($answer[0]);
        }

        return !$answer || 'y' == strtolower($answer[0]);
    }

    /**
     * Asks a question to the user, the response is hidden
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $question The question
     * @param Boolean         $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string         The answer
     *
     * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
     */
    public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $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;
            }

            $output->write($question);
            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->hasSttyAvailable()) {
            $output->write($question);

            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($this->inputStream ?: STDIN, 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()) {
            $output->write($question);
            $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;
        }

        if ($fallback) {
            return $this->ask($output, $question);
        }

        throw new \RuntimeException('Unable to hide the response');
    }

    /**
     * Asks for a value and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param callable        $validator    A PHP callback
     * @param integer         $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return mixed
     *
     * @throws \Exception When any of the validators return an error
     */
    public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null)
    {
        $that = $this;

        $interviewer = function() use ($output, $question, $default, $autocomplete, $that) {
            return $that->ask($output, $question, $default, $autocomplete);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * Asks for a value, hide and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param OutputInterface $output    An Output instance
     * @param string|array    $question  The question to ask
     * @param callable        $validator A PHP callback
     * @param integer         $attempts  Max number of times to ask before giving up (false by default, which means infinite)
     * @param Boolean         $fallback  In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string         The response
     *
     * @throws \Exception        When any of the validators return an error
     * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
     *
     */
    public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
    {
        $that = $this;

        $interviewer = function() use ($output, $question, $fallback, $that) {
            return $that->askHiddenResponse($output, $question, $fallback);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     */
    public function setInputStream($stream)
    {
        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream
     *
     * @return string
     */
    public function getInputStream()
    {
        return $this->inputStream;
    }

    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return 'dialog';
    }

    /**
     * Return a valid unix shell
     *
     * @return string|Boolean  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;
    }

    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = $exitcode === 0;
    }

    /**
     * Validate an attempt
     *
     * @param callable         $interviewer  A callable that will ask for a question and return the result
     * @param OutputInterface  $output       An Output instance
     * @param callable         $validator    A PHP callback
     * @param integer          $attempts     Max number of times to ask before giving up ; false will ask infinitely
     *
     * @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($interviewer, OutputInterface $output, $validator, $attempts)
    {
        $error = null;
        while (false === $attempts || $attempts--) {
            if (null !== $error) {
                $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
            }

            try {
                return call_user_func($validator, $interviewer());
            } catch (\Exception $error) {
            }
        }

        throw $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\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 Boolean      $large    Whether to return a large block
     *
     * @return string The formatter message
     */
    public function formatBlock($messages, $style, $large = false)
    {
        $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();
        foreach ($lines as $line) {
            $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        foreach ($messages as &$message) {
            $message = sprintf('<%s>%s</%s>', $style, $message, $style);
        }

        return implode("\n", $messages);
    }

    /**
     * {@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;

/**
 * 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_strlen if it is available.
     *
     * @param string $string The string to check its length
     *
     * @return integer The length of the string
     */
    protected function strlen($string)
    {
        if (!function_exists('mb_strlen')) {
            return strlen($string);
        }

        if (false === $encoding = mb_detect_encoding($string)) {
            return strlen($string);
        }

        return mb_strlen($string, $encoding);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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>
 *
 * @api
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     *
     * @param HelperSet $helperSet A HelperSet instance
     *
     * @api
     */
    public function setHelperSet(HelperSet $helperSet = null);

    /**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet A HelperSet instance
     *
     * @api
     */
    public function getHelperSet();

    /**
     * Returns the canonical name of this helper.
     *
     * @return string The canonical name
     *
     * @api
     */
    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;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelperSet
{
    private $helpers;
    private $command;

    /**
     * Constructor.
     *
     * @param Helper[] $helpers An array of helper.
     */
    public function __construct(array $helpers = array())
    {
        $this->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 Boolean 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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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;

/**
 * The Progress class provides helpers to display progress output.
 *
 * @author Chris Jones <leeked@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ProgressHelper extends Helper
{
    const FORMAT_QUIET         = ' %percent%%';
    const FORMAT_NORMAL        = ' %current%/%max% [%bar%] %percent%%';
    const FORMAT_VERBOSE       = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
    const FORMAT_QUIET_NOMAX   = ' %current%';
    const FORMAT_NORMAL_NOMAX  = ' %current% [%bar%]';
    const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';

    // options
    private $barWidth     = 28;
    private $barChar      = '=';
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format       = null;
    private $redrawFreq   = 1;

    private $lastMessagesLength;
    private $barCharOriginal;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Current step
     *
     * @var integer
     */
    private $current;

    /**
     * Maximum number of steps
     *
     * @var integer
     */
    private $max;

    /**
     * Start time of the progress bar
     *
     * @var integer
     */
    private $startTime;

    /**
     * List of formatting variables
     *
     * @var array
     */
    private $defaultFormatVars = array(
        'current',
        'max',
        'bar',
        'percent',
        'elapsed',
    );

    /**
     * Available formatting variables
     *
     * @var array
     */
    private $formatVars;

    /**
     * Stored format part widths (used for padding)
     *
     * @var array
     */
    private $widths = array(
        'current' => 4,
        'max'     => 4,
        'percent' => 3,
        'elapsed' => 6,
    );

    /**
     * Various time formats
     *
     * @var array
     */
    private $timeFormats = array(
        array(0, '???'),
        array(2, '1 sec'),
        array(59, 'secs', 1),
        array(60, '1 min'),
        array(3600, 'mins', 60),
        array(5400, '1 hr'),
        array(86400, 'hrs', 3600),
        array(129600, '1 day'),
        array(604800, 'days', 86400),
    );

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = (int) $size;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int $freq The frequency in seconds
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = (int) $freq;
    }

    /**
     * Starts the progress output.
     *
     * @param OutputInterface $output An Output instance
     * @param integer         $max    Maximum steps
     */
    public function start(OutputInterface $output, $max = null)
    {
        $this->startTime = time();
        $this->current   = 0;
        $this->max       = (int) $max;
        $this->output    = $output;

        if (null === $this->format) {
            switch ($output->getVerbosity()) {
                case OutputInterface::VERBOSITY_QUIET:
                    $this->format = self::FORMAT_QUIET_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_QUIET;
                    }
                    break;
                case OutputInterface::VERBOSITY_VERBOSE:
                case OutputInterface::VERBOSITY_VERY_VERBOSE:
                case OutputInterface::VERBOSITY_DEBUG:
                    $this->format = self::FORMAT_VERBOSE_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_VERBOSE;
                    }
                    break;
                default:
                    $this->format = self::FORMAT_NORMAL_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_NORMAL;
                    }
                    break;
            }
        }

        $this->initialize();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param integer $step   Number of steps to advance
     * @param Boolean $redraw Whether to redraw or not
     *
     * @throws \LogicException
     */
    public function advance($step = 1, $redraw = false)
    {
        if (null === $this->startTime) {
            throw new \LogicException('You must start the progress bar before calling advance().');
        }

        if (0 === $this->current) {
            $redraw = true;
        }

        $this->current += $step;
        if ($redraw || 0 === $this->current % $this->redrawFreq) {
            $this->display();
        }
    }

    /**
     * Sets the current progress.
     *
     * @param integer $current The current progress
     * @param Boolean $redraw  Whether to redraw or not
     *
     * @throws \LogicException
     */
    public function setCurrent($current, $redraw = false)
    {
        if (null === $this->startTime) {
            throw new \LogicException('You must start the progress bar before calling setCurrent().');
        }

        $current = (int) $current;

        if ($current < $this->current) {
            throw new \LogicException('You can\'t regress the progress bar');
        }

        if (0 === $this->current) {
            $redraw = true;
        }

        $this->current = $current;
        if ($redraw || 0 === $this->current % $this->redrawFreq) {
            $this->display();
        }
    }

    /**
     * Outputs the current progress string.
     *
     * @param Boolean $finish Forces the end result
     *
     * @throws \LogicException
     */
    public function display($finish = false)
    {
        if (null === $this->startTime) {
            throw new \LogicException('You must start the progress bar before calling display().');
        }

        $message = $this->format;
        foreach ($this->generate($finish) as $name => $value) {
            $message = str_replace("%{$name}%", $value, $message);
        }
        $this->overwrite($this->output, $message);
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        if (null === $this->startTime) {
            throw new \LogicException('You must start the progress bar before calling finish().');
        }

        if (null !== $this->startTime) {
            if (!$this->max) {
                $this->barChar = $this->barCharOriginal;
                $this->display(true);
            }
            $this->startTime = null;
            $this->output->writeln('');
            $this->output = null;
        }
    }

    /**
     * Initializes the progress helper.
     */
    private function initialize()
    {
        $this->formatVars = array();
        foreach ($this->defaultFormatVars as $var) {
            if (false !== strpos($this->format, "%{$var}%")) {
                $this->formatVars[$var] = true;
            }
        }

        if ($this->max > 0) {
            $this->widths['max']     = $this->strlen($this->max);
            $this->widths['current'] = $this->widths['max'];
        } else {
            $this->barCharOriginal = $this->barChar;
            $this->barChar         = $this->emptyBarChar;
        }
    }

    /**
     * Generates the array map of format variables to values.
     *
     * @param Boolean $finish Forces the end result
     *
     * @return array Array of format vars and values
     */
    private function generate($finish = false)
    {
        $vars    = array();
        $percent = 0;
        if ($this->max > 0) {
            $percent = (double) round($this->current / $this->max, 2);
        }

        if (isset($this->formatVars['bar'])) {
            $completeBars = 0;
            $emptyBars = 0;
            if ($this->max > 0) {
                $completeBars = floor($percent * $this->barWidth);
            } else {
                if (!$finish) {
                    $completeBars = floor($this->current % $this->barWidth);
                } else {
                    $completeBars = $this->barWidth;
                }
            }

            $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar);
            $bar = str_repeat($this->barChar, $completeBars);
            if ($completeBars < $this->barWidth) {
                $bar .= $this->progressChar;
                $bar .= str_repeat($this->emptyBarChar, $emptyBars);
            }

            $vars['bar'] = $bar;
        }

        if (isset($this->formatVars['elapsed'])) {
            $elapsed = time() - $this->startTime;
            $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['current'])) {
            $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['max'])) {
            $vars['max'] = $this->max;
        }

        if (isset($this->formatVars['percent'])) {
            $vars['percent'] = str_pad($percent * 100, $this->widths['percent'], ' ', STR_PAD_LEFT);
        }

        return $vars;
    }

    /**
     * Converts seconds into human-readable format.
     *
     * @param integer $secs Number of seconds
     *
     * @return string Time in readable format
     */
    private function humaneTime($secs)
    {
        $text = '';
        foreach ($this->timeFormats as $format) {
            if ($secs < $format[0]) {
                if (count($format) == 2) {
                    $text = $format[1];
                    break;
                } else {
                    $text = ceil($secs / $format[2]).' '.$format[1];
                    break;
                }
            }
        }

        return $text;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param OutputInterface $output   An Output instance
     * @param string          $message  The message
     */
    private function overwrite(OutputInterface $output, $message)
    {
        $length = $this->strlen($message);

        // append whitespace to match the last line's length
        if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
            $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
        }

        // carriage return
        $output->write("\x0D");
        $output->write($message);

        $this->lastMessagesLength = $this->strlen($message);
    }

    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return 'progress';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 InvalidArgumentException;

/**
 * Provides helpers to display table output.
 *
 * @author Саша Стаменковић <umpirsky@gmail.com>
 */
class TableHelper extends Helper
{
    const LAYOUT_DEFAULT = 0;
    const LAYOUT_BORDERLESS = 1;

    /**
     * Table headers.
     *
     * @var array
     */
    private $headers = array();

    /**
     * Table rows.
     *
     * @var array
     */
    private $rows = array();

    // Rendering options
    private $paddingChar;
    private $horizontalBorderChar;
    private $verticalBorderChar;
    private $crossingChar;
    private $cellHeaderFormat;
    private $cellRowFormat;
    private $borderFormat;
    private $padType;

    /**
     * Column widths cache.
     *
     * @var array
     */
    private $columnWidths = array();

    /**
     * Number of columns cache.
     *
     * @var array
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    public function __construct()
    {
        $this->setLayout(self::LAYOUT_DEFAULT);
    }

    /**
     * Sets table layout type.
     *
     * @param int $layout self::LAYOUT_*
     *
     * @return TableHelper
     */
    public function setLayout($layout)
    {
        switch ($layout) {
            case self::LAYOUT_BORDERLESS:
                $this
                    ->setPaddingChar(' ')
                    ->setHorizontalBorderChar('=')
                    ->setVerticalBorderChar(' ')
                    ->setCrossingChar(' ')
                    ->setCellHeaderFormat('<info>%s</info>')
                    ->setCellRowFormat('<comment>%s</comment>')
                    ->setBorderFormat('%s')
                    ->setPadType(STR_PAD_RIGHT)
                ;
                break;

            case self::LAYOUT_DEFAULT:
                $this
                    ->setPaddingChar(' ')
                    ->setHorizontalBorderChar('-')
                    ->setVerticalBorderChar('|')
                    ->setCrossingChar('+')
                    ->setCellHeaderFormat('<info>%s</info>')
                    ->setCellRowFormat('<comment>%s</comment>')
                    ->setBorderFormat('%s')
                    ->setPadType(STR_PAD_RIGHT)
                ;
                break;

            default:
                throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout));
                break;
        };

        return $this;
    }

    public function setHeaders(array $headers)
    {
        $this->headers = array_values($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(array $row)
    {
        $this->rows[] = array_values($row);

        return $this;
    }

    public function setRow($column, array $row)
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return TableHelper
     */
    public function setPaddingChar($paddingChar)
    {
        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return TableHelper
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->horizontalBorderChar = $horizontalBorderChar;

        return $this;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return TableHelper
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->verticalBorderChar = $verticalBorderChar;

        return $this;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return TableHelper
     */
    public function setCrossingChar($crossingChar)
    {
        $this->crossingChar = $crossingChar;

        return $this;
    }

    /**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return TableHelper
     */
    public function setCellHeaderFormat($cellHeaderFormat)
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return TableHelper
     */
    public function setCellRowFormat($cellRowFormat)
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return TableHelper
     */
    public function setBorderFormat($borderFormat)
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Sets cell padding type.
     *
     * @param integer $padType STR_PAD_*
     *
     * @return TableHelper
     */
    public function setPadType($padType)
    {
        $this->padType = $padType;

        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 |
     * +---------------+-----------------------+------------------+
     *
     * @param OutputInterface $output
     */
    public function render(OutputInterface $output)
    {
        $this->output = $output;

        $this->renderRowSeparator();
        $this->renderRow($this->headers, $this->cellHeaderFormat);
        if (!empty($this->headers)) {
            $this->renderRowSeparator();
        }
        foreach ($this->rows as $row) {
            $this->renderRow($row, $this->cellRowFormat);
        }
        if (!empty($this->rows)) {
            $this->renderRowSeparator();
        }

        $this->cleanup();
    }

    /**
     * Renders horizontal header separator.
     *
     * Example: +-----+-----------+-------+
     */
    private function renderRowSeparator()
    {
        if (0 === $count = $this->getNumberOfColumns()) {
            return;
        }

        $markup = $this->crossingChar;
        for ($column = 0; $column < $count; $column++) {
            $markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column))
                    .$this->crossingChar
            ;
        }

        $this->output->writeln(sprintf($this->borderFormat, $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator()
    {
        $this->output->write(sprintf($this->borderFormat, $this->verticalBorderChar));
    }

    /**
     * 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;
        }

        $this->renderColumnSeparator();
        for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) {
            $this->renderCell($row, $column, $cellFormat);
            $this->renderColumnSeparator();
        }
        $this->output->writeln('');
    }

    /**
     * Renders table cell with padding.
     *
     * @param array   $row
     * @param integer $column
     * @param string  $cellFormat
     */
    private function renderCell(array $row, $column, $cellFormat)
    {
        $cell = isset($row[$column]) ? $row[$column] : '';

        $this->output->write(sprintf(
            $cellFormat,
            str_pad(
                $this->paddingChar.$cell.$this->paddingChar,
                $this->getColumnWidth($column),
                $this->paddingChar,
                $this->padType
            )
        ));
    }

    /**
     * Gets number of columns for this table.
     *
     * @return int
     */
    private function getNumberOfColumns()
    {
        if (null !== $this->numberOfColumns) {
            return $this->numberOfColumns;
        }

        $columns = array(0);
        $columns[] = count($this->headers);
        foreach ($this->rows as $row) {
            $columns[] = count($row);
        }

        return $this->numberOfColumns = max($columns);
    }

    /**
     * Gets column width.
     *
     * @param integer $column
     *
     * @return int
     */
    private function getColumnWidth($column)
    {
        if (isset($this->columnWidths[$column])) {
            return $this->columnWidths[$column];
        }

        $lengths = array(0);
        $lengths[] = $this->getCellWidth($this->headers, $column);
        foreach ($this->rows as $row) {
            $lengths[] = $this->getCellWidth($row, $column);
        }

        return $this->columnWidths[$column] = max($lengths) + 2;
    }

    /**
     * Gets cell width.
     *
     * @param array   $row
     * @param integer $column
     *
     * @return int
     */
    private function getCellWidth(array $row, $column)
    {
        if ($column < 0) {
            return 0;
        }

        if (isset($row[$column])) {
            return $this->strlen($row[$column]);
        }

        return $this->getCellWidth($row, $column - 1);
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->columnWidths = array();
        $this->numberOfColumns = null;
    }

    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return 'table';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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;

/**
 * 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
 *
 * @api
 */
class ArgvInput extends Input
{
    private $tokens;
    private $parsed;

    /**
     * Constructor.
     *
     * @param array           $argv       An array of parameters from the CLI (in the argv format)
     * @param InputDefinition $definition A InputDefinition instance
     *
     * @api
     */
    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;
    }

    /**
     * Processes command line arguments.
     */
    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]) {
                $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(), true);
            }
        }
    }

    /**
     * Parses a long option.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
        } 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 {
            throw new \RuntimeException('Too many arguments.');
        }
    }

    /**
     * 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 false values (from a previous call to substr()) to null
        if (false === $value) {
            $value = null;
        }

        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 = '';
            } 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;
        }
    }

    /**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string The value of the first argument or null otherwise
     */
    public function getFirstArgument()
    {
        foreach ($this->tokens as $token) {
            if ($token && '-' === $token[0]) {
                continue;
            }

            return $token;
        }
    }

    /**
     * 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 value(s) to look for in the raw parameters (can be an array)
     *
     * @return Boolean true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->tokens as $v) {
            if (in_array($v, $values)) {
                return true;
            }
        }

        return 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
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;

        $tokens = $this->tokens;
        while ($token = array_shift($tokens)) {
            foreach ($values as $value) {
                if (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()
    {
        $self = $this;
        $tokens = array_map(function ($token) use ($self) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1] . $self->escapeToken($match[2]);
            }

            if ($token && $token[0] !== '-') {
                return $self->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;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
class ArrayInput extends Input
{
    private $parameters;

    /**
     * Constructor.
     *
     * @param array           $parameters An array of parameters
     * @param InputDefinition $definition A InputDefinition instance
     *
     * @api
     */
    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string The value of the first argument or null otherwise
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $key => $value) {
            if ($key && '-' === $key[0]) {
                continue;
            }

            return $value;
        }
    }

    /**
     * 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)
     *
     * @return Boolean true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!is_int($k)) {
                $v = $k;
            }

            if (in_array($v, $values)) {
                return true;
            }
        }

        return 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
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (is_int($k) && 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);
    }

    /**
     * Processes command line arguments.
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            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 \InvalidArgumentException When option given doesn't exist
     */
    private function addShortOption($shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new \InvalidArgumentException(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 \InvalidArgumentException When option given doesn't exist
     * @throws \InvalidArgumentException When a required value is missing
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new \InvalidArgumentException(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;

/**
 * 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
{
    protected $definition;
    protected $options;
    protected $arguments;
    protected $interactive = true;

    /**
     * Constructor.
     *
     * @param InputDefinition $definition A InputDefinition instance
     */
    public function __construct(InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->arguments = array();
            $this->options = array();
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @param InputDefinition $definition A InputDefinition instance
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = array();
        $this->options = array();
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     */
    abstract protected function parse();

    /**
     * Validates the input.
     *
     * @throws \RuntimeException When not enough arguments are given
     */
    public function validate()
    {
        if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
            throw new \RuntimeException('Not enough arguments.');
        }
    }

    /**
     * Checks if the input is interactive.
     *
     * @return Boolean Returns true if the input is interactive
     */
    public function isInteractive()
    {
        return $this->interactive;
    }

    /**
     * Sets the input interactivity.
     *
     * @param Boolean $interactive If the input should be interactive
     */
    public function setInteractive($interactive)
    {
        $this->interactive = (Boolean) $interactive;
    }

    /**
     * Returns the argument values.
     *
     * @return array An array of argument values
     */
    public function getArguments()
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    /**
     * 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)
    {
        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();
    }

    /**
     * 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)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|integer $name The InputArgument name or position
     *
     * @return Boolean true if the InputArgument object exists, false otherwise
     */
    public function hasArgument($name)
    {
        return $this->definition->hasArgument($name);
    }

    /**
     * Returns the options values.
     *
     * @return array An array of option values
     */
    public function getOptions()
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    /**
     * 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)
    {
        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();
    }

    /**
     * Sets an option value by name.
     *
     * @param string $name  The option name
     * @param string $value The option value
     *
     * @throws \InvalidArgumentException When option given doesn't exist
     */
    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;
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * @param string $name The InputOption name
     *
     * @return Boolean true if the InputOption object exists, false otherwise
     */
    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;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
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 integer $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
     *
     * @api
     */
    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 Boolean 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 Boolean 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;

use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;

/**
 * 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>
 *
 * @api
 */
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
     *
     * @api
     */
    public function __construct(array $definition = array())
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     *
     * @param array $definition The definition array
     *
     * @api
     */
    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
     *
     * @api
     */
    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
     *
     * @api
     */
    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
     *
     * @api
     */
    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|integer $name The InputArgument name or position
     *
     * @return InputArgument An InputArgument object
     *
     * @throws \InvalidArgumentException When argument given doesn't exist
     *
     * @api
     */
    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|integer $name The InputArgument name or position
     *
     * @return Boolean true if the InputArgument object exists, false otherwise
     *
     * @api
     */
    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
     *
     * @api
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     *
     * @return integer The number of InputArguments
     */
    public function getArgumentCount()
    {
        return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     *
     * @return integer 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
     *
     * @api
     */
    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
     *
     * @api
     */
    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
     *
     * @api
     */
    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
     *
     * @api
     */
    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.
     *
     * @param string $name The InputOption name
     *
     * @return Boolean true if the InputOption object exists, false otherwise
     *
     * @api
     */
    public function hasOption($name)
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[] An array of InputOption objects
     *
     * @api
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     *
     * @param string $name The InputOption shortcut
     *
     * @return Boolean 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.
     *
     * @return string The synopsis
     */
    public function getSynopsis()
    {
        $elements = array();
        foreach ($this->getOptions() as $option) {
            $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
            $elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
        }

        foreach ($this->getArguments() as $argument) {
            $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));

            if ($argument->isArray()) {
                $elements[] = sprintf('... [%sN]', $argument->getName());
            }
        }

        return implode(' ', $elements);
    }

    /**
     * Returns a textual representation of the InputDefinition.
     *
     * @return string A string representing the InputDefinition
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        $descriptor = new TextDescriptor();

        return $descriptor->describe($this);
    }

    /**
     * Returns an XML representation of the InputDefinition.
     *
     * @param Boolean $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the InputDefinition
     *
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        $descriptor = new XmlDescriptor();

        return $descriptor->describe($this, array('as_dom' => $asDom));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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;

/**
 * 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)
     *
     * @return Boolean true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values);

    /**
     * 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
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @param InputDefinition $definition A InputDefinition instance
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates if arguments given are correct.
     *
     * Throws an exception when not enough arguments are given.
     *
     * @throws \RuntimeException
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array
     */
    public function getArguments();

    /**
     * Gets argument by name.
     *
     * @param string $name The name of the argument
     *
     * @return mixed
     */
    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|integer $name The InputArgument name or position
     *
     * @return Boolean 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();

    /**
     * Gets an option by name.
     *
     * @param string $name The name of the option
     *
     * @return mixed
     */
    public function getOption($name);

    /**
     * Sets an option value by name.
     *
     * @param string $name  The option name
     * @param string $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 Boolean true if the InputOption object exists, false otherwise
     */
    public function hasOption($name);

    /**
     * Is this input means interactive?
     *
     * @return Boolean
     */
    public function isInteractive();

    /**
     * Sets the input interactivity.
     *
     * @param Boolean $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;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
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 integer      $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_REQUIRED or self::VALUE_NONE)
     *
     * @throws \InvalidArgumentException If option mode is invalid or incompatible
     *
     * @api
     */
    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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean
     */
    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;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
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)
     * @param InputDefinition $definition A InputDefinition instance
     *
     * @deprecated The second argument is deprecated as it does not work (will be removed in 3.0), use 'bind' method instead
     *
     * @api
     */
    public function __construct($input, InputDefinition $definition = null)
    {
        parent::__construct(array(), null);

        $this->setTokens($this->tokenize($input));

        if (null !== $definition) {
            $this->bind($definition);
        }
    }

    /**
     * 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
                // @codeCoverageIgnoreStart
                throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
                // @codeCoverageIgnoreEnd
            }

            $cursor += strlen($match[0]);
        }

        return $tokens;
    }
}
Copyright (c) 2004-2013 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT.
 *
 * This class is a convenient wrapper around `StreamOutput`.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @api
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private $stderr;

    /**
     * Constructor.
     *
     * @param integer                       $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param Boolean|null                  $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @api
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
    {
        $outputStream = 'php://stdout';
        if (!$this->hasStdoutSupport()) {
            $outputStream = 'php://output';
        }

        parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter);

        $this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $formatter);
    }

    /**
     * {@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.
     *
     * IBM iSeries (OS400) exhibits character-encoding issues when writing to
     * STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage
     * output.
     *
     * @return boolean
     */
    protected function hasStdoutSupport()
    {
        return ('OS400' != php_uname('s'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Output\OutputInterface;

/**
 * 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>
 *
 * @api
 */
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 writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = 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>
 *
 * @api
 */
abstract class Output implements OutputInterface
{
    private $verbosity;
    private $formatter;

    /**
     * Constructor.
     *
     * @param integer                       $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param Boolean                       $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @api
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
        $this->formatter = null === $formatter ? new OutputFormatter() : $formatter;
        $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 writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        if (self::VERBOSITY_QUIET === $this->verbosity) {
            return;
        }

        $messages = (array) $messages;

        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;
                default:
                    throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
            }

            $this->doWrite($message, $newline);
        }
    }

    /**
     * Writes a message to the output.
     *
     * @param string  $message A message to write to the output
     * @param Boolean $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>
 *
 * @api
 */
interface OutputInterface
{
    const VERBOSITY_QUIET        = 0;
    const VERBOSITY_NORMAL       = 1;
    const VERBOSITY_VERBOSE      = 2;
    const VERBOSITY_VERY_VERBOSE = 3;
    const VERBOSITY_DEBUG        = 4;

    const OUTPUT_NORMAL = 0;
    const OUTPUT_RAW    = 1;
    const OUTPUT_PLAIN  = 2;

    /**
     * Writes a message to the output.
     *
     * @param string|array $messages The message as an array of lines or a single string
     * @param Boolean      $newline  Whether to add a newline
     * @param integer      $type     The type of output (one of the OUTPUT constants)
     *
     * @throws \InvalidArgumentException When unknown output type is given
     *
     * @api
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL);

    /**
     * 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 integer      $type     The type of output (one of the OUTPUT constants)
     *
     * @throws \InvalidArgumentException When unknown output type is given
     *
     * @api
     */
    public function writeln($messages, $type = self::OUTPUT_NORMAL);

    /**
     * Sets the verbosity of the output.
     *
     * @param integer $level The level of verbosity (one of the VERBOSITY constants)
     *
     * @api
     */
    public function setVerbosity($level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return integer The current level of verbosity (one of the VERBOSITY constants)
     *
     * @api
     */
    public function getVerbosity();

    /**
     * Sets the decorated flag.
     *
     * @param Boolean $decorated Whether to decorate the messages
     *
     * @api
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return Boolean true if the output will decorate messages, false otherwise
     *
     * @api
     */
    public function isDecorated();

    /**
     * Sets output formatter.
     *
     * @param OutputFormatterInterface $formatter
     *
     * @api
     */
    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     *
     * @return  OutputFormatterInterface
     *
     * @api
     */
    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\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>
 *
 * @api
 */
class StreamOutput extends Output
{
    private $stream;

    /**
     * Constructor.
     *
     * @param mixed                         $stream    A stream resource
     * @param integer                       $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param Boolean|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
     *
     * @api
     */
    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 ? PHP_EOL : ''))) {
            // @codeCoverageIgnoreStart
            // should never happen
            throw new \RuntimeException('Unable to write output.');
            // @codeCoverageIgnoreEnd
        }

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     *  -  windows without ansicon and ConEmu
     *  -  non tty consoles
     *
     * @return Boolean true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport()
    {
        // @codeCoverageIgnoreStart
        if (DIRECTORY_SEPARATOR == '\\') {
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
        }

        return function_exists('posix_isatty') && @posix_isatty($this->stream);
        // @codeCoverageIgnoreEnd
    }
}
Console Component
=================

Console eases the creation of beautiful and testable command line interfaces.

The Application object manages the CLI application:

    use Symfony\Component\Console\Application;

    $console = new Application();
    $console->run();

The ``run()`` method parses the arguments and options passed on the command
line and executes the right command.

Registering a new command can easily be done via the ``register()`` method,
which returns a ``Command`` instance:

    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Input\InputArgument;
    use Symfony\Component\Console\Input\InputOption;
    use Symfony\Component\Console\Output\OutputInterface;

    $console
        ->register('ls')
        ->setDefinition(array(
            new InputArgument('dir', InputArgument::REQUIRED, 'Directory name'),
        ))
        ->setDescription('Displays the files in the given directory')
        ->setCode(function (InputInterface $input, OutputInterface $output) {
            $dir = $input->getArgument('dir');

            $output->writeln(sprintf('Dir listing for <info>%s</info>', $dir));
        })
    ;

You can also register new commands via classes.

The component provides a lot of features like output coloring, input and
output abstractions (so that you can easily unit-test your commands),
validation, automatic help messages, ...

Tests
-----

You can run the unit tests with the following command:

    $ cd path/to/Symfony/Component/Console/
    $ composer.phar install --dev
    $ phpunit

Third Party
-----------

`Resources/bin/hiddeninput.exe` is a third party binary provided within this
component. Find sources and license at https://github.com/Seldaek/hidden-input.

Resources
---------

[The Console Component](http://symfony.com/doc/current/components/console.html)

[How to create a Console Command](http://symfony.com/doc/current/cookbook/console/console_command.html)
MZ                @                                       	!L!This program cannot be run in DOS mode.
$       ,;B;B;B2מ:B2-B2ƞ9B2ў?Ba98B;CB2Ȟ:B2֞:B2Ӟ:BRich;B        PE  L MoO         	  
         8           @                      `     ?   @                           "  P    @                      P  p   !                             8!  @                                          .text   	      
                    `.rdata  	       
                 @  @.data      0                    @  .rsrc       @                    @  @.reloc     P      "              @  B                                                                                                                                                                                                                                                                                                                                                        j$@ x  j @ e EPV  @ EЃPV @ MX @ e EP5H @ L @ YY5\ @ EP5` @ D @ YYP @ MMT @ 3H  ; 0@ u  h@   l3@ $40@ 5h3@ 40@ h$0@ h(0@ h 0@  @ 00@ }j  Yjh"@   3ۉ]d   p]俀3@ SVW0 @ ;t;u3Fuh  4 @ 3F|3@ ;u
j\  Y;|3@ u,5|3@ h @ h @   YYtE      5<0@ |3@ ;uh @ h @ l  YY|3@    9]uSW8 @ 93@ th3@   Yt
SjS3@ $0@  @ 5$0@ 5(0@ 5 0@ 80@ 9,0@ u7P @ E	MPQ  YYËeE80@ 39,0@ uPh @ 9<0@ u @ E80@   øMZ  f9  @ t3M< @   @ 8PE  uH  t  uՃ   v39   xtv39   j,0@ p @ jl @ YY3@ 3@  @ t3@  @ p3@  @  x3@ V    =0@  uh@  @ Yg  =0@ u	j @ Y3{  U(  H1@ D1@ @1@ <1@ 581@ =41@ f`1@ fT1@ f01@ f,1@ f%(1@ f-$1@ X1@ E L1@ EP1@ E\1@ 0@   P1@ L0@ @0@ 	 D0@     0@ 0@  @ 0@ j?  Yj   @ h!@ $ @ =0@  uj  Yh	 ( @ P, @ ËUE 8csmu*xu$@= t=!t="t= @u  3] hH@   @ 3% @ jh("@ b  53@ 5 @ YEuu @ Ygj  Ye 53@ ։E53@ YYEEPEPu5l @ YPU  Eu֣3@ uփ3@ E	   E  j  YËUuNYH]ËV!@ !@ W;stЃ;r_^ËV"@ "@ W;stЃ;r_^% @ ̋UMMZ  f9t3]ËA<8PE  u3ҹ  f9H]̋UEH<ASVq3WDv}H;r	X;r
B(;r3_^[]̋UjhH"@ he@ d    PSVW 0@ 1E3PEd    eE    h  @ *tUE-  @ Ph  @ Pt;@$ЃEMd    Y_^[]ËE3=  ËeE3Md    Y_^[]% @ % @ he@ d5    D$l$l$+SVW 0@ 1E3PeuEEEEd    ËMd    Y__^[]QËUuuuuh@ h 0@    ]ËVh   h   3V   tVVVVV   ^3ËU 0@ e e SWN@  ;tt	У0@ `VEP< @ u3u @ 3 @ 3 @ 3EP @ E3E3;uO@u5 0@ ։50@ ^_[%t @ %x @ %| @ % @ % @ % @ % @ % @ % @ Pd5    D$+d$SVW( 0@ 3PEuEEd    ËMd    Y__^[]QËM3M%T @ T$BJ3J3l"@ s                                                                                                                                                                                                                                                     #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )          @         W@ @                     MoO       l   !    @0@ 0@ bad allocation      H                                                            0@ !@    RSDSьJ!LZ    c:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdb     e                            @ @                 :@             @ @ @ "   d"@                        "          #      $#          &  D   H#          (  h                       #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )      GetConsoleMode  SetConsoleMode  ;GetStdHandle  KERNEL32.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@A  J?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@@Z  MSVCP90.dll _amsg_exit   __getmainargs ,_cexit  |_exit f _XcptFilter exit   __initenv _initterm _initterm_e <_configthreadlocale  __setusermatherr  _adjust_fdiv   __p__commode   __p__fmode  j_encode_pointer  __set_app_type  K_crt_debugger_hook  C ?terminate@@YAXXZ MSVCR90.dll _unlock  __dllonexit v_lock _onexit `_decode_pointer s_except_handler4_common _invoke_watson  ?_controlfp_s  InterlockedExchange !Sleep InterlockedCompareExchange  -TerminateProcess  GetCurrentProcess >UnhandledExceptionFilter  SetUnhandledExceptionFilter IsDebuggerPresent TQueryPerformanceCounter fGetTickCount  GetCurrentThreadId  GetCurrentProcessId OGetSystemTimeAsFileTime s __CxxFrameHandler3                                                    N@D   $!@                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            8                   P                   h                	                   	     @  (        C  V        (4   V S _ V E R S I O N _ I N F O                                                  S t r i n g F i l e I n f o   b   0 4 0 9 0 4 b 0    Q  F i l e D e s c r i p t i o n     R e a d s   f r o m   s t d i n   w i t h o u t   l e a k i n g   i n f o   t o   t h e   t e r m i n a l   a n d   o u t p u t s   b a c k   t o   s t d o u t     6   F i l e V e r s i o n     1 ,   0 ,   0 ,   0     8   I n t e r n a l N a m e   h i d d e n i n p u t   P   L e g a l C o p y r i g h t   J o r d i   B o g g i a n o   -   2 0 1 2   H   O r i g i n a l F i l e n a m e   h i d d e n i n p u t . e x e   :   P r o d u c t N a m e     H i d d e n   I n p u t     :   P r o d u c t V e r s i o n   1 ,   0 ,   0 ,   0     D    V a r F i l e I n f o     $    T r a n s l a t i o n     	<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   @  00!0/080F0L0T0^0d0n0{000000000000001#1-1@1J1O1T1v1{1111111111111112"2*23292A2M2_2j2p222222222222333%303N3T3Z3`3f3l3s3z333333333333333334444%4;4B444444444445!5^5c5555H6M6_6}666 777*7w7|77777888=8E8P8V8\8b8h8n8t8z88889      $   0001 1t1x12 2@2\2`2h2t2 0     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;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\PhpExecutableFinder;

/**
 * A Shell wraps an Application to add shell capabilities to it.
 *
 * Support for history and completion only works with a PHP compiled
 * with readline support (either --with-readline or --with-libedit)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class Shell
{
    private $application;
    private $history;
    private $output;
    private $hasReadline;
    private $processIsolation;

    /**
     * Constructor.
     *
     * If there is no readline support for the current PHP executable
     * a \RuntimeException exception is thrown.
     *
     * @param Application $application An application instance
     */
    public function __construct(Application $application)
    {
        $this->hasReadline = function_exists('readline');
        $this->application = $application;
        $this->history = getenv('HOME').'/.history_'.$application->getName();
        $this->output = new ConsoleOutput();
        $this->processIsolation = false;
    }

    /**
     * Runs the shell.
     */
    public function run()
    {
        $this->application->setAutoExit(false);
        $this->application->setCatchExceptions(true);

        if ($this->hasReadline) {
            readline_read_history($this->history);
            readline_completion_function(array($this, 'autocompleter'));
        }

        $this->output->writeln($this->getHeader());
        $php = null;
        if ($this->processIsolation) {
            $finder = new PhpExecutableFinder();
            $php = $finder->find();
            $this->output->writeln(<<<EOF
<info>Running with process isolation, you should consider this:</info>
  * each command is executed as separate process,
  * commands don't support interactivity, all params must be passed explicitly,
  * commands output is not colorized.

EOF
            );
        }

        while (true) {
            $command = $this->readline();

            if (false === $command) {
                $this->output->writeln("\n");

                break;
            }

            if ($this->hasReadline) {
                readline_add_history($command);
                readline_write_history($this->history);
            }

            if ($this->processIsolation) {
                $pb = new ProcessBuilder();

                $process = $pb
                    ->add($php)
                    ->add($_SERVER['argv'][0])
                    ->add($command)
                    ->inheritEnvironmentVariables(true)
                    ->getProcess()
                ;

                $output = $this->output;
                $process->run(function($type, $data) use ($output) {
                    $output->writeln($data);
                });

                $ret = $process->getExitCode();
            } else {
                $ret = $this->application->run(new StringInput($command), $this->output);
            }

            if (0 !== $ret) {
                $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
            }
        }
    }

    /**
     * Returns the shell header.
     *
     * @return string The header string
     */
    protected function getHeader()
    {
        return <<<EOF

Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).

At the prompt, type <comment>help</comment> for some help,
or <comment>list</comment> to get a list of available commands.

To exit the shell, type <comment>^D</comment>.

EOF;
    }

    /**
     * Renders a prompt.
     *
     * @return string The prompt
     */
    protected function getPrompt()
    {
        // using the formatter here is required when using readline
        return $this->output->getFormatter()->format($this->application->getName().' > ');
    }

    protected function getOutput()
    {
        return $this->output;
    }

    protected function getApplication()
    {
        return $this->application;
    }

    /**
     * Tries to return autocompletion for the current entered text.
     *
     * @param string $text The last segment of the entered text
     *
     * @return Boolean|array A list of guessed strings or true
     */
    private function autocompleter($text)
    {
        $info = readline_info();
        $text = substr($info['line_buffer'], 0, $info['end']);

        if ($info['point'] !== $info['end']) {
            return true;
        }

        // task name?
        if (false === strpos($text, ' ') || !$text) {
            return array_keys($this->application->all());
        }

        // options and arguments?
        try {
            $command = $this->application->find(substr($text, 0, strpos($text, ' ')));
        } catch (\Exception $e) {
            return true;
        }

        $list = array('--help');
        foreach ($command->getDefinition()->getOptions() as $option) {
            $list[] = '--'.$option->getName();
        }

        return $list;
    }

    /**
     * Reads a single line from standard input.
     *
     * @return string The single line from standard input
     */
    private function readline()
    {
        if ($this->hasReadline) {
            $line = readline($this->getPrompt());
        } else {
            $this->output->write($this->getPrompt());
            $line = fgets(STDIN, 1024);
            $line = (!$line && strlen($line) == 0) ? false : rtrim($line);
        }

        return $line;
    }

    public function getProcessIsolation()
    {
        return $this->processIsolation;
    }

    public function setProcessIsolation($processIsolation)
    {
        $this->processIsolation = (Boolean) $processIsolation;

        if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) {
            throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.');
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    private $application;
    private $input;
    private $output;

    /**
     * Constructor.
     *
     * @param Application $application An Application instance to test.
     */
    public function __construct(Application $application)
    {
        $this->application = $application;
    }

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive: Sets the input interactive flag
     *  * decorated:   Sets the output decorated flag
     *  * verbosity:   Sets the output verbosity flag
     *
     * @param array $input   An array of arguments and options
     * @param array $options An array of options
     *
     * @return integer The command exit code
     */
    public function run(array $input, $options = array())
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->output = new StreamOutput(fopen('php://memory', 'w', false));
        if (isset($options['decorated'])) {
            $this->output->setDecorated($options['decorated']);
        }
        if (isset($options['verbosity'])) {
            $this->output->setVerbosity($options['verbosity']);
        }

        return $this->application->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the application.
     *
     * @param Boolean $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the application.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the application.
     *
     * @return OutputInterface The current output 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\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class CommandTester
{
    private $command;
    private $input;
    private $output;

    /**
     * Constructor.
     *
     * @param Command $command A Command instance to test.
     */
    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Executes the command.
     *
     * Available options:
     *
     *  * interactive: Sets the input interactive flag
     *  * decorated:   Sets the output decorated flag
     *  * verbosity:   Sets the output verbosity flag
     *
     * @param array $input   An array of arguments and options
     * @param array $options An array of options
     *
     * @return integer The command exit code
     */
    public function execute(array $input, array $options = array())
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->output = new StreamOutput(fopen('php://memory', 'w', false));
        if (isset($options['decorated'])) {
            $this->output->setDecorated($options['decorated']);
        }
        if (isset($options['verbosity'])) {
            $this->output->setVerbosity($options['verbosity']);
        }

        return $this->command->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the command.
     *
     * @param Boolean $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command.
     *
     * @return OutputInterface The current output 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\Tests;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\ApplicationTester;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;

class ApplicationTest extends \PHPUnit_Framework_TestCase
{
    protected static $fixturesPath;

    public static function setUpBeforeClass()
    {
        self::$fixturesPath = realpath(__DIR__.'/Fixtures/');
        require_once self::$fixturesPath.'/FooCommand.php';
        require_once self::$fixturesPath.'/Foo1Command.php';
        require_once self::$fixturesPath.'/Foo2Command.php';
        require_once self::$fixturesPath.'/Foo3Command.php';
        require_once self::$fixturesPath.'/Foo4Command.php';
    }

    protected function normalizeLineBreaks($text)
    {
        return str_replace(PHP_EOL, "\n", $text);
    }

    /**
     * Replaces the dynamic placeholders of the command help text with a static version.
     * The placeholder %command.full_name% includes the script path that is not predictable
     * and can not be tested against.
     */
    protected function ensureStaticCommandHelp(Application $application)
    {
        foreach ($application->all() as $command) {
            $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp()));
        }
    }

    public function testConstructor()
    {
        $application = new Application('foo', 'bar');
        $this->assertEquals('foo', $application->getName(), '__construct() takes the application name as its first argument');
        $this->assertEquals('bar', $application->getVersion(), '__construct() takes the application version as its first argument');
        $this->assertEquals(array('help', 'list'), array_keys($application->all()), '__construct() registered the help and list commands by default');
    }

    public function testSetGetName()
    {
        $application = new Application();
        $application->setName('foo');
        $this->assertEquals('foo', $application->getName(), '->setName() sets the name of the application');
    }

    public function testSetGetVersion()
    {
        $application = new Application();
        $application->setVersion('bar');
        $this->assertEquals('bar', $application->getVersion(), '->setVersion() sets the version of the application');
    }

    public function testGetLongVersion()
    {
        $application = new Application('foo', 'bar');
        $this->assertEquals('<info>foo</info> version <comment>bar</comment>', $application->getLongVersion(), '->getLongVersion() returns the long version of the application');
    }

    public function testHelp()
    {
        $application = new Application();
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->setHelp() returns a help message');
    }

    public function testAll()
    {
        $application = new Application();
        $commands = $application->all();
        $this->assertEquals('Symfony\\Component\\Console\\Command\\HelpCommand', get_class($commands['help']), '->all() returns the registered commands');

        $application->add(new \FooCommand());
        $commands = $application->all('foo');
        $this->assertEquals(1, count($commands), '->all() takes a namespace as its first argument');
    }

    public function testRegister()
    {
        $application = new Application();
        $command = $application->register('foo');
        $this->assertEquals('foo', $command->getName(), '->register() registers a new command');
    }

    public function testAdd()
    {
        $application = new Application();
        $application->add($foo = new \FooCommand());
        $commands = $application->all();
        $this->assertEquals($foo, $commands['foo:bar'], '->add() registers a command');

        $application = new Application();
        $application->addCommands(array($foo = new \FooCommand(), $foo1 = new \Foo1Command()));
        $commands = $application->all();
        $this->assertEquals(array($foo, $foo1), array($commands['foo:bar'], $commands['foo:bar1']), '->addCommands() registers an array of commands');
    }

    public function testHasGet()
    {
        $application = new Application();
        $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered');
        $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered');

        $application->add($foo = new \FooCommand());
        $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered');
        $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
        $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');

        $application = new Application();
        $application->add($foo = new \FooCommand());
        // simulate --help
        $r = new \ReflectionObject($application);
        $p = $r->getProperty('wantHelps');
        $p->setAccessible(true);
        $p->setValue($application, true);
        $command = $application->get('foo:bar');
        $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input');
    }

    public function testSilentHelp()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $tester = new ApplicationTester($application);
        $tester->run(array('-h' => true, '-q' => true), array('decorated' => false));

        $this->assertEmpty($tester->getDisplay(true));
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The command "foofoo" does not exist.
     */
    public function testGetInvalidCommand()
    {
        $application = new Application();
        $application->get('foofoo');
    }

    public function testGetNamespaces()
    {
        $application = new Application();
        $application->add(new \FooCommand());
        $application->add(new \Foo1Command());
        $this->assertEquals(array('foo'), $application->getNamespaces(), '->getNamespaces() returns an array of unique used namespaces');
    }

    public function testFindNamespace()
    {
        $application = new Application();
        $application->add(new \FooCommand());
        $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists');
        $this->assertEquals('foo', $application->findNamespace('f'), '->findNamespace() finds a namespace given an abbreviation');
        $application->add(new \Foo2Command());
        $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The namespace "f" is ambiguous (foo, foo1).
     */
    public function testFindAmbiguousNamespace()
    {
        $application = new Application();
        $application->add(new \FooCommand());
        $application->add(new \Foo2Command());
        $application->findNamespace('f');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage There are no commands defined in the "bar" namespace.
     */
    public function testFindInvalidNamespace()
    {
        $application = new Application();
        $application->findNamespace('bar');
    }

    public function testFind()
    {
        $application = new Application();
        $application->add(new \FooCommand());

        $this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
        $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');
        $this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists');
        $this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist');
        $this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
    }

    /**
     * @dataProvider provideAmbiguousAbbreviations
     */
    public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage)
    {
        $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage);

        $application = new Application();
        $application->add(new \FooCommand());
        $application->add(new \Foo1Command());
        $application->add(new \Foo2Command());

        $application->find($abbreviation);
    }

    public function provideAmbiguousAbbreviations()
    {
        return array(
            array('f', 'Command "f" is not defined.'),
            array('a', 'Command "a" is ambiguous (afoobar, afoobar1 and 1 more).'),
            array('foo:b', 'Command "foo:b" is ambiguous (foo:bar, foo:bar1).')
        );
    }

    public function testFindCommandEqualNamespace()
    {
        $application = new Application();
        $application->add(new \Foo3Command());
        $application->add(new \Foo4Command());

        $this->assertInstanceOf('Foo3Command', $application->find('foo3:bar'), '->find() returns the good command even if a namespace has same name');
        $this->assertInstanceOf('Foo4Command', $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name');
    }

    /**
     * @dataProvider             provideInvalidCommandNamesSingle
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Did you mean this
     */
    public function testFindAlternativeExceptionMessageSingle($name)
    {
        $application = new Application();
        $application->add(new \FooCommand());
        $application->find($name);
    }

    public function provideInvalidCommandNamesSingle()
    {
        return array(
            array('foo:baR'),
            array('foO:bar')
        );
    }

    public function testFindAlternativeExceptionMessageMultiple()
    {
        $application = new Application();
        $application->add(new \FooCommand());
        $application->add(new \Foo1Command());
        $application->add(new \Foo2Command());

        // Command + plural
        try {
            $application->find('foo:baR');
            $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
            $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
        }

        // Namespace + plural
        try {
            $application->find('foo2:bar');
            $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
            $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
        }

        $application->add(new \Foo3Command());
        $application->add(new \Foo4Command());

        // Subnamespace + plural
        try {
            $a = $application->find('foo3:');
            $this->fail('->find() should throw an \InvalidArgumentException if a command is ambiguous because of a subnamespace, with alternatives');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e);
            $this->assertRegExp('/foo3:bar/', $e->getMessage());
            $this->assertRegExp('/foo3:bar:toh/', $e->getMessage());
        }
    }

    public function testFindAlternativeCommands()
    {
        $application = new Application();

        $application->add(new \FooCommand());
        $application->add(new \Foo1Command());
        $application->add(new \Foo2Command());

        try {
            $application->find($commandName = 'Unknown command');
            $this->fail('->find() throws an \InvalidArgumentException if command does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
            $this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without alternatives');
        }

        try {
            $application->find($commandName = 'foo');
            $this->fail('->find() throws an \InvalidArgumentException if command does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
            $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
            $this->assertRegExp('/foo:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar"');
            $this->assertRegExp('/foo1:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo1:bar"');
            $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"');
        }

        // Test if "foo1" command throw an "\InvalidArgumentException" and does not contain
        // "foo:bar" as alternative because "foo1" is too far from "foo:bar"
        try {
            $application->find($commandName = 'foo1');
            $this->fail('->find() throws an \InvalidArgumentException if command does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
            $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
            $this->assertFalse(strpos($e->getMessage(), 'foo:bar'), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative');
        }
    }

    public function testFindAlternativeNamespace()
    {
        $application = new Application();

        $application->add(new \FooCommand());
        $application->add(new \Foo1Command());
        $application->add(new \Foo2Command());
        $application->add(new \foo3Command());

        try {
            $application->find('Unknown-namespace:Unknown-command');
            $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist');
            $this->assertEquals('There are no commands defined in the "Unknown-namespace" namespace.', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, without alternatives');
        }

        try {
            $application->find('foo2:command');
            $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist');
            $this->assertRegExp('/There are no commands defined in the "foo2" namespace./', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative');
            $this->assertRegExp('/foo/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo"');
            $this->assertRegExp('/foo1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo1"');
            $this->assertRegExp('/foo3/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo3"');
        }
    }

    public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces()
    {
        $application = $this->getMock('Symfony\Component\Console\Application', array('getNamespaces'));
        $application->expects($this->once())
            ->method('getNamespaces')
            ->will($this->returnValue(array('foo:sublong', 'bar:sub')));

        $this->assertEquals('foo:sublong', $application->findNamespace('f:sub'));
    }

    public function testSetCatchExceptions()
    {
        $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
        $application->setAutoExit(false);
        $application->expects($this->any())
            ->method('getTerminalWidth')
            ->will($this->returnValue(120));
        $tester = new ApplicationTester($application);

        $application->setCatchExceptions(true);
        $tester->run(array('command' => 'foo'), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag');

        $application->setCatchExceptions(false);
        try {
            $tester->run(array('command' => 'foo'), array('decorated' => false));
            $this->fail('->setCatchExceptions() sets the catch exception flag');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\Exception', $e, '->setCatchExceptions() sets the catch exception flag');
            $this->assertEquals('Command "foo" is not defined.', $e->getMessage(), '->setCatchExceptions() sets the catch exception flag');
        }
    }

    public function testAsText()
    {
        $application = new Application();
        $application->add(new \FooCommand);
        $this->ensureStaticCommandHelp($application);
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext1.txt', $this->normalizeLineBreaks($application->asText()), '->asText() returns a text representation of the application');
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext2.txt', $this->normalizeLineBreaks($application->asText('foo')), '->asText() returns a text representation of the application');
    }

    public function testAsXml()
    {
        $application = new Application();
        $application->add(new \FooCommand);
        $this->ensureStaticCommandHelp($application);
        $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml1.txt', $application->asXml(), '->asXml() returns an XML representation of the application');
        $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml2.txt', $application->asXml('foo'), '->asXml() returns an XML representation of the application');
    }

    public function testRenderException()
    {
        $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
        $application->setAutoExit(false);
        $application->expects($this->any())
            ->method('getTerminalWidth')
            ->will($this->returnValue(120));
        $tester = new ApplicationTester($application);

        $tester->run(array('command' => 'foo'), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exception');

        $tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE));
        $this->assertContains('Exception trace', $tester->getDisplay(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose');

        $tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getDisplay(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command');

        $application->add(new \Foo3Command);
        $tester = new ApplicationTester($application);
        $tester->run(array('command' => 'foo3:bar'), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');

        $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
        $application->setAutoExit(false);
        $application->expects($this->any())
            ->method('getTerminalWidth')
            ->will($this->returnValue(32));
        $tester = new ApplicationTester($application);

        $tester->run(array('command' => 'foo'), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
    }

    public function testRun()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);
        $application->add($command = new \Foo1Command());
        $_SERVER['argv'] = array('cli.php', 'foo:bar1');

        ob_start();
        $application->run();
        ob_end_clean();

        $this->assertSame('Symfony\Component\Console\Input\ArgvInput', get_class($command->input), '->run() creates an ArgvInput by default if none is given');
        $this->assertSame('Symfony\Component\Console\Output\ConsoleOutput', get_class($command->output), '->run() creates a ConsoleOutput by default if none is given');

        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $this->ensureStaticCommandHelp($application);
        $tester = new ApplicationTester($application);

        $tester->run(array(), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run1.txt', $tester->getDisplay(true), '->run() runs the list command if no argument is passed');

        $tester->run(array('--help' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if --help is passed');

        $tester->run(array('-h' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if -h is passed');

        $tester->run(array('command' => 'list', '--help' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if --help is passed');

        $tester->run(array('command' => 'list', '-h' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if -h is passed');

        $tester->run(array('--ansi' => true));
        $this->assertTrue($tester->getOutput()->isDecorated(), '->run() forces color output if --ansi is passed');

        $tester->run(array('--no-ansi' => true));
        $this->assertFalse($tester->getOutput()->isDecorated(), '->run() forces color output to be disabled if --no-ansi is passed');

        $tester->run(array('--version' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if --version is passed');

        $tester->run(array('-V' => true), array('decorated' => false));
        $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if -v is passed');

        $tester->run(array('command' => 'list', '--quiet' => true));
        $this->assertSame('', $tester->getDisplay(), '->run() removes all output if --quiet is passed');

        $tester->run(array('command' => 'list', '-q' => true));
        $this->assertSame('', $tester->getDisplay(), '->run() removes all output if -q is passed');

        $tester->run(array('command' => 'list', '--verbose' => true));
        $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose is passed');

        $tester->run(array('command' => 'list', '--verbose' => 1));
        $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose=1 is passed');

        $tester->run(array('command' => 'list', '--verbose' => 2));
        $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to very verbose if --verbose=2 is passed');

        $tester->run(array('command' => 'list', '--verbose' => 3));
        $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to debug if --verbose=3 is passed');

        $tester->run(array('command' => 'list', '--verbose' => 4));
        $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if unknown --verbose level is passed');

        $tester->run(array('command' => 'list', '-v' => true));
        $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed');

        $tester->run(array('command' => 'list', '-vv' => true));
        $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed');

        $tester->run(array('command' => 'list', '-vvv' => true));
        $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed');

        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);
        $application->add(new \FooCommand());
        $tester = new ApplicationTester($application);

        $tester->run(array('command' => 'foo:bar', '--no-interaction' => true), array('decorated' => false));
        $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if --no-interaction is passed');

        $tester->run(array('command' => 'foo:bar', '-n' => true), array('decorated' => false));
        $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed');
    }

    public function testRunReturnsIntegerExitCode()
    {
        $exception = new \Exception('', 4);

        $application = $this->getMock('Symfony\Component\Console\Application', array('doRun'));
        $application->setAutoExit(false);
        $application->expects($this->once())
             ->method('doRun')
             ->will($this->throwException($exception));

        $exitCode = $application->run(new ArrayInput(array()), new NullOutput());

        $this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception');
    }

    /**
     * @expectedException \LogicException
     * @dataProvider getAddingAlreadySetDefinitionElementData
     */
    public function testAddingAlreadySetDefinitionElementData($def)
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);
        $application
            ->register('foo')
            ->setDefinition(array($def))
            ->setCode(function (InputInterface $input, OutputInterface $output) {})
        ;

        $input = new ArrayInput(array('command' => 'foo'));
        $output = new NullOutput();
        $application->run($input, $output);
    }

    public function getAddingAlreadySetDefinitionElementData()
    {
        return array(
            array(new InputArgument('command', InputArgument::REQUIRED)),
            array(new InputOption('quiet', '', InputOption::VALUE_NONE)),
            array(new InputOption('query', 'q', InputOption::VALUE_NONE)),
        );
    }

    public function testGetDefaultHelperSetReturnsDefaultValues()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $helperSet = $application->getHelperSet();

        $this->assertTrue($helperSet->has('formatter'));
        $this->assertTrue($helperSet->has('dialog'));
        $this->assertTrue($helperSet->has('progress'));
    }

    public function testAddingSingleHelperSetOverwritesDefaultValues()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $application->setHelperSet(new HelperSet(array(new FormatterHelper())));

        $helperSet = $application->getHelperSet();

        $this->assertTrue($helperSet->has('formatter'));

        // no other default helper set should be returned
        $this->assertFalse($helperSet->has('dialog'));
        $this->assertFalse($helperSet->has('progress'));
    }

    public function testOverwritingDefaultHelperSetOverwritesDefaultValues()
    {
        $application = new CustomApplication();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $application->setHelperSet(new HelperSet(array(new FormatterHelper())));

        $helperSet = $application->getHelperSet();

        $this->assertTrue($helperSet->has('formatter'));

        // no other default helper set should be returned
        $this->assertFalse($helperSet->has('dialog'));
        $this->assertFalse($helperSet->has('progress'));
    }

    public function testGetDefaultInputDefinitionReturnsDefaultValues()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $inputDefinition = $application->getDefinition();

        $this->assertTrue($inputDefinition->hasArgument('command'));

        $this->assertTrue($inputDefinition->hasOption('help'));
        $this->assertTrue($inputDefinition->hasOption('quiet'));
        $this->assertTrue($inputDefinition->hasOption('verbose'));
        $this->assertTrue($inputDefinition->hasOption('version'));
        $this->assertTrue($inputDefinition->hasOption('ansi'));
        $this->assertTrue($inputDefinition->hasOption('no-ansi'));
        $this->assertTrue($inputDefinition->hasOption('no-interaction'));
    }

    public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues()
    {
        $application = new CustomApplication();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $inputDefinition = $application->getDefinition();

        // check whether the default arguments and options are not returned any more
        $this->assertFalse($inputDefinition->hasArgument('command'));

        $this->assertFalse($inputDefinition->hasOption('help'));
        $this->assertFalse($inputDefinition->hasOption('quiet'));
        $this->assertFalse($inputDefinition->hasOption('verbose'));
        $this->assertFalse($inputDefinition->hasOption('version'));
        $this->assertFalse($inputDefinition->hasOption('ansi'));
        $this->assertFalse($inputDefinition->hasOption('no-ansi'));
        $this->assertFalse($inputDefinition->hasOption('no-interaction'));

        $this->assertTrue($inputDefinition->hasOption('custom'));
    }

    public function testSettingCustomInputDefinitionOverwritesDefaultValues()
    {
        $application = new Application();
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $application->setDefinition(new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.'))));

        $inputDefinition = $application->getDefinition();

        // check whether the default arguments and options are not returned any more
        $this->assertFalse($inputDefinition->hasArgument('command'));

        $this->assertFalse($inputDefinition->hasOption('help'));
        $this->assertFalse($inputDefinition->hasOption('quiet'));
        $this->assertFalse($inputDefinition->hasOption('verbose'));
        $this->assertFalse($inputDefinition->hasOption('version'));
        $this->assertFalse($inputDefinition->hasOption('ansi'));
        $this->assertFalse($inputDefinition->hasOption('no-ansi'));
        $this->assertFalse($inputDefinition->hasOption('no-interaction'));

        $this->assertTrue($inputDefinition->hasOption('custom'));
    }

    public function testRunWithDispatcher()
    {
        if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
            $this->markTestSkipped('The "EventDispatcher" component is not available');
        }

        $application = new Application();
        $application->setAutoExit(false);
        $application->setDispatcher($this->getDispatcher());

        $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) {
            $output->write('foo.');
        });

        $tester = new ApplicationTester($application);
        $tester->run(array('command' => 'foo'));
        $this->assertEquals('before.foo.after.', $tester->getDisplay());
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage caught
     */
    public function testRunWithExceptionAndDispatcher()
    {
        if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
            $this->markTestSkipped('The "EventDispatcher" component is not available');
        }

        $application = new Application();
        $application->setDispatcher($this->getDispatcher());
        $application->setAutoExit(false);
        $application->setCatchExceptions(false);

        $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) {
            throw new \RuntimeException('foo');
        });

        $tester = new ApplicationTester($application);
        $tester->run(array('command' => 'foo'));
    }

    public function testRunDispatchesAllEventsWithException()
    {
        if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
            $this->markTestSkipped('The "EventDispatcher" component is not available');
        }

        $application = new Application();
        $application->setDispatcher($this->getDispatcher());
        $application->setAutoExit(false);

        $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) {
            $output->write('foo.');

            throw new \RuntimeException('foo');
        });

        $tester = new ApplicationTester($application);
        $tester->run(array('command' => 'foo'));
        $this->assertContains('before.foo.after.caught.', $tester->getDisplay());
    }

    protected function getDispatcher()
    {
        $dispatcher = new EventDispatcher;
        $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) {
            $event->getOutput()->write('before.');
        });
        $dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) {
            $event->getOutput()->write('after.');

            $event->setExitCode(128);
        });
        $dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) {
            $event->getOutput()->writeln('caught.');

            $event->setException(new \LogicException('caught.', $event->getExitCode(), $event->getException()));
        });

        return $dispatcher;
    }
}

class CustomApplication extends Application
{
    /**
     * Overwrites the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.')));
    }

    /**
     * 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()));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Tester\CommandTester;

class CommandTest extends \PHPUnit_Framework_TestCase
{
    protected static $fixturesPath;

    public static function setUpBeforeClass()
    {
        self::$fixturesPath = __DIR__.'/../Fixtures/';
        require_once self::$fixturesPath.'/TestCommand.php';
    }

    public function testConstructor()
    {
        $command = new Command('foo:bar');
        $this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage The command name cannot be empty.
     */
    public function testCommandNameCannotBeEmpty()
    {
        new Command();
    }

    public function testSetApplication()
    {
        $application = new Application();
        $command = new \TestCommand();
        $command->setApplication($application);
        $this->assertEquals($application, $command->getApplication(), '->setApplication() sets the current application');
    }

    public function testSetGetDefinition()
    {
        $command = new \TestCommand();
        $ret = $command->setDefinition($definition = new InputDefinition());
        $this->assertEquals($command, $ret, '->setDefinition() implements a fluent interface');
        $this->assertEquals($definition, $command->getDefinition(), '->setDefinition() sets the current InputDefinition instance');
        $command->setDefinition(array(new InputArgument('foo'), new InputOption('bar')));
        $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument');
        $this->assertTrue($command->getDefinition()->hasOption('bar'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument');
        $command->setDefinition(new InputDefinition());
    }

    public function testAddArgument()
    {
        $command = new \TestCommand();
        $ret = $command->addArgument('foo');
        $this->assertEquals($command, $ret, '->addArgument() implements a fluent interface');
        $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->addArgument() adds an argument to the command');
    }

    public function testAddOption()
    {
        $command = new \TestCommand();
        $ret = $command->addOption('foo');
        $this->assertEquals($command, $ret, '->addOption() implements a fluent interface');
        $this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command');
    }

    public function testGetNamespaceGetNameSetName()
    {
        $command = new \TestCommand();
        $this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name');
        $command->setName('foo');
        $this->assertEquals('foo', $command->getName(), '->setName() sets the command name');

        $ret = $command->setName('foobar:bar');
        $this->assertEquals($command, $ret, '->setName() implements a fluent interface');
        $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name');
    }

    /**
     * @dataProvider provideInvalidCommandNames
     */
    public function testInvalidCommandNames($name)
    {
        $this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name));

        $command = new \TestCommand();
        $command->setName($name);
    }

    public function provideInvalidCommandNames()
    {
        return array(
            array(''),
            array('foo:')
        );
    }

    public function testGetSetDescription()
    {
        $command = new \TestCommand();
        $this->assertEquals('description', $command->getDescription(), '->getDescription() returns the description');
        $ret = $command->setDescription('description1');
        $this->assertEquals($command, $ret, '->setDescription() implements a fluent interface');
        $this->assertEquals('description1', $command->getDescription(), '->setDescription() sets the description');
    }

    public function testGetSetHelp()
    {
        $command = new \TestCommand();
        $this->assertEquals('help', $command->getHelp(), '->getHelp() returns the help');
        $ret = $command->setHelp('help1');
        $this->assertEquals($command, $ret, '->setHelp() implements a fluent interface');
        $this->assertEquals('help1', $command->getHelp(), '->setHelp() sets the help');
    }

    public function testGetProcessedHelp()
    {
        $command = new \TestCommand();
        $command->setHelp('The %command.name% command does... Example: php %command.full_name%.');
        $this->assertContains('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly');
        $this->assertNotContains('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name%');
    }

    public function testGetSetAliases()
    {
        $command = new \TestCommand();
        $this->assertEquals(array('name'), $command->getAliases(), '->getAliases() returns the aliases');
        $ret = $command->setAliases(array('name1'));
        $this->assertEquals($command, $ret, '->setAliases() implements a fluent interface');
        $this->assertEquals(array('name1'), $command->getAliases(), '->setAliases() sets the aliases');
    }

    public function testGetSynopsis()
    {
        $command = new \TestCommand();
        $command->addOption('foo');
        $command->addArgument('foo');
        $this->assertEquals('namespace:name [--foo] [foo]', $command->getSynopsis(), '->getSynopsis() returns the synopsis');
    }

    public function testGetHelper()
    {
        $application = new Application();
        $command = new \TestCommand();
        $command->setApplication($application);
        $formatterHelper = new FormatterHelper();
        $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper');
    }

    public function testGet()
    {
        $application = new Application();
        $command = new \TestCommand();
        $command->setApplication($application);
        $formatterHelper = new FormatterHelper();
        $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->__get() returns the correct helper');
    }

    public function testMergeApplicationDefinition()
    {
        $application1 = new Application();
        $application1->getDefinition()->addArguments(array(new InputArgument('foo')));
        $application1->getDefinition()->addOptions(array(new InputOption('bar')));
        $command = new \TestCommand();
        $command->setApplication($application1);
        $command->setDefinition($definition = new InputDefinition(array(new InputArgument('bar'), new InputOption('foo'))));

        $r = new \ReflectionObject($command);
        $m = $r->getMethod('mergeApplicationDefinition');
        $m->setAccessible(true);
        $m->invoke($command);
        $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition() merges the application arguments and the command arguments');
        $this->assertTrue($command->getDefinition()->hasArgument('bar'), '->mergeApplicationDefinition() merges the application arguments and the command arguments');
        $this->assertTrue($command->getDefinition()->hasOption('foo'), '->mergeApplicationDefinition() merges the application options and the command options');
        $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition() merges the application options and the command options');

        $m->invoke($command);
        $this->assertEquals(3, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments and options');
    }

    public function testMergeApplicationDefinitionWithoutArgsThenWithArgsAddsArgs()
    {
        $application1 = new Application();
        $application1->getDefinition()->addArguments(array(new InputArgument('foo')));
        $application1->getDefinition()->addOptions(array(new InputOption('bar')));
        $command = new \TestCommand();
        $command->setApplication($application1);
        $command->setDefinition($definition = new InputDefinition(array()));

        $r = new \ReflectionObject($command);
        $m = $r->getMethod('mergeApplicationDefinition');
        $m->setAccessible(true);
        $m->invoke($command, false);
        $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the commmand options');
        $this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments');

        $m->invoke($command, true);
        $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(true) merges the application arguments and the command arguments');

        $m->invoke($command);
        $this->assertEquals(2, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments');
    }

    public function testRunInteractive()
    {
        $tester = new CommandTester(new \TestCommand());

        $tester->execute(array(), array('interactive' => true));

        $this->assertEquals('interact called'.PHP_EOL.'execute called'.PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive');
    }

    public function testRunNonInteractive()
    {
        $tester = new CommandTester(new \TestCommand());

        $tester->execute(array(), array('interactive' => false));

        $this->assertEquals('execute called'.PHP_EOL, $tester->getDisplay(), '->run() does not call the interact() method if the input is not interactive');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage You must override the execute() method in the concrete command class.
     */
    public function testExecuteMethodNeedsToBeOverriden()
    {
        $command = new Command('foo');
        $command->run(new StringInput(''), new NullOutput());
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "--bar" option does not exist.
     */
    public function testRunWithInvalidOption()
    {
        $command = new \TestCommand();
        $tester = new CommandTester($command);
        $tester->execute(array('--bar' => true));
    }

    public function testRunReturnsIntegerExitCode()
    {
        $command = new \TestCommand();
        $exitCode = $command->run(new StringInput(''), new NullOutput());
        $this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)');

        $command = $this->getMock('TestCommand', array('execute'));
        $command->expects($this->once())
             ->method('execute')
             ->will($this->returnValue('2.3'));
        $exitCode = $command->run(new StringInput(''), new NullOutput());
        $this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)');
    }

    public function testRunReturnsAlwaysInteger()
    {
        $command = new \TestCommand();

        $this->assertSame(0, $command->run(new StringInput(''), new NullOutput()));
    }

    public function testSetCode()
    {
        $command = new \TestCommand();
        $ret = $command->setCode(function (InputInterface $input, OutputInterface $output) {
            $output->writeln('from the code...');
        });
        $this->assertEquals($command, $ret, '->setCode() implements a fluent interface');
        $tester = new CommandTester($command);
        $tester->execute(array());
        $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
    }

    public function testSetCodeWithNonClosureCallable()
    {
        $command = new \TestCommand();
        $ret = $command->setCode(array($this, 'callableMethodCommand'));
        $this->assertEquals($command, $ret, '->setCode() implements a fluent interface');
        $tester = new CommandTester($command);
        $tester->execute(array());
        $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Invalid callable provided to Command::setCode.
     */
    public function testSetCodeWithNonCallable()
    {
        $command = new \TestCommand();
        $command->setCode(array($this, 'nonExistentMethod'));
    }

    public function callableMethodCommand(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('from the code...');
    }

    public function testAsText()
    {
        $command = new \TestCommand();
        $command->setApplication(new Application());
        $tester = new CommandTester($command);
        $tester->execute(array('command' => $command->getName()));
        $this->assertStringEqualsFile(self::$fixturesPath.'/command_astext.txt', $command->asText(), '->asText() returns a text representation of the command');
    }

    public function testAsXml()
    {
        $command = new \TestCommand();
        $command->setApplication(new Application());
        $tester = new CommandTester($command);
        $tester->execute(array('command' => $command->getName()));
        $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/command_asxml.txt', $command->asXml(), '->asXml() returns an XML representation of the 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\Tests\Command;

use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Application;

class HelpCommandTest extends \PHPUnit_Framework_TestCase
{
    public function testExecuteForCommandAlias()
    {
        $command = new HelpCommand();
        $command->setApplication(new Application());
        $commandTester = new CommandTester($command);
        $commandTester->execute(array('command_name' => 'li'));
        $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias');
    }

    public function testExecuteForCommand()
    {
        $command = new HelpCommand();
        $commandTester = new CommandTester($command);
        $command->setCommand(new ListCommand());
        $commandTester->execute(array());
        $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
    }

    public function testExecuteForCommandWithXmlOption()
    {
        $command = new HelpCommand();
        $commandTester = new CommandTester($command);
        $command->setCommand(new ListCommand());
        $commandTester->execute(array('--format' => 'xml'));
        $this->assertRegExp('/<command/', $commandTester->getDisplay(), '->execute() returns an XML help text if --xml is passed');
    }

    public function testExecuteForApplicationCommand()
    {
        $application = new Application();
        $commandTester = new CommandTester($application->get('help'));
        $commandTester->execute(array('command_name' => 'list'));
        $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
    }


    public function testExecuteForApplicationCommandWithXmlOption()
    {
        $application = new Application();
        $commandTester = new CommandTester($application->get('help'));
        $commandTester->execute(array('command_name' => 'list', '--format' => 'xml'));
        $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
        $this->assertRegExp('/<command/', $commandTester->getDisplay(), '->execute() returns an XML help text if --format=xml is passed');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Command;

use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Console\Application;

class ListCommandTest extends \PHPUnit_Framework_TestCase
{
    public function testExecuteListsCommands()
    {
        $application = new Application();
        $commandTester = new CommandTester($command = $application->get('list'));
        $commandTester->execute(array('command' => $command->getName()), array('decorated' => false));

        $this->assertRegExp('/help   Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands');
    }

    public function testExecuteListsCommandsWithXmlOption()
    {
        $application = new Application();
        $commandTester = new CommandTester($command = $application->get('list'));
        $commandTester->execute(array('command' => $command->getName(), '--format' => 'xml'));
        $this->assertRegExp('/<command id="list" name="list">/', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed');
    }

    public function testExecuteListsCommandsWithRawOption()
    {
        $application = new Application();
        $commandTester = new CommandTester($command = $application->get('list'));
        $commandTester->execute(array('command' => $command->getName(), '--raw' => true));
        $output = <<<EOF
help   Displays help for a command
list   Lists commands

EOF;

        $this->assertEquals($output, $commandTester->getDisplay(true));
    }

    public function testExecuteListsCommandsWithNamespaceArgument()
    {

        require_once(realpath(__DIR__.'/../Fixtures/FooCommand.php'));
        $application = new Application();
        $application->add(new \FooCommand());
        $commandTester = new CommandTester($command = $application->get('list'));
        $commandTester->execute(array('command' => $command->getName(), 'namespace' => 'foo', '--raw' => true));
        $output = <<<EOF
foo:bar   The foo:bar command

EOF;

        $this->assertEquals($output, $commandTester->getDisplay(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\Console\Tests\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase
{
    /** @dataProvider getDescribeInputArgumentTestData */
    public function testDescribeInputArgument(InputArgument $argument, $expectedDescription)
    {
        $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($argument)));
    }

    /** @dataProvider getDescribeInputOptionTestData */
    public function testDescribeInputOption(InputOption $option, $expectedDescription)
    {
        $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($option)));
    }

    /** @dataProvider getDescribeInputDefinitionTestData */
    public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription)
    {
        $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($definition)));
    }

    /** @dataProvider getDescribeCommandTestData */
    public function testDescribeCommand(Command $command, $expectedDescription)
    {
        $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($command)));
    }

    /** @dataProvider getDescribeApplicationTestData */
    public function testDescribeApplication(Application $application, $expectedDescription)
    {
        // Replaces the dynamic placeholders of the command help text with a static version.
        // The placeholder %command.full_name% includes the script path that is not predictable
        // and can not be tested against.
        foreach ($application->all() as $command) {
            $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp()));
        }

        $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $this->getDescriptor()->describe($application))));
    }

    public function getDescribeInputArgumentTestData()
    {
        return $this->getDescriptionTestData(ObjectsProvider::getInputArguments());
    }

    public function getDescribeInputOptionTestData()
    {
        return $this->getDescriptionTestData(ObjectsProvider::getInputOptions());
    }

    public function getDescribeInputDefinitionTestData()
    {
        return $this->getDescriptionTestData(ObjectsProvider::getInputDefinitions());
    }

    public function getDescribeCommandTestData()
    {
        return $this->getDescriptionTestData(ObjectsProvider::getCommands());
    }

    public function getDescribeApplicationTestData()
    {
        return $this->getDescriptionTestData(ObjectsProvider::getApplications());
    }

    abstract protected function getDescriptor();
    abstract protected function getFormat();

    private function getDescriptionTestData(array $objects)
    {
        $data = array();
        foreach ($objects as $name => $object) {
            $description = file_get_contents(sprintf('%s/../Fixtures/%s.%s', __DIR__, $name, $this->getFormat()));
            $data[] = array($object, $description);
        }

        return $data;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Descriptor;

use Symfony\Component\Console\Descriptor\JsonDescriptor;

class JsonDescriptorTest extends AbstractDescriptorTest
{
    protected function getDescriptor()
    {
        return new JsonDescriptor();
    }

    protected function getFormat()
    {
        return 'json';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tests\Descriptor;

use Symfony\Component\Console\Descriptor\MarkdownDescriptor;

class MarkdownDescriptorTest extends AbstractDescriptorTest
{
    protected function getDescriptor()
    {
        return new MarkdownDescriptor();
    }

    protected function getFormat()
    {
        return 'md';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Descriptor;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication1;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication2;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand1;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand2;
use Symfony\Component\Finder\Shell\Command;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class ObjectsProvider
{
    public static function getInputArguments()
    {
        return array(
            'input_argument_1' => new InputArgument('argument_name', InputArgument::REQUIRED),
            'input_argument_2' => new InputArgument('argument_name', InputArgument::IS_ARRAY, 'argument description'),
            'input_argument_3' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'default_value'),
        );
    }

    public static function getInputOptions()
    {
        return array(
            'input_option_1' => new InputOption('option_name', 'o', InputOption::VALUE_NONE),
            'input_option_2' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', 'default_value'),
            'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'),
            'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()),
        );
    }

    public static function getInputDefinitions()
    {
        return array(
            'input_definition_1' => new InputDefinition(),
            'input_definition_2' => new InputDefinition(array(new InputArgument('argument_name', InputArgument::REQUIRED))),
            'input_definition_3' => new InputDefinition(array(new InputOption('option_name', 'o', InputOption::VALUE_NONE))),
            'input_definition_4' => new InputDefinition(array(
                new InputArgument('argument_name', InputArgument::REQUIRED),
                new InputOption('option_name', 'o', InputOption::VALUE_NONE),
            )),
        );
    }

    public static function getCommands()
    {
        return array(
            'command_1' => new DescriptorCommand1(),
            'command_2' => new DescriptorCommand2(),
        );
    }

    public static function getApplications()
    {
        return array(
            'application_1' => new DescriptorApplication1(),
            'application_2' => new DescriptorApplication2(),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Descriptor;

use Symfony\Component\Console\Descriptor\TextDescriptor;

class TextDescriptorTest extends AbstractDescriptorTest
{
    protected function getDescriptor()
    {
        return new TextDescriptor();
    }

    protected function getFormat()
    {
        return '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\Tests\Descriptor;

use Symfony\Component\Console\Descriptor\XmlDescriptor;

class XmlDescriptorTest extends AbstractDescriptorTest
{
    protected function getDescriptor()
    {
        return new XmlDescriptor();
    }

    protected function getFormat()
    {
        return 'xml';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Fixtures;

use Symfony\Component\Console\Application;

class DescriptorApplication1 extends Application
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Fixtures;

use Symfony\Component\Console\Application;

class DescriptorApplication2 extends Application
{
    public function __construct()
    {
        parent::__construct('My Symfony application', 'v1.0');
        $this->add(new DescriptorCommand1());
        $this->add(new DescriptorCommand2());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Fixtures;

use Symfony\Component\Console\Command\Command;

class DescriptorCommand1 extends Command
{
    protected function configure()
    {
        $this
            ->setName('descriptor:command1')
            ->setAliases(array('alias1', 'alias2'))
            ->setDescription('command 1 description')
            ->setHelp('command 1 help')
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Fixtures;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class DescriptorCommand2 extends Command
{
    protected function configure()
    {
        $this
            ->setName('descriptor:command2')
            ->setDescription('command 2 description')
            ->setHelp('command 2 help')
            ->addArgument('argument_name', InputArgument::REQUIRED)
            ->addOption('option_name', 'o', InputOption::VALUE_NONE)
        ;
    }
}
<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Foo1Command extends Command
{
    public $input;
    public $output;

    protected function configure()
    {
        $this
            ->setName('foo:bar1')
            ->setDescription('The foo:bar1 command')
            ->setAliases(array('afoobar1'))
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->output = $output;
    }
}
<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Foo2Command extends Command
{
    protected function configure()
    {
        $this
            ->setName('foo1:bar')
            ->setDescription('The foo1:bar command')
            ->setAliases(array('afoobar2'))
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
    }
}
<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Foo3Command extends Command
{
    protected function configure()
    {
        $this
            ->setName('foo3:bar')
            ->setDescription('The foo3:bar command')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            throw new \Exception("First exception");
        } catch (\Exception $e) {
            throw new \Exception("Second exception", 0, $e);
        }
    }
}
<?php

use Symfony\Component\Console\Command\Command;

class Foo4Command extends Command
{
    protected function configure()
    {
        $this->setName('foo3:bar:toh');
    }
}
<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class FooCommand extends Command
{
    public $input;
    public $output;

    protected function configure()
    {
        $this
            ->setName('foo:bar')
            ->setDescription('The foo:bar command')
            ->setAliases(array('afoobar'))
        ;
    }

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('interact called');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->output = $output;

        $output->writeln('called');
    }
}
<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class TestCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('namespace:name')
            ->setAliases(array('name'))
            ->setDescription('description')
            ->setHelp('help')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('execute called');
    }

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('interact called');
    }
}
{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The <info>help<\/info> command displays help for a given command:\n\n  <info>php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the <comment>--format<\/comment> option:\n\n  <info>php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the <info>list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":null},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The <info>list<\/info> command lists all commands:\n\n  <info>php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n  <info>php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the <comment>--format<\/comment> option:\n\n  <info>php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n  <info>php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":null}}}}],"namespaces":[{"id":"_global","commands":["help","list"]}]}
UNKNOWN
=======

* help
* list

help
----

* Description: Displays help for a command
* Usage: `help [--xml] [--format="..."] [--raw] [command_name]`
* Aliases: <none>

The <info>help</info> command displays help for a given command:

  <info>php app/console help list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>php app/console help --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.

### Arguments:

**command_name:**

* Name: command_name
* Is required: no
* Is array: no
* Description: The command name
* Default: `'help'`

### Options:

**xml:**

* Name: `--xml`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output help as XML
* Default: `false`

**format:**

* Name: `--format`
* Shortcut: <none>
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: To output help in other formats
* Default: `NULL`

**raw:**

* Name: `--raw`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output raw command help
* Default: `false`

**help:**

* Name: `--help`
* Shortcut: `-h`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this help message.
* Default: `false`

**quiet:**

* Name: `--quiet`
* Shortcut: `-q`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not output any message.
* Default: `false`

**verbose:**

* Name: `--verbose`
* Shortcut: `-v|-vv|-vvv`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
* Default: `false`

**version:**

* Name: `--version`
* Shortcut: `-V`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this application version.
* Default: `false`

**ansi:**

* Name: `--ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Force ANSI output.
* Default: `false`

**no-ansi:**

* Name: `--no-ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Disable ANSI output.
* Default: `false`

**no-interaction:**

* Name: `--no-interaction`
* Shortcut: `-n`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not ask any interactive question.
* Default: `false`

list
----

* Description: Lists commands
* Usage: `list [--xml] [--raw] [--format="..."] [namespace]`
* Aliases: <none>

The <info>list</info> command lists all commands:

  <info>php app/console list</info>

You can also display the commands for a specific namespace:

  <info>php app/console list test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>php app/console list --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>php app/console list --raw</info>

### Arguments:

**namespace:**

* Name: namespace
* Is required: no
* Is array: no
* Description: The namespace name
* Default: `NULL`

### Options:

**xml:**

* Name: `--xml`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output list as XML
* Default: `false`

**raw:**

* Name: `--raw`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output raw command list
* Default: `false`

**format:**

* Name: `--format`
* Shortcut: <none>
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: To output list in other formats
* Default: `NULL`
<info>Console Tool</info>

<comment>Usage:</comment>
  [options] command [arguments]

<comment>Options:</comment>
  <info>--help</info>           <info>-h</info> Display this help message.
  <info>--quiet</info>          <info>-q</info> Do not output any message.
  <info>--verbose</info>        <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  <info>--version</info>        <info>-V</info> Display this application version.
  <info>--ansi</info>              Force ANSI output.
  <info>--no-ansi</info>           Disable ANSI output.
  <info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.

<comment>Available commands:</comment>
  <info>help  </info> Displays help for a command
  <info>list  </info> Lists commands
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
  <commands>
    <command id="help" name="help">
      <usage>help [--xml] [--format="..."] [--raw] [command_name]</usage>
      <description>Displays help for a command</description>
      <help>The &lt;info&gt;help&lt;/info&gt; command displays help for a given command:
 
   &lt;info&gt;php app/console help list&lt;/info&gt;
 
 You can also output the help in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console help --format=xml list&lt;/info&gt;
 
 To display the list of available commands, please use the &lt;info&gt;list&lt;/info&gt; command.</help>
      <aliases/>
      <arguments>
        <argument name="command_name" is_required="0" is_array="0">
          <description>The command name</description>
          <defaults>
            <default>help</default>
          </defaults>
        </argument>
      </arguments>
      <options>
        <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output help as XML</description>
        </option>
        <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
          <description>To output help in other formats</description>
          <defaults/>
        </option>
        <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output raw command help</description>
        </option>
        <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this help message.</description>
        </option>
        <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not output any message.</description>
        </option>
        <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
        </option>
        <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this application version.</description>
        </option>
        <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Force ANSI output.</description>
        </option>
        <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Disable ANSI output.</description>
        </option>
        <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not ask any interactive question.</description>
        </option>
      </options>
    </command>
    <command id="list" name="list">
      <usage>list [--xml] [--raw] [--format="..."] [namespace]</usage>
      <description>Lists commands</description>
      <help>The &lt;info&gt;list&lt;/info&gt; command lists all commands:
 
   &lt;info&gt;php app/console list&lt;/info&gt;
 
 You can also display the commands for a specific namespace:
 
   &lt;info&gt;php app/console list test&lt;/info&gt;
 
 You can also output the information in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console list --format=xml&lt;/info&gt;
 
 It's also possible to get raw list of commands (useful for embedding command runner):
 
   &lt;info&gt;php app/console list --raw&lt;/info&gt;</help>
      <aliases/>
      <arguments>
        <argument name="namespace" is_required="0" is_array="0">
          <description>The namespace name</description>
          <defaults/>
        </argument>
      </arguments>
      <options>
        <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output list as XML</description>
        </option>
        <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output raw command list</description>
        </option>
        <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
          <description>To output list in other formats</description>
          <defaults/>
        </option>
      </options>
    </command>
  </commands>
  <namespaces>
    <namespace id="_global">
      <command>help</command>
      <command>list</command>
    </namespace>
  </namespaces>
</symfony>
{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The <info>help<\/info> command displays help for a given command:\n\n  <info>php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the <comment>--format<\/comment> option:\n\n  <info>php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the <info>list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":null},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The <info>list<\/info> command lists all commands:\n\n  <info>php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n  <info>php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the <comment>--format<\/comment> option:\n\n  <info>php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n  <info>php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":null}}}},{"name":"descriptor:command1","usage":"descriptor:command1","description":"command 1 description","help":"command 1 help","aliases":["alias1","alias2"],"definition":{"arguments":[],"options":{"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"descriptor:command2","usage":"descriptor:command2 [-o|--option_name] argument_name","description":"command 2 description","help":"command 2 help","aliases":[],"definition":{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}}],"namespaces":[{"id":"_global","commands":["alias1","alias2","help","list"]},{"id":"descriptor","commands":["descriptor:command1","descriptor:command2"]}]}
My Symfony application
======================

* alias1
* alias2
* help
* list

**descriptor:**

* descriptor:command1
* descriptor:command2

help
----

* Description: Displays help for a command
* Usage: `help [--xml] [--format="..."] [--raw] [command_name]`
* Aliases: <none>

The <info>help</info> command displays help for a given command:

  <info>php app/console help list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>php app/console help --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.

### Arguments:

**command_name:**

* Name: command_name
* Is required: no
* Is array: no
* Description: The command name
* Default: `'help'`

### Options:

**xml:**

* Name: `--xml`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output help as XML
* Default: `false`

**format:**

* Name: `--format`
* Shortcut: <none>
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: To output help in other formats
* Default: `NULL`

**raw:**

* Name: `--raw`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output raw command help
* Default: `false`

**help:**

* Name: `--help`
* Shortcut: `-h`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this help message.
* Default: `false`

**quiet:**

* Name: `--quiet`
* Shortcut: `-q`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not output any message.
* Default: `false`

**verbose:**

* Name: `--verbose`
* Shortcut: `-v|-vv|-vvv`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
* Default: `false`

**version:**

* Name: `--version`
* Shortcut: `-V`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this application version.
* Default: `false`

**ansi:**

* Name: `--ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Force ANSI output.
* Default: `false`

**no-ansi:**

* Name: `--no-ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Disable ANSI output.
* Default: `false`

**no-interaction:**

* Name: `--no-interaction`
* Shortcut: `-n`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not ask any interactive question.
* Default: `false`

list
----

* Description: Lists commands
* Usage: `list [--xml] [--raw] [--format="..."] [namespace]`
* Aliases: <none>

The <info>list</info> command lists all commands:

  <info>php app/console list</info>

You can also display the commands for a specific namespace:

  <info>php app/console list test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>php app/console list --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>php app/console list --raw</info>

### Arguments:

**namespace:**

* Name: namespace
* Is required: no
* Is array: no
* Description: The namespace name
* Default: `NULL`

### Options:

**xml:**

* Name: `--xml`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output list as XML
* Default: `false`

**raw:**

* Name: `--raw`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: To output raw command list
* Default: `false`

**format:**

* Name: `--format`
* Shortcut: <none>
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: To output list in other formats
* Default: `NULL`

descriptor:command1
-------------------

* Description: command 1 description
* Usage: `descriptor:command1`
* Aliases: `alias1`, `alias2`

command 1 help

### Options:

**help:**

* Name: `--help`
* Shortcut: `-h`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this help message.
* Default: `false`

**quiet:**

* Name: `--quiet`
* Shortcut: `-q`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not output any message.
* Default: `false`

**verbose:**

* Name: `--verbose`
* Shortcut: `-v|-vv|-vvv`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
* Default: `false`

**version:**

* Name: `--version`
* Shortcut: `-V`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this application version.
* Default: `false`

**ansi:**

* Name: `--ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Force ANSI output.
* Default: `false`

**no-ansi:**

* Name: `--no-ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Disable ANSI output.
* Default: `false`

**no-interaction:**

* Name: `--no-interaction`
* Shortcut: `-n`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not ask any interactive question.
* Default: `false`

descriptor:command2
-------------------

* Description: command 2 description
* Usage: `descriptor:command2 [-o|--option_name] argument_name`
* Aliases: <none>

command 2 help

### Arguments:

**argument_name:**

* Name: argument_name
* Is required: yes
* Is array: no
* Description: <none>
* Default: `NULL`

### Options:

**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: <none>
* Default: `false`

**help:**

* Name: `--help`
* Shortcut: `-h`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this help message.
* Default: `false`

**quiet:**

* Name: `--quiet`
* Shortcut: `-q`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not output any message.
* Default: `false`

**verbose:**

* Name: `--verbose`
* Shortcut: `-v|-vv|-vvv`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
* Default: `false`

**version:**

* Name: `--version`
* Shortcut: `-V`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Display this application version.
* Default: `false`

**ansi:**

* Name: `--ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Force ANSI output.
* Default: `false`

**no-ansi:**

* Name: `--no-ansi`
* Shortcut: <none>
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Disable ANSI output.
* Default: `false`

**no-interaction:**

* Name: `--no-interaction`
* Shortcut: `-n`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: Do not ask any interactive question.
* Default: `false`
<info>My Symfony application</info> version <comment>v1.0</comment>

<comment>Usage:</comment>
  [options] command [arguments]

<comment>Options:</comment>
  <info>--help</info>           <info>-h</info> Display this help message.
  <info>--quiet</info>          <info>-q</info> Do not output any message.
  <info>--verbose</info>        <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  <info>--version</info>        <info>-V</info> Display this application version.
  <info>--ansi</info>              Force ANSI output.
  <info>--no-ansi</info>           Disable ANSI output.
  <info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.

<comment>Available commands:</comment>
  <info>alias1               </info> command 1 description
  <info>alias2               </info> command 1 description
  <info>help                 </info> Displays help for a command
  <info>list                 </info> Lists commands
<comment>descriptor</comment>
  <info>descriptor:command1  </info> command 1 description
  <info>descriptor:command2  </info> command 2 description
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
  <commands>
    <command id="help" name="help">
      <usage>help [--xml] [--format="..."] [--raw] [command_name]</usage>
      <description>Displays help for a command</description>
      <help>The &lt;info&gt;help&lt;/info&gt; command displays help for a given command:
 
   &lt;info&gt;php app/console help list&lt;/info&gt;
 
 You can also output the help in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console help --format=xml list&lt;/info&gt;
 
 To display the list of available commands, please use the &lt;info&gt;list&lt;/info&gt; command.</help>
      <aliases/>
      <arguments>
        <argument name="command_name" is_required="0" is_array="0">
          <description>The command name</description>
          <defaults>
            <default>help</default>
          </defaults>
        </argument>
      </arguments>
      <options>
        <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output help as XML</description>
        </option>
        <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
          <description>To output help in other formats</description>
          <defaults/>
        </option>
        <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output raw command help</description>
        </option>
        <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this help message.</description>
        </option>
        <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not output any message.</description>
        </option>
        <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
        </option>
        <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this application version.</description>
        </option>
        <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Force ANSI output.</description>
        </option>
        <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Disable ANSI output.</description>
        </option>
        <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not ask any interactive question.</description>
        </option>
      </options>
    </command>
    <command id="list" name="list">
      <usage>list [--xml] [--raw] [--format="..."] [namespace]</usage>
      <description>Lists commands</description>
      <help>The &lt;info&gt;list&lt;/info&gt; command lists all commands:
 
   &lt;info&gt;php app/console list&lt;/info&gt;
 
 You can also display the commands for a specific namespace:
 
   &lt;info&gt;php app/console list test&lt;/info&gt;
 
 You can also output the information in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console list --format=xml&lt;/info&gt;
 
 It's also possible to get raw list of commands (useful for embedding command runner):
 
   &lt;info&gt;php app/console list --raw&lt;/info&gt;</help>
      <aliases/>
      <arguments>
        <argument name="namespace" is_required="0" is_array="0">
          <description>The namespace name</description>
          <defaults/>
        </argument>
      </arguments>
      <options>
        <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output list as XML</description>
        </option>
        <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>To output raw command list</description>
        </option>
        <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
          <description>To output list in other formats</description>
          <defaults/>
        </option>
      </options>
    </command>
    <command id="descriptor:command1" name="descriptor:command1">
      <usage>descriptor:command1</usage>
      <description>command 1 description</description>
      <help>command 1 help</help>
      <aliases>
        <alias>alias1</alias>
        <alias>alias2</alias>
      </aliases>
      <arguments/>
      <options>
        <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this help message.</description>
        </option>
        <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not output any message.</description>
        </option>
        <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
        </option>
        <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this application version.</description>
        </option>
        <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Force ANSI output.</description>
        </option>
        <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Disable ANSI output.</description>
        </option>
        <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not ask any interactive question.</description>
        </option>
      </options>
    </command>
    <command id="descriptor:command2" name="descriptor:command2">
      <usage>descriptor:command2 [-o|--option_name] argument_name</usage>
      <description>command 2 description</description>
      <help>command 2 help</help>
      <aliases/>
      <arguments>
        <argument name="argument_name" is_required="1" is_array="0">
          <description></description>
          <defaults/>
        </argument>
      </arguments>
      <options>
        <option name="--option_name" shortcut="-o" accept_value="0" is_value_required="0" is_multiple="0">
          <description></description>
        </option>
        <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this help message.</description>
        </option>
        <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not output any message.</description>
        </option>
        <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
        </option>
        <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Display this application version.</description>
        </option>
        <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Force ANSI output.</description>
        </option>
        <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Disable ANSI output.</description>
        </option>
        <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
          <description>Do not ask any interactive question.</description>
        </option>
      </options>
    </command>
  </commands>
  <namespaces>
    <namespace id="_global">
      <command>alias1</command>
      <command>alias2</command>
      <command>help</command>
      <command>list</command>
    </namespace>
    <namespace id="descriptor">
      <command>descriptor:command1</command>
      <command>descriptor:command2</command>
    </namespace>
  </namespaces>
</symfony>
<info>Console Tool</info>

<comment>Usage:</comment>
  [options] command [arguments]

<comment>Options:</comment>
  <info>--help</info>           <info>-h</info> Display this help message.
  <info>--quiet</info>          <info>-q</info> Do not output any message.
  <info>--verbose</info>        <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  <info>--version</info>        <info>-V</info> Display this application version.
  <info>--ansi</info>              Force ANSI output.
  <info>--no-ansi</info>           Disable ANSI output.
  <info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.

<comment>Available commands:</comment>
  <info>afoobar  </info> The foo:bar command
  <info>help     </info> Displays help for a command
  <info>list     </info> Lists commands
<comment>foo</comment>
  <info>foo:bar  </info> The foo:bar command<info>Console Tool</info>

<comment>Usage:</comment>
  [options] command [arguments]

<comment>Options:</comment>
  <info>--help</info>           <info>-h</info> Display this help message.
  <info>--quiet</info>          <info>-q</info> Do not output any message.
  <info>--verbose</info>        <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  <info>--version</info>        <info>-V</info> Display this application version.
  <info>--ansi</info>              Force ANSI output.
  <info>--no-ansi</info>           Disable ANSI output.
  <info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.

<comment>Available commands for the "foo" namespace:</comment>
  <info>foo:bar  </info> The foo:bar command<?xml version="1.0" encoding="UTF-8"?>
<symfony>
  <commands>
    <command id="help" name="help">
  <usage>help [--xml] [--format="..."] [--raw] [command_name]</usage>
  <description>Displays help for a command</description>
  <help>The &lt;info&gt;help&lt;/info&gt; command displays help for a given command:
 
   &lt;info&gt;php app/console help list&lt;/info&gt;
 
 You can also output the help in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console help --format=xml list&lt;/info&gt;
 
 To display the list of available commands, please use the &lt;info&gt;list&lt;/info&gt; command.</help>
  <aliases />
  <arguments>
    <argument name="command_name" is_required="0" is_array="0">
      <description>The command name</description>
      <defaults>
        <default>help</default>
      </defaults>
    </argument>
  </arguments>
  <options>
    <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>To output help as XML</description>
    </option>
    <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
      <description>To output help in other formats</description>
      <defaults/>
    </option>
    <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>To output raw command help</description>
    </option>
    <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Display this help message.</description>
    </option>
    <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Do not output any message.</description>
    </option>
    <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
    </option>
    <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Display this application version.</description>
    </option>
    <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Force ANSI output.</description>
    </option>
    <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Disable ANSI output.</description>
    </option>
    <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Do not ask any interactive question.</description>
    </option>
  </options>
</command>
    <command id="list" name="list">
  <usage>list [--xml] [--raw] [--format="..."] [namespace]</usage>
  <description>Lists commands</description>
  <help>The &lt;info&gt;list&lt;/info&gt; command lists all commands:
 
   &lt;info&gt;php app/console list&lt;/info&gt;
 
 You can also display the commands for a specific namespace:
 
   &lt;info&gt;php app/console list test&lt;/info&gt;
 
 You can also output the information in other formats by using the &lt;comment&gt;--format&lt;/comment&gt; option:
 
   &lt;info&gt;php app/console list --format=xml&lt;/info&gt;
 
 It's also possible to get raw list of commands (useful for embedding command runner):
 
   &lt;info&gt;php app/console list --raw&lt;/info&gt;</help>
  <aliases/>
  <arguments>
    <argument name="namespace" is_required="0" is_array="0">
      <description>The namespace name</description>
      <defaults/>
    </argument>
  </arguments>
  <options>
    <option name="--xml" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>To output list as XML</description>
    </option>
    <option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>To output raw command list</description>
    </option>
    <option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
      <description>To output list in other formats</description>
      <defaults/>
    </option>
  </options>
</command>
    <command id="foo:bar" name="foo:bar">
  <usage>foo:bar</usage>
  <description>The foo:bar command</description>
  <help/>
  <aliases>
    <alias>afoobar</alias>
  </aliases>
  <arguments/>
    <options>
      <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Display this help message.</description>
      </option>
      <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Do not output any message.</description>
      </option>
      <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
      </option>
      <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Display this application version.</description>
      </option>
      <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Force ANSI output.</description>
      </option>
      <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Disable ANSI output.</description>
      </option>
      <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Do not ask any interactive question.</description>
      </option>
    </options>
</command>
  </commands>
  <namespaces>
    <namespace id="_global">
      <command>afoobar</command>
      <command>help</command>
      <command>list</command>
    </namespace>
    <namespace id="foo">
      <command>foo:bar</command>
    </namespace>
  </namespaces>
</symfony>
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
  <commands namespace="foo">
    <command id="foo:bar" name="foo:bar">
  <usage>foo:bar</usage>
  <description>The foo:bar command</description>
  <help/>
  <aliases>
    <alias>afoobar</alias>
  </aliases>
  <arguments/>
    <options>
      <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Display this help message.</description>
      </option>
      <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Do not output any message.</description>
      </option>
      <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
      </option>
      <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Display this application version.</description>
      </option>
      <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Force ANSI output.</description>
      </option>
      <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Disable ANSI output.</description>
      </option>
      <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
        <description>Do not ask any interactive question.</description>
      </option>
    </options>
</command>
  </commands>
</symfony>
<info>Console Tool</info>

<comment>Usage:</comment>
  [options] command [arguments]

<comment>Options:</comment>
  <info>--help</info>           <info>-h</info> Display this help message.
  <info>--quiet</info>          <info>-q</info> Do not output any message.
  <info>--verbose</info>        <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  <info>--version</info>        <info>-V</info> Display this application version.
  <info>--ansi</info>              Force ANSI output.
  <info>--no-ansi</info>           Disable ANSI output.
  <info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.

                                 
  [InvalidArgumentException]     
  Command "foo" is not defined.  
                                 




                                      
  [InvalidArgumentException]          
  The "--foo" option does not exist.  
                                      


list [--xml] [--raw] [--format="..."] [namespace]




                    
  [Exception]       
  Second exception  
                    




                   
  [Exception]      
  First exception  
                   


foo3:bar




                               
  [InvalidArgumentException]   
  Command "foo" is not define  
  d.                           
                               


Console Tool

Usage:
  [options] command [arguments]

Options:
  --help           -h Display this help message.
  --quiet          -q Do not output any message.
  --verbose        -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  --version        -V Display this application version.
  --ansi              Force ANSI output.
  --no-ansi           Disable ANSI output.
  --no-interaction -n Do not ask any interactive question.

Available commands:
  help   Displays help for a command
  list   Lists commands
Usage:
 help [--xml] [--format="..."] [--raw] [command_name]

Arguments:
 command               The command to execute
 command_name          The command name (default: "help")

Options:
 --xml                 To output help as XML
 --format              To output help in other formats
 --raw                 To output raw command help
 --help (-h)           Display this help message.
 --quiet (-q)          Do not output any message.
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version.
 --ansi                Force ANSI output.
 --no-ansi             Disable ANSI output.
 --no-interaction (-n) Do not ask any interactive question.

Help:
 The help command displays help for a given command:
 
   php app/console help list
 
 You can also output the help in other formats by using the --format option:
 
   php app/console help --format=xml list
 
 To display the list of available commands, please use the list command.

Usage:
 list [--xml] [--raw] [--format="..."] [namespace]

Arguments:
 namespace  The namespace name

Options:
 --xml      To output list as XML
 --raw      To output raw command list
 --format   To output list in other formats

Help:
 The list command lists all commands:
 
   php app/console list
 
 You can also display the commands for a specific namespace:
 
   php app/console list test
 
 You can also output the information in other formats by using the --format option:
 
   php app/console list --format=xml
 
 It's also possible to get raw list of commands (useful for embedding command runner):
 
   php app/console list --raw

Console Tool
{"name":"descriptor:command1","usage":"descriptor:command1","description":"command 1 description","help":"command 1 help","aliases":["alias1","alias2"],"definition":{"arguments":[],"options":[]}}
descriptor:command1
-------------------

* Description: command 1 description
* Usage: `descriptor:command1`
* Aliases: `alias1`, `alias2`

command 1 help
<comment>Usage:</comment>
 descriptor:command1

<comment>Aliases:</comment> <info>alias1, alias2</info>

<comment>Help:</comment>
 command 1 help
<?xml version="1.0" encoding="UTF-8"?>
<command id="descriptor:command1" name="descriptor:command1">
  <usage>descriptor:command1</usage>
  <description>command 1 description</description>
  <help>command 1 help</help>
  <aliases>
    <alias>alias1</alias>
    <alias>alias2</alias>
  </aliases>
  <arguments/>
  <options/>
</command>
{"name":"descriptor:command2","usage":"descriptor:command2 [-o|--option_name] argument_name","description":"command 2 description","help":"command 2 help","aliases":[],"definition":{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}}}
descriptor:command2
-------------------

* Description: command 2 description
* Usage: `descriptor:command2 [-o|--option_name] argument_name`
* Aliases: <none>

command 2 help

### Arguments:

**argument_name:**

* Name: argument_name
* Is required: yes
* Is array: no
* Description: <none>
* Default: `NULL`

### Options:

**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: <none>
* Default: `false`
<comment>Usage:</comment>
 descriptor:command2 [-o|--option_name] argument_name

<comment>Arguments:</comment>
 <info>argument_name     </info> 

<comment>Options:</comment>
 <info>--option_name</info> (-o) 

<comment>Help:</comment>
 command 2 help
<?xml version="1.0" encoding="UTF-8"?>
<command id="descriptor:command2" name="descriptor:command2">
  <usage>descriptor:command2 [-o|--option_name] argument_name</usage>
  <description>command 2 description</description>
  <help>command 2 help</help>
  <aliases/>
  <arguments>
    <argument name="argument_name" is_required="1" is_array="0">
      <description></description>
      <defaults/>
    </argument>
  </arguments>
  <options>
    <option name="--option_name" shortcut="-o" accept_value="0" is_value_required="0" is_multiple="0">
      <description></description>
    </option>
  </options>
</command>
<comment>Usage:</comment>
 namespace:name

<comment>Aliases:</comment> <info>name</info>
<comment>Arguments:</comment>
 <info>command              </info> The command to execute

<comment>Options:</comment>
 <info>--help</info> (-h)           Display this help message.
 <info>--quiet</info> (-q)          Do not output any message.
 <info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
 <info>--version</info> (-V)        Display this application version.
 <info>--ansi</info>                Force ANSI output.
 <info>--no-ansi</info>             Disable ANSI output.
 <info>--no-interaction</info> (-n) Do not ask any interactive question.

<comment>Help:</comment>
 help
<?xml version="1.0" encoding="UTF-8"?>
<command id="namespace:name" name="namespace:name">
  <usage>namespace:name</usage>
  <description>description</description>
  <help>help</help>
  <aliases>
    <alias>name</alias>
  </aliases>
  <arguments>
    <argument name="command" is_required="1" is_array="0">
      <description>The command to execute</description>
      <defaults/>
    </argument>
  </arguments>
  <options>
    <option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Display this help message.</description>
    </option>
    <option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Do not output any message.</description>
    </option>
    <option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
    </option>
    <option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Display this application version.</description>
    </option>
    <option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Force ANSI output.</description>
    </option>
    <option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Disable ANSI output.</description>
    </option>
    <option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
      <description>Do not ask any interactive question.</description>
    </option>
  </options>
</command>
<comment>Arguments:</comment>
 <info>foo       </info> The foo argument
 <info>baz       </info> The baz argument<comment> (default: true)</comment>
 <info>bar       </info> The bar argument<comment> (default: ["http://foo.com/"])</comment>

<comment>Options:</comment>
 <info>--foo</info> (-f) The foo option
 <info>--baz</info>      The baz option<comment> (default: false)</comment>
 <info>--bar</info> (-b) The bar option<comment> (default: "bar")</comment>
 <info>--qux</info>      The qux option<comment> (default: ["http://foo.com/","bar"])</comment><comment> (multiple values allowed)</comment>
 <info>--qux2</info>     The qux2 option<comment> (default: {"foo":"bar"})</comment><comment> (multiple values allowed)</comment>
<?xml version="1.0" encoding="UTF-8"?>
<definition>
  <arguments>
    <argument name="foo" is_required="0" is_array="0">
      <description>The foo argument</description>
      <defaults/>
    </argument>
    <argument name="baz" is_required="0" is_array="0">
      <description>The baz argument</description>
      <defaults>
        <default>true</default>
      </defaults>
    </argument>
    <argument name="bar" is_required="0" is_array="1">
      <description>The bar argument</description>
      <defaults>
        <default>bar</default>
      </defaults>
    </argument>
  </arguments>
  <options>
    <option name="--foo" shortcut="-f" accept_value="1" is_value_required="1" is_multiple="0">
      <description>The foo option</description>
      <defaults/>
    </option>
    <option name="--baz" shortcut="" accept_value="1" is_value_required="0" is_multiple="0">
      <description>The baz option</description>
      <defaults>
        <default>false</default>
      </defaults>
    </option>
    <option name="--bar" shortcut="-b" accept_value="1" is_value_required="0" is_multiple="0">
      <description>The bar option</description>
      <defaults>
        <default>bar</default>
      </defaults>
    </option>
  </options>
</definition>
{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}
**argument_name:**

* Name: argument_name
* Is required: yes
* Is array: no
* Description: <none>
* Default: `NULL`
 <info>argument_name</info>
<?xml version="1.0" encoding="UTF-8"?>
<argument name="argument_name" is_required="1" is_array="0">
  <description></description>
  <defaults/>
</argument>
{"name":"argument_name","is_required":false,"is_array":true,"description":"argument description","default":[]}
**argument_name:**

* Name: argument_name
* Is required: no
* Is array: yes
* Description: argument description
* Default: `array ()`
 <info>argument_name</info> argument description
<?xml version="1.0" encoding="UTF-8"?>
<argument name="argument_name" is_required="0" is_array="1">
  <description>argument description</description>
  <defaults/>
</argument>
{"name":"argument_name","is_required":false,"is_array":false,"description":"argument description","default":"default_value"}
**argument_name:**

* Name: argument_name
* Is required: no
* Is array: no
* Description: argument description
* Default: `'default_value'`
 <info>argument_name</info> argument description<comment> (default: "default_value")</comment>
<?xml version="1.0" encoding="UTF-8"?>
<argument name="argument_name" is_required="0" is_array="0">
  <description>argument description</description>
  <defaults>
    <default>default_value</default>
  </defaults>
</argument>
{"arguments":[],"options":[]}
<?xml version="1.0" encoding="UTF-8"?>
<definition>
  <arguments/>
  <options/>
</definition>
{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":[]}
### Arguments:

**argument_name:**

* Name: argument_name
* Is required: yes
* Is array: no
* Description: <none>
* Default: `NULL`
<comment>Arguments:</comment>
 <info>argument_name </info>
<?xml version="1.0" encoding="UTF-8"?>
<definition>
  <arguments>
    <argument name="argument_name" is_required="1" is_array="0">
      <description></description>
      <defaults/>
    </argument>
  </arguments>
  <options/>
</definition>
{"arguments":[],"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}}
### Options:

**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: <none>
* Default: `false`
<comment>Options:</comment>
 <info>--option_name</info> (-o)
<?xml version="1.0" encoding="UTF-8"?>
<definition>
  <arguments/>
  <options>
    <option name="--option_name" shortcut="-o" accept_value="0" is_value_required="0" is_multiple="0">
      <description></description>
    </option>
  </options>
</definition>
{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}}
### Arguments:

**argument_name:**

* Name: argument_name
* Is required: yes
* Is array: no
* Description: <none>
* Default: `NULL`

### Options:

**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: <none>
* Default: `false`
<comment>Arguments:</comment>
 <info>argument_name     </info> 

<comment>Options:</comment>
 <info>--option_name</info> (-o)
<?xml version="1.0" encoding="UTF-8"?>
<definition>
  <arguments>
    <argument name="argument_name" is_required="1" is_array="0">
      <description></description>
      <defaults/>
    </argument>
  </arguments>
  <options>
    <option name="--option_name" shortcut="-o" accept_value="0" is_value_required="0" is_multiple="0">
      <description></description>
    </option>
  </options>
</definition>
{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}
**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: no
* Is value required: no
* Is multiple: no
* Description: <none>
* Default: `false`
 <info>--option_name</info> (-o)
<?xml version="1.0" encoding="UTF-8"?>
<option name="--option_name" shortcut="-o" accept_value="0" is_value_required="0" is_multiple="0">
  <description></description>
</option>
{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":false,"is_multiple":false,"description":"option description","default":"default_value"}
**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: yes
* Is value required: no
* Is multiple: no
* Description: option description
* Default: `'default_value'`
 <info>--option_name</info> (-o) option description<comment> (default: "default_value")</comment>
<?xml version="1.0" encoding="UTF-8"?>
<option name="--option_name" shortcut="-o" accept_value="1" is_value_required="0" is_multiple="0">
  <description>option description</description>
  <defaults>
    <default>default_value</default>
  </defaults>
</option>
{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"option description","default":null}
**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: option description
* Default: `NULL`
 <info>--option_name</info> (-o) option description
<?xml version="1.0" encoding="UTF-8"?>
<option name="--option_name" shortcut="-o" accept_value="1" is_value_required="1" is_multiple="0">
  <description>option description</description>
  <defaults/>
</option>
{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":false,"is_multiple":true,"description":"option description","default":[]}
**option_name:**

* Name: `--option_name`
* Shortcut: `-o`
* Accept value: yes
* Is value required: no
* Is multiple: yes
* Description: option description
* Default: `array ()`
 <info>--option_name</info> (-o) option description<comment> (multiple values allowed)</comment>
<?xml version="1.0" encoding="UTF-8"?>
<option name="--option_name" shortcut="-o" accept_value="1" is_value_required="0" is_multiple="1">
  <description>option description</description>
  <defaults/>
</option>
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Formatter;

use Symfony\Component\Console\Formatter\OutputFormatterStyleStack;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class OutputFormatterStyleStackTest extends \PHPUnit_Framework_TestCase
{
    public function testPush()
    {
        $stack = new OutputFormatterStyleStack();
        $stack->push($s1 = new OutputFormatterStyle('white', 'black'));
        $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue'));

        $this->assertEquals($s2, $stack->getCurrent());

        $stack->push($s3 = new OutputFormatterStyle('green', 'red'));

        $this->assertEquals($s3, $stack->getCurrent());
    }

    public function testPop()
    {
        $stack = new OutputFormatterStyleStack();
        $stack->push($s1 = new OutputFormatterStyle('white', 'black'));
        $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue'));

        $this->assertEquals($s2, $stack->pop());
        $this->assertEquals($s1, $stack->pop());
    }

    public function testPopEmpty()
    {
        $stack = new OutputFormatterStyleStack();
        $style = new OutputFormatterStyle();

        $this->assertEquals($style, $stack->pop());
    }

    public function testPopNotLast()
    {
        $stack = new OutputFormatterStyleStack();
        $stack->push($s1 = new OutputFormatterStyle('white', 'black'));
        $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue'));
        $stack->push($s3 = new OutputFormatterStyle('green', 'red'));

        $this->assertEquals($s2, $stack->pop($s2));
        $this->assertEquals($s1, $stack->pop());
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testInvalidPop()
    {
        $stack = new OutputFormatterStyleStack();
        $stack->push(new OutputFormatterStyle('white', 'black'));
        $stack->pop(new OutputFormatterStyle('yellow', 'blue'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Formatter;

use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class OutputFormatterStyleTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $style = new OutputFormatterStyle('green', 'black', array('bold', 'underscore'));
        $this->assertEquals("\033[32;40;1;4mfoo\033[0m", $style->apply('foo'));

        $style = new OutputFormatterStyle('red', null, array('blink'));
        $this->assertEquals("\033[31;5mfoo\033[0m", $style->apply('foo'));

        $style = new OutputFormatterStyle(null, 'white');
        $this->assertEquals("\033[47mfoo\033[0m", $style->apply('foo'));
    }

    public function testForeground()
    {
        $style = new OutputFormatterStyle();

        $style->setForeground('black');
        $this->assertEquals("\033[30mfoo\033[0m", $style->apply('foo'));

        $style->setForeground('blue');
        $this->assertEquals("\033[34mfoo\033[0m", $style->apply('foo'));

        $this->setExpectedException('InvalidArgumentException');
        $style->setForeground('undefined-color');
    }

    public function testBackground()
    {
        $style = new OutputFormatterStyle();

        $style->setBackground('black');
        $this->assertEquals("\033[40mfoo\033[0m", $style->apply('foo'));

        $style->setBackground('yellow');
        $this->assertEquals("\033[43mfoo\033[0m", $style->apply('foo'));

        $this->setExpectedException('InvalidArgumentException');
        $style->setBackground('undefined-color');
    }

    public function testOptions()
    {
        $style = new OutputFormatterStyle();

        $style->setOptions(array('reverse', 'conceal'));
        $this->assertEquals("\033[7;8mfoo\033[0m", $style->apply('foo'));

        $style->setOption('bold');
        $this->assertEquals("\033[7;8;1mfoo\033[0m", $style->apply('foo'));

        $style->unsetOption('reverse');
        $this->assertEquals("\033[8;1mfoo\033[0m", $style->apply('foo'));

        $style->setOption('bold');
        $this->assertEquals("\033[8;1mfoo\033[0m", $style->apply('foo'));

        $style->setOptions(array('bold'));
        $this->assertEquals("\033[1mfoo\033[0m", $style->apply('foo'));

        try {
            $style->setOption('foo');
            $this->fail('->setOption() throws an \InvalidArgumentException when the option does not exist in the available options');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options');
            $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options');
        }

        try {
            $style->unsetOption('foo');
            $this->fail('->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options');
            $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available 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\Tests\Formatter;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class FormatterStyleTest extends \PHPUnit_Framework_TestCase
{
    public function testEmptyTag()
    {
        $formatter = new OutputFormatter(true);
        $this->assertEquals("foo<>bar", $formatter->format('foo<>bar'));
    }

    public function testLGCharEscaping()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals("foo<bar", $formatter->format('foo\\<bar'));
        $this->assertEquals("<info>some info</info>", $formatter->format('\\<info>some info\\</info>'));
        $this->assertEquals("\\<info>some info\\</info>", OutputFormatter::escape('<info>some info</info>'));

        $this->assertEquals(
            "\033[33mSymfony\\Component\\Console does work very well!\033[0m",
            $formatter->format('<comment>Symfony\Component\Console does work very well!</comment>')
        );
    }

    public function testBundledStyles()
    {
        $formatter = new OutputFormatter(true);

        $this->assertTrue($formatter->hasStyle('error'));
        $this->assertTrue($formatter->hasStyle('info'));
        $this->assertTrue($formatter->hasStyle('comment'));
        $this->assertTrue($formatter->hasStyle('question'));

        $this->assertEquals(
            "\033[37;41msome error\033[0m",
            $formatter->format('<error>some error</error>')
        );
        $this->assertEquals(
            "\033[32msome info\033[0m",
            $formatter->format('<info>some info</info>')
        );
        $this->assertEquals(
            "\033[33msome comment\033[0m",
            $formatter->format('<comment>some comment</comment>')
        );
        $this->assertEquals(
            "\033[30;46msome question\033[0m",
            $formatter->format('<question>some question</question>')
        );
    }

    public function testNestedStyles()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals(
            "\033[37;41msome \033[0m\033[32msome info\033[0m\033[37;41m error\033[0m",
            $formatter->format('<error>some <info>some info</info> error</error>')
        );
    }

    public function testStyleMatchingNotGreedy()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals(
            "(\033[32m>=2.0,<2.3\033[0m)",
            $formatter->format('(<info>>=2.0,<2.3</info>)')
        );
    }

    public function testStyleEscaping()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals(
            "(\033[32mz>=2.0,<a2.3\033[0m)",
            $formatter->format('(<info>'.$formatter->escape('z>=2.0,<a2.3').'</info>)')
        );
    }

    public function testDeepNestedStyles()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals(
            "\033[37;41merror\033[0m\033[32minfo\033[0m\033[33mcomment\033[0m\033[37;41merror\033[0m",
            $formatter->format('<error>error<info>info<comment>comment</info>error</error>')
        );
    }

    public function testNewStyle()
    {
        $formatter = new OutputFormatter(true);

        $style = new OutputFormatterStyle('blue', 'white');
        $formatter->setStyle('test', $style);

        $this->assertEquals($style, $formatter->getStyle('test'));
        $this->assertNotEquals($style, $formatter->getStyle('info'));

        $this->assertEquals("\033[34;47msome custom msg\033[0m", $formatter->format('<test>some custom msg</test>'));
    }

    public function testRedefineStyle()
    {
        $formatter = new OutputFormatter(true);

        $style = new OutputFormatterStyle('blue', 'white');
        $formatter->setStyle('info', $style);

        $this->assertEquals("\033[34;47msome custom msg\033[0m", $formatter->format('<info>some custom msg</info>'));
    }

    public function testInlineStyle()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals("\033[34;41msome text\033[0m", $formatter->format('<fg=blue;bg=red>some text</>'));
        $this->assertEquals("\033[34;41msome text\033[0m", $formatter->format('<fg=blue;bg=red>some text</fg=blue;bg=red>'));
    }

    public function testNonStyleTag()
    {
        $formatter = new OutputFormatter(true);
        $this->assertEquals("\033[32msome \033[0m\033[32m<tag> styled\033[0m", $formatter->format('<info>some <tag> styled</info>'));
    }

    public function testNotDecoratedFormatter()
    {
        $formatter = new OutputFormatter(false);

        $this->assertTrue($formatter->hasStyle('error'));
        $this->assertTrue($formatter->hasStyle('info'));
        $this->assertTrue($formatter->hasStyle('comment'));
        $this->assertTrue($formatter->hasStyle('question'));

        $this->assertEquals(
            "some error", $formatter->format('<error>some error</error>')
        );
        $this->assertEquals(
            "some info", $formatter->format('<info>some info</info>')
        );
        $this->assertEquals(
            "some comment", $formatter->format('<comment>some comment</comment>')
        );
        $this->assertEquals(
            "some question", $formatter->format('<question>some question</question>')
        );

        $formatter->setDecorated(true);

        $this->assertEquals(
            "\033[37;41msome error\033[0m", $formatter->format('<error>some error</error>')
        );
        $this->assertEquals(
            "\033[32msome info\033[0m", $formatter->format('<info>some info</info>')
        );
        $this->assertEquals(
            "\033[33msome comment\033[0m", $formatter->format('<comment>some comment</comment>')
        );
        $this->assertEquals(
            "\033[30;46msome question\033[0m", $formatter->format('<question>some question</question>')
        );
    }

    public function testContentWithLineBreaks()
    {
        $formatter = new OutputFormatter(true);

        $this->assertEquals(<<<EOF
\033[32m
some text\033[0m
EOF
            , $formatter->format(<<<EOF
<info>
some text</info>
EOF
        ));

        $this->assertEquals(<<<EOF
\033[32msome text
\033[0m
EOF
            , $formatter->format(<<<EOF
<info>some text
</info>
EOF
        ));

        $this->assertEquals(<<<EOF
\033[32m
some text
\033[0m
EOF
            , $formatter->format(<<<EOF
<info>
some text
</info>
EOF
        ));

        $this->assertEquals(<<<EOF
\033[32m
some text
more text
\033[0m
EOF
            , $formatter->format(<<<EOF
<info>
some text
more text
</info>
EOF
        ));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Helper;

use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Output\StreamOutput;

class DialogHelperTest extends \PHPUnit_Framework_TestCase
{
    public function testSelect()
    {
        $dialog = new DialogHelper();

        $helperSet = new HelperSet(array(new FormatterHelper()));
        $dialog->setHelperSet($helperSet);

        $heroes = array('Superman', 'Batman', 'Spiderman');

        $dialog->setInputStream($this->getInputStream("\n1\n  1  \nFabien\n1\nFabien\n1\n0,2\n 0 , 2  \n\n\n"));
        $this->assertEquals('2', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '2'));
        $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes));
        $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes));
        $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', false));

        rewind($output->getStream());
        $this->assertContains('Input "Fabien" is not a superhero!', stream_get_contents($output->getStream()));

        try {
            $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, 1));
            $this->fail();
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals('Value "Fabien" is invalid', $e->getMessage());
        }

        $this->assertEquals(array('1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true));
        $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true));
        $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true));
        $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '0,1', false, 'Input "%s" is not a superhero!', true));
        $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, ' 0 , 1 ', false, 'Input "%s" is not a superhero!', true));
    }

    public function testAsk()
    {
        $dialog = new DialogHelper();

        $dialog->setInputStream($this->getInputStream("\n8AM\n"));

        $this->assertEquals('2PM', $dialog->ask($this->getOutputStream(), 'What time is it?', '2PM'));
        $this->assertEquals('8AM', $dialog->ask($output = $this->getOutputStream(), 'What time is it?', '2PM'));

        rewind($output->getStream());
        $this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
    }

    public function testAskWithAutocomplete()
    {
        if (!$this->hasSttyAvailable()) {
            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
        }

        // Acm<NEWLINE>
        // Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
        // <NEWLINE>
        // <UP ARROW><UP ARROW><NEWLINE>
        // <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
        // <DOWN ARROW><NEWLINE>
        // S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
        // F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
        $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n");

        $dialog = new DialogHelper();
        $dialog->setInputStream($inputStream);

        $bundles = array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle');

        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('AsseticBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('FrameworkBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('SecurityBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('FooBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('AsseticBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
        $this->assertEquals('FooBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles));
    }

    public function testAskHiddenResponse()
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->markTestSkipped('This test is not supported on Windows');
        }

        $dialog = new DialogHelper();

        $dialog->setInputStream($this->getInputStream("8AM\n"));

        $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?'));
    }

    public function testAskConfirmation()
    {
        $dialog = new DialogHelper();

        $dialog->setInputStream($this->getInputStream("\n\n"));
        $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?'));
        $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false));

        $dialog->setInputStream($this->getInputStream("y\nyes\n"));
        $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false));
        $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false));

        $dialog->setInputStream($this->getInputStream("n\nno\n"));
        $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true));
        $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true));
    }

    public function testAskAndValidate()
    {
        $dialog = new DialogHelper();
        $helperSet = new HelperSet(array(new FormatterHelper()));
        $dialog->setHelperSet($helperSet);

        $question ='What color was the white horse of Henry IV?';
        $error = 'This is not a color!';
        $validator = function ($color) use ($error) {
            if (!in_array($color, array('white', 'black'))) {
                throw new \InvalidArgumentException($error);
            }

            return $color;
        };

        $dialog->setInputStream($this->getInputStream("\nblack\n"));
        $this->assertEquals('white', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white'));
        $this->assertEquals('black', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white'));

        $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n"));
        try {
            $this->assertEquals('white', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white'));
            $this->fail();
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals($error, $e->getMessage());
        }
    }

    protected function getInputStream($input)
    {
        $stream = fopen('php://memory', 'r+', false);
        fputs($stream, $input);
        rewind($stream);

        return $stream;
    }

    protected function getOutputStream()
    {
        return new StreamOutput(fopen('php://memory', 'r+', false));
    }

    private function hasSttyAvailable()
    {
        exec('stty 2>&1', $output, $exitcode);

        return $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\Tests\Helper;

use Symfony\Component\Console\Helper\FormatterHelper;

class FormatterHelperTest extends \PHPUnit_Framework_TestCase
{
    public function testFormatSection()
    {
        $formatter = new FormatterHelper();

        $this->assertEquals(
            '<info>[cli]</info> Some text to display',
            $formatter->formatSection('cli', 'Some text to display'),
            '::formatSection() formats a message in a section'
        );
    }

    public function testFormatBlock()
    {
        $formatter = new FormatterHelper();

        $this->assertEquals(
            '<error> Some text to display </error>',
            $formatter->formatBlock('Some text to display', 'error'),
            '::formatBlock() formats a message in a block'
        );

        $this->assertEquals(
            '<error> Some text to display </error>'."\n" .
            '<error> foo bar              </error>',
            $formatter->formatBlock(array('Some text to display', 'foo bar'), 'error'),
            '::formatBlock() formats a message in a block'
        );

        $this->assertEquals(
            '<error>                        </error>'."\n" .
            '<error>  Some text to display  </error>'."\n" .
            '<error>                        </error>',
            $formatter->formatBlock('Some text to display', 'error', true),
            '::formatBlock() formats a message in a block'
        );
    }

    public function testFormatBlockWithDiacriticLetters()
    {
        if (!extension_loaded('mbstring')) {
            $this->markTestSkipped('This test requires mbstring to work.');
        }

        $formatter = new FormatterHelper();

        $this->assertEquals(
            '<error>                       </error>'."\n" .
            '<error>  Du texte à afficher  </error>'."\n" .
            '<error>                       </error>',
            $formatter->formatBlock('Du texte à afficher', 'error', true),
            '::formatBlock() formats a message in a block'
        );
    }

    public function testFormatBlockLGEscaping()
    {
        $formatter = new FormatterHelper();

        $this->assertEquals(
            '<error>                            </error>'."\n" .
            '<error>  \<info>some info\</info>  </error>'."\n" .
            '<error>                            </error>',
            $formatter->formatBlock('<info>some info</info>', 'error', true),
            '::formatBlock() escapes \'<\' chars'
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Helper;

use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Command\Command;

class HelperSetTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::__construct
     */
    public function testConstructor()
    {
        $mock_helper = $this->getGenericMockHelper('fake_helper');
        $helperset = new HelperSet(array('fake_helper_alias' => $mock_helper));

        $this->assertEquals($mock_helper, $helperset->get('fake_helper_alias'), '__construct sets given helper to helpers');
        $this->assertTrue($helperset->has('fake_helper_alias'), '__construct sets helper alias for given helper');
    }

    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::set
     */
    public function testSet()
    {
        $helperset = new HelperSet();
        $helperset->set($this->getGenericMockHelper('fake_helper', $helperset));
        $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper to helpers');

        $helperset = new HelperSet();
        $helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset));
        $helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset));
        $this->assertTrue($helperset->has('fake_helper_01'), '->set() will set multiple helpers on consecutive calls');
        $this->assertTrue($helperset->has('fake_helper_02'), '->set() will set multiple helpers on consecutive calls');

        $helperset = new HelperSet();
        $helperset->set($this->getGenericMockHelper('fake_helper', $helperset), 'fake_helper_alias');
        $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper alias when set');
        $this->assertTrue($helperset->has('fake_helper_alias'), '->set() adds helper alias when set');
    }

    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::has
     */
    public function testHas()
    {
        $helperset = new HelperSet(array('fake_helper_alias' => $this->getGenericMockHelper('fake_helper')));
        $this->assertTrue($helperset->has('fake_helper'), '->has() finds set helper');
        $this->assertTrue($helperset->has('fake_helper_alias'), '->has() finds set helper by alias');
    }

    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::get
     */
    public function testGet()
    {
        $helper_01 = $this->getGenericMockHelper('fake_helper_01');
        $helper_02 = $this->getGenericMockHelper('fake_helper_02');
        $helperset = new HelperSet(array('fake_helper_01_alias' => $helper_01, 'fake_helper_02_alias' => $helper_02));
        $this->assertEquals($helper_01, $helperset->get('fake_helper_01'), '->get() returns correct helper by name');
        $this->assertEquals($helper_01, $helperset->get('fake_helper_01_alias'), '->get() returns correct helper by alias');
        $this->assertEquals($helper_02, $helperset->get('fake_helper_02'), '->get() returns correct helper by name');
        $this->assertEquals($helper_02, $helperset->get('fake_helper_02_alias'), '->get() returns correct helper by alias');

        $helperset = new HelperSet();
        try {
            $helperset->get('foo');
            $this->fail('->get() throws \InvalidArgumentException when helper not found');
        } catch (\Exception $e) {
            $this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws \InvalidArgumentException when helper not found');
            $this->assertContains('The helper "foo" is not defined.', $e->getMessage(), '->get() throws \InvalidArgumentException when helper not found');
        }
    }

    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::setCommand
     */
    public function testSetCommand()
    {
        $cmd_01 = new Command('foo');
        $cmd_02 = new Command('bar');

        $helperset = new HelperSet();
        $helperset->setCommand($cmd_01);
        $this->assertEquals($cmd_01, $helperset->getCommand(), '->setCommand() stores given command');

        $helperset = new HelperSet();
        $helperset->setCommand($cmd_01);
        $helperset->setCommand($cmd_02);
        $this->assertEquals($cmd_02, $helperset->getCommand(), '->setCommand() overwrites stored command with consecutive calls');
    }

    /**
     * @covers \Symfony\Component\Console\Helper\HelperSet::getCommand
     */
    public function testGetCommand()
    {
        $cmd = new Command('foo');
        $helperset = new HelperSet();
        $helperset->setCommand($cmd);
        $this->assertEquals($cmd, $helperset->getCommand(), '->getCommand() retrieves stored command');
    }

   /**
     * Create a generic mock for the helper interface. Optionally check for a call to setHelperSet with a specific
     * helperset instance.
     *
     * @param string    $name
     * @param HelperSet $helperset allows a mock to verify a particular helperset set is being added to the Helper
     */
    private function getGenericMockHelper($name, HelperSet $helperset = null)
    {
        $mock_helper = $this->getMock('\Symfony\Component\Console\Helper\HelperInterface');
        $mock_helper->expects($this->any())
            ->method('getName')
            ->will($this->returnValue($name));

        if ($helperset) {
            $mock_helper->expects($this->any())
                ->method('setHelperSet')
                ->with($this->equalTo($helperset));
        }

        return $mock_helper;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Helper;

use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Output\StreamOutput;

class ProgressHelperTest extends \PHPUnit_Framework_TestCase
{
    public function testAdvance()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream());
        $progress->advance();

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('    1 [->--------------------------]'), stream_get_contents($output->getStream()));
    }

    public function testAdvanceWithStep()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream());
        $progress->advance(5);

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('    5 [----->----------------------]'), stream_get_contents($output->getStream()));
    }

    public function testAdvanceMultipleTimes()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream());
        $progress->advance(3);
        $progress->advance(2);

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('    3 [--->------------------------]').$this->generateOutput('    5 [----->----------------------]'), stream_get_contents($output->getStream()));
    }

    public function testCustomizations()
    {
        $progress = new ProgressHelper();
        $progress->setBarWidth(10);
        $progress->setBarCharacter('_');
        $progress->setEmptyBarCharacter(' ');
        $progress->setProgressCharacter('/');
        $progress->setFormat(' %current%/%max% [%bar%] %percent%%');
        $progress->start($output = $this->getOutputStream(), 10);
        $progress->advance();

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('  1/10 [_/        ]  10%'), stream_get_contents($output->getStream()));
    }

    public function testPercent()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream(), 50);
        $progress->display();
        $progress->advance();
        $progress->advance();

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('  0/50 [>---------------------------]   0%').$this->generateOutput('  1/50 [>---------------------------]   2%').$this->generateOutput('  2/50 [=>--------------------------]   4%'), stream_get_contents($output->getStream()));
    }

    public function testOverwriteWithShorterLine()
    {
        $progress = new ProgressHelper();
        $progress->setFormat(' %current%/%max% [%bar%] %percent%%');
        $progress->start($output = $this->getOutputStream(), 50);
        $progress->display();
        $progress->advance();

        // set shorter format
        $progress->setFormat(' %current%/%max% [%bar%]');
        $progress->advance();

        rewind($output->getStream());
        $this->assertEquals(
            $this->generateOutput('  0/50 [>---------------------------]   0%') .
            $this->generateOutput('  1/50 [>---------------------------]   2%') .
            $this->generateOutput('  2/50 [=>--------------------------]     '),
            stream_get_contents($output->getStream())
        );
    }

    public function testSetCurrentProgress()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream(), 50);
        $progress->display();
        $progress->advance();
        $progress->setCurrent(15);
        $progress->setCurrent(25);

        rewind($output->getStream());
        $this->assertEquals(
            $this->generateOutput('  0/50 [>---------------------------]   0%') .
            $this->generateOutput('  1/50 [>---------------------------]   2%') .
            $this->generateOutput(' 15/50 [========>-------------------]  30%') .
            $this->generateOutput(' 25/50 [==============>-------------]  50%'),
            stream_get_contents($output->getStream())
        );
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage You must start the progress bar
     */
    public function testSetCurrentBeforeStarting()
    {
        $progress = new ProgressHelper();
        $progress->setCurrent(15);
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage You can't regress the progress bar
     */
    public function testRegressProgress()
    {
        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream(), 50);
        $progress->setCurrent(15);
        $progress->setCurrent(10);
    }

    public function testMultiByteSupport()
    {
        if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding('■'))) {
            $this->markTestSkipped('The mbstring extension is needed for multi-byte support');
        }

        $progress = new ProgressHelper();
        $progress->start($output = $this->getOutputStream());
        $progress->setBarCharacter('■');
        $progress->advance(3);

        rewind($output->getStream());
        $this->assertEquals($this->generateOutput('    3 [■■■>------------------------]'), stream_get_contents($output->getStream()));
    }

    protected function getOutputStream()
    {
        return new StreamOutput(fopen('php://memory', 'r+', false));
    }

    protected $lastMessagesLength;

    protected function generateOutput($expected)
    {
        $expectedout = $expected;

        if ($this->lastMessagesLength !== null) {
            $expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
        }

        $this->lastMessagesLength = strlen($expectedout);

        return "\x0D".$expectedout;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Helper;

use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Output\StreamOutput;

class TableHelperTest extends \PHPUnit_Framework_TestCase
{
    protected $stream;

    protected function setUp()
    {
        $this->stream = fopen('php://memory', 'r+');
    }

    protected function tearDown()
    {
        fclose($this->stream);
        $this->stream = null;
    }

    /**
     * @dataProvider testRenderProvider
     */
    public function testRender($headers, $rows, $layout, $expected)
    {
        $table = new TableHelper();
        $table
            ->setHeaders($headers)
            ->setRows($rows)
            ->setLayout($layout)
        ;
        $table->render($output = $this->getOutputStream());

        $this->assertEquals($expected, $this->getOutputContent($output));
    }

    /**
     * @dataProvider testRenderProvider
     */
    public function testRenderAddRows($headers, $rows, $layout, $expected)
    {
        $table = new TableHelper();
        $table
            ->setHeaders($headers)
            ->addRows($rows)
            ->setLayout($layout)
        ;
        $table->render($output = $this->getOutputStream());

        $this->assertEquals($expected, $this->getOutputContent($output));
    }

    /**
     * @dataProvider testRenderProvider
     */
    public function testRenderAddRowsOneByOne($headers, $rows, $layout, $expected)
    {
        $table = new TableHelper();
        $table
            ->setHeaders($headers)
            ->setLayout($layout)
        ;
        foreach ($rows as $row) {
            $table->addRow($row);
        }
        $table->render($output = $this->getOutputStream());

        $this->assertEquals($expected, $this->getOutputContent($output));
    }

    public function testRenderProvider()
    {
        return array(
            array(
                array('ISBN', 'Title', 'Author'),
                array(
                    array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
                    array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
                    array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
                    array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
                ),
                TableHelper::LAYOUT_DEFAULT,
<<<TABLE
+---------------+--------------------------+------------------+
| 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 |
| 80-902734-1-6 | And Then There Were None | Agatha Christie  |
+---------------+--------------------------+------------------+

TABLE
            ),
            array(
                array('ISBN', 'Title', 'Author'),
                array(
                    array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
                    array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
                    array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
                    array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
                ),
                TableHelper::LAYOUT_BORDERLESS,
                " =============== ========================== ================== \n  ISBN            Title                      Author            \n =============== ========================== ================== \n  99921-58-10-7   Divine Comedy              Dante Alighieri   \n  9971-5-0210-0   A Tale of Two Cities       Charles Dickens   \n  960-425-059-0   The Lord of the Rings      J. R. R. Tolkien  \n  80-902734-1-6   And Then There Were None   Agatha Christie   \n =============== ========================== ================== \n"
            ),
            array(
                array('ISBN', 'Title'),
                array(
                    array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
                    array('9971-5-0210-0'),
                    array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
                    array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
                ),
                TableHelper::LAYOUT_DEFAULT,
<<<TABLE
+---------------+--------------------------+------------------+
| ISBN          | Title                    |                  |
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy            | Dante Alighieri  |
| 9971-5-0210-0 |                          |                  |
| 960-425-059-0 | The Lord of the Rings    | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie  |
+---------------+--------------------------+------------------+

TABLE
            ),
            array(
                array(),
                array(
                    array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
                    array('9971-5-0210-0'),
                    array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
                    array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
                ),
                TableHelper::LAYOUT_DEFAULT,
<<<TABLE
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy            | Dante Alighieri  |
| 9971-5-0210-0 |                          |                  |
| 960-425-059-0 | The Lord of the Rings    | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie  |
+---------------+--------------------------+------------------+

TABLE
            ),
            array(
                array('ISBN', 'Title'),
                array(),
                TableHelper::LAYOUT_DEFAULT,
<<<TABLE
+------+-------+
| ISBN | Title |
+------+-------+

TABLE
            ),
            array(
                array(),
                array(),
                TableHelper::LAYOUT_DEFAULT,
                '',
            ),
        );
    }

    protected function getOutputStream()
    {
        return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, false);
    }

    protected function getOutputContent(StreamOutput $output)
    {
        rewind($output->getStream());

        return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream()));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tests\Input;

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class ArgvInputTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $_SERVER['argv'] = array('cli.php', 'foo');
        $input = new ArgvInput();
        $r = new \ReflectionObject($input);
        $p = $r->getProperty('tokens');
        $p->setAccessible(true);

        $this->assertEquals(array('foo'), $p->getValue($input), '__construct() automatically get its input from the argv server variable');
    }

    public function testParseArguments()
    {
        $input = new ArgvInput(array('cli.php', 'foo'));
        $input->bind(new InputDefinition(array(new InputArgument('name'))));
        $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments');

        $input->bind(new InputDefinition(array(new InputArgument('name'))));
        $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() is stateless');
    }

    /**
     * @dataProvider provideOptions
     */
    public function testParseOptions($input, $options, $expectedOptions, $message)
    {
        $input = new ArgvInput($input);
        $input->bind(new InputDefinition($options));

        $this->assertEquals($expectedOptions, $input->getOptions(), $message);
    }

    public function provideOptions()
    {
        return array(
            array(
                array('cli.php', '--foo'),
                array(new InputOption('foo')),
                array('foo' => true),
                '->parse() parses long options without a value'
            ),
            array(
                array('cli.php', '--foo=bar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)),
                array('foo' => 'bar'),
                '->parse() parses long options with a required value (with a = separator)'
            ),
            array(
                array('cli.php', '--foo', 'bar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)),
                array('foo' => 'bar'),
                '->parse() parses long options with a required value (with a space separator)'
            ),
            array(
                array('cli.php', '-f'),
                array(new InputOption('foo', 'f')),
                array('foo' => true),
                '->parse() parses short options without a value'
            ),
            array(
                array('cli.php', '-fbar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)),
                array('foo' => 'bar'),
                '->parse() parses short options with a required value (with no separator)'
            ),
            array(
                array('cli.php', '-f', 'bar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)),
                array('foo' => 'bar'),
                '->parse() parses short options with a required value (with a space separator)'
            ),
            array(
                array('cli.php', '-f', ''),
                array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)),
                array('foo' => ''),
                '->parse() parses short options with an optional empty value'
            ),
            array(
                array('cli.php', '-f', '', 'foo'),
                array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)),
                array('foo' => ''),
                '->parse() parses short options with an optional empty value followed by an argument'
            ),
            array(
                array('cli.php', '-f', '', '-b'),
                array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')),
                array('foo' => '', 'bar' => true),
                '->parse() parses short options with an optional empty value followed by an option'
            ),
            array(
                array('cli.php', '-f', '-b', 'foo'),
                array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')),
                array('foo' => null, 'bar' => true),
                '->parse() parses short options with an optional value which is not present'
            ),
            array(
                array('cli.php', '-fb'),
                array(new InputOption('foo', 'f'), new InputOption('bar', 'b')),
                array('foo' => true, 'bar' => true),
                '->parse() parses short options when they are aggregated as a single one'
            ),
            array(
                array('cli.php', '-fb', 'bar'),
                array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_REQUIRED)),
                array('foo' => true, 'bar' => 'bar'),
                '->parse() parses short options when they are aggregated as a single one and the last one has a required value'
            ),
            array(
                array('cli.php', '-fb', 'bar'),
                array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)),
                array('foo' => true, 'bar' => 'bar'),
                '->parse() parses short options when they are aggregated as a single one and the last one has an optional value'
            ),
            array(
                array('cli.php', '-fbbar'),
                array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)),
                array('foo' => true, 'bar' => 'bar'),
                '->parse() parses short options when they are aggregated as a single one and the last one has an optional value with no separator'
            ),
            array(
                array('cli.php', '-fbbar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)),
                array('foo' => 'bbar', 'bar' => null),
                '->parse() parses short options when they are aggregated as a single one and one of them takes a value'
            )
        );
    }

    /**
     * @dataProvider provideInvalidInput
     */
    public function testInvalidInput($argv, $definition, $expectedExceptionMessage)
    {
        $this->setExpectedException('RuntimeException', $expectedExceptionMessage);

        $input = new ArgvInput($argv);
        $input->bind($definition);
    }

    public function provideInvalidInput()
    {
        return array(
            array(
                array('cli.php', '--foo'),
                new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))),
                'The "--foo" option requires a value.'
            ),
            array(
                array('cli.php', '-f'),
                new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))),
                'The "--foo" option requires a value.'
            ),
            array(
                array('cli.php', '-ffoo'),
                new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))),
                'The "-o" option does not exist.'
            ),
            array(
                array('cli.php', 'foo', 'bar'),
                new InputDefinition(),
                'Too many arguments.'
            ),
            array(
                array('cli.php', '--foo'),
                new InputDefinition(),
                'The "--foo" option does not exist.'
            ),
            array(
                array('cli.php', '-f'),
                new InputDefinition(),
                'The "-f" option does not exist.'
            ),
            array(
                array('cli.php', '-1'),
                new InputDefinition(array(new InputArgument('number'))),
                'The "-1" option does not exist.'
            )
        );
    }

    public function testParseArrayArgument()
    {
        $input = new ArgvInput(array('cli.php', 'foo', 'bar', 'baz', 'bat'));
        $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::IS_ARRAY))));

        $this->assertEquals(array('name' => array('foo', 'bar', 'baz', 'bat')), $input->getArguments(), '->parse() parses array arguments');
    }

    public function testParseArrayOption()
    {
        $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name=baz'));
        $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY))));

        $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option=value" syntax)');

        $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', 'baz'));
        $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY))));
        $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option value" syntax)');

        $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name='));
        $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY))));
        $this->assertSame(array('name' => array('foo', 'bar', null)), $input->getOptions(), '->parse() parses empty array options as null ("--option=value" syntax)');

        $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', '--anotherOption'));
        $input->bind(new InputDefinition(array(
            new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY),
            new InputOption('anotherOption', null, InputOption::VALUE_NONE),
        )));
        $this->assertSame(array('name' => array('foo', 'bar', null), 'anotherOption' => true), $input->getOptions(), '->parse() parses empty array options as null ("--option value" syntax)');
    }

    public function testParseNegativeNumberAfterDoubleDash()
    {
        $input = new ArgvInput(array('cli.php', '--', '-1'));
        $input->bind(new InputDefinition(array(new InputArgument('number'))));
        $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence');

        $input = new ArgvInput(array('cli.php', '-f', 'bar', '--', '-1'));
        $input->bind(new InputDefinition(array(new InputArgument('number'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL))));
        $this->assertEquals(array('foo' => 'bar'), $input->getOptions(), '->parse() parses arguments with leading dashes as options before having encountered a double-dash sequence');
        $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence');
    }

    public function testParseEmptyStringArgument()
    {
        $input = new ArgvInput(array('cli.php', '-f', 'bar', ''));
        $input->bind(new InputDefinition(array(new InputArgument('empty'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL))));

        $this->assertEquals(array('empty' => ''), $input->getArguments(), '->parse() parses empty string arguments');
    }

    public function testGetFirstArgument()
    {
        $input = new ArgvInput(array('cli.php', '-fbbar'));
        $this->assertEquals('', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input');

        $input = new ArgvInput(array('cli.php', '-fbbar', 'foo'));
        $this->assertEquals('foo', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input');
    }

    public function testHasParameterOption()
    {
        $input = new ArgvInput(array('cli.php', '-f', 'foo'));
        $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input');

        $input = new ArgvInput(array('cli.php', '--foo', 'foo'));
        $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input');

        $input = new ArgvInput(array('cli.php', 'foo'));
        $this->assertFalse($input->hasParameterOption('--foo'), '->hasParameterOption() returns false if the given short option is not in the raw input');
    }

    public function testToString()
    {
        $input = new ArgvInput(array('cli.php', '-f', 'foo'));
        $this->assertEquals('-f foo', (string) $input);

        $input = new ArgvInput(array('cli.php', '-f', '--bar=foo', 'a b c d', "A\nB'C"));
        $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input);
    }

    /**
     * @dataProvider provideGetParameterOptionValues
     */
    public function testGetParameterOptionEqualSign($argv, $key, $expected)
    {
        $input = new ArgvInput($argv);
        $this->assertEquals($expected, $input->getParameterOption($key), '->getParameterOption() returns the expected value');
    }

    public function provideGetParameterOptionValues()
    {
        return array(
            array(array('app/console', 'foo:bar', '-e', 'dev'), '-e', 'dev'),
            array(array('app/console', 'foo:bar', '--env=dev'), '--env', 'dev'),
            array(array('app/console', 'foo:bar', '-e', 'dev'), array('-e', '--env'), 'dev'),
            array(array('app/console', 'foo:bar', '--env=dev'), array('-e', '--env'), 'dev'),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Input;

use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class ArrayInputTest extends \PHPUnit_Framework_TestCase
{
    public function testGetFirstArgument()
    {
        $input = new ArrayInput(array());
        $this->assertNull($input->getFirstArgument(), '->getFirstArgument() returns null if no argument were passed');
        $input = new ArrayInput(array('name' => 'Fabien'));
        $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument');
        $input = new ArrayInput(array('--foo' => 'bar', 'name' => 'Fabien'));
        $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument');
    }

    public function testHasParameterOption()
    {
        $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar'));
        $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters');
        $this->assertFalse($input->hasParameterOption('--bar'), '->hasParameterOption() returns false if an option is not present in the passed parameters');

        $input = new ArrayInput(array('--foo'));
        $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters');
    }

    public function testParseArguments()
    {
        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'))));

        $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments');
    }

    /**
     * @dataProvider provideOptions
     */
    public function testParseOptions($input, $options, $expectedOptions, $message)
    {
        $input = new ArrayInput($input, new InputDefinition($options));

        $this->assertEquals($expectedOptions, $input->getOptions(), $message);
    }

    public function provideOptions()
    {
        return array(
            array(
                array('--foo' => 'bar'),
                array(new InputOption('foo')),
                array('foo' => 'bar'),
                '->parse() parses long options'
            ),
            array(
                array('--foo' => 'bar'),
                array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')),
                array('foo' => 'bar'),
                '->parse() parses long options with a default value'
            ),
            array(
                array('--foo' => null),
                array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')),
                array('foo' => 'default'),
                '->parse() parses long options with a default value'
            ),
            array(
                array('-f' => 'bar'),
                array(new InputOption('foo', 'f')),
                array('foo' => 'bar'),
                '->parse() parses short options'
            )
        );
    }

    /**
     * @dataProvider provideInvalidInput
     */
    public function testParseInvalidInput($parameters, $definition, $expectedExceptionMessage)
    {
        $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage);

        new ArrayInput($parameters, $definition);
    }

    public function provideInvalidInput()
    {
        return array(
            array(
                array('foo' => 'foo'),
                new InputDefinition(array(new InputArgument('name'))),
                'The "foo" argument does not exist.'
            ),
            array(
                array('--foo' => null),
                new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))),
                'The "--foo" option requires a value.'
            ),
            array(
                array('--foo' => 'foo'),
                new InputDefinition(),
                'The "--foo" option does not exist.'
            ),
            array(
                array('-o' => 'foo'),
                new InputDefinition(),
                'The "-o" option does not exist.'
            )
        );
    }

    public function testToString()
    {
        $input = new ArrayInput(array('-f' => null, '-b' => 'bar', '--foo' => 'b a z', '--lala' => null, 'test' => 'Foo', 'test2' => "A\nB'C"));
        $this->assertEquals('-f -b=bar --foo='.escapeshellarg('b a z').' --lala Foo '.escapeshellarg("A\nB'C"), (string) $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\Tests\Input;

use Symfony\Component\Console\Input\InputArgument;

class InputArgumentTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $argument = new InputArgument('foo');
        $this->assertEquals('foo', $argument->getName(), '__construct() takes a name as its first argument');
    }

    public function testModes()
    {
        $argument = new InputArgument('foo');
        $this->assertFalse($argument->isRequired(), '__construct() gives a "InputArgument::OPTIONAL" mode by default');

        $argument = new InputArgument('foo', null);
        $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode');

        $argument = new InputArgument('foo', InputArgument::OPTIONAL);
        $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode');

        $argument = new InputArgument('foo', InputArgument::REQUIRED);
        $this->assertTrue($argument->isRequired(), '__construct() can take "InputArgument::REQUIRED" as its mode');
    }

    /**
     * @dataProvider provideInvalidModes
     */
    public function testInvalidModes($mode)
    {
        $this->setExpectedException('InvalidArgumentException', sprintf('Argument mode "%s" is not valid.', $mode));

        new InputArgument('foo', $mode);
    }

    public function provideInvalidModes()
    {
        return array(
            array('ANOTHER_ONE'),
            array(-1)
        );
    }

    public function testIsArray()
    {
        $argument = new InputArgument('foo', InputArgument::IS_ARRAY);
        $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array');
        $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY);
        $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array');
        $argument = new InputArgument('foo', InputArgument::OPTIONAL);
        $this->assertFalse($argument->isArray(), '->isArray() returns false if the argument can not be an array');
    }

    public function testGetDescription()
    {
        $argument = new InputArgument('foo', null, 'Some description');
        $this->assertEquals('Some description', $argument->getDescription(), '->getDescription() return the message description');
    }

    public function testGetDefault()
    {
        $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default');
        $this->assertEquals('default', $argument->getDefault(), '->getDefault() return the default value');
    }

    public function testSetDefault()
    {
        $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default');
        $argument->setDefault(null);
        $this->assertNull($argument->getDefault(), '->setDefault() can reset the default value by passing null');
        $argument->setDefault('another');
        $this->assertEquals('another', $argument->getDefault(), '->setDefault() changes the default value');

        $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY);
        $argument->setDefault(array(1, 2));
        $this->assertEquals(array(1, 2), $argument->getDefault(), '->setDefault() changes the default value');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage Cannot set a default value except for InputArgument::OPTIONAL mode.
     */
    public function testSetDefaultWithRequiredArgument()
    {
        $argument = new InputArgument('foo', InputArgument::REQUIRED);
        $argument->setDefault('default');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage A default value for an array argument must be an array.
     */
    public function testSetDefaultWithArrayArgument()
    {
        $argument = new InputArgument('foo', InputArgument::IS_ARRAY);
        $argument->setDefault('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\Console\Tests\Input;

use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class InputDefinitionTest extends \PHPUnit_Framework_TestCase
{
    protected static $fixtures;

    protected $foo, $bar, $foo1, $foo2;

    public static function setUpBeforeClass()
    {
        self::$fixtures = __DIR__.'/../Fixtures/';
    }

    public function testConstructorArguments()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $this->assertEquals(array(), $definition->getArguments(), '__construct() creates a new InputDefinition object');

        $definition = new InputDefinition(array($this->foo, $this->bar));
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '__construct() takes an array of InputArgument objects as its first argument');
    }

    public function testConstructorOptions()
    {
        $this->initializeOptions();

        $definition = new InputDefinition();
        $this->assertEquals(array(), $definition->getOptions(), '__construct() creates a new InputDefinition object');

        $definition = new InputDefinition(array($this->foo, $this->bar));
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '__construct() takes an array of InputOption objects as its first argument');
    }

    public function testSetArguments()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->setArguments(array($this->foo));
        $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->setArguments() sets the array of InputArgument objects');
        $definition->setArguments(array($this->bar));

        $this->assertEquals(array('bar' => $this->bar), $definition->getArguments(), '->setArguments() clears all InputArgument objects');
    }

    public function testAddArguments()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArguments(array($this->foo));
        $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArguments() adds an array of InputArgument objects');
        $definition->addArguments(array($this->bar));
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArguments() does not clear existing InputArgument objects');
    }

    public function testAddArgument()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument($this->foo);
        $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArgument() adds a InputArgument object');
        $definition->addArgument($this->bar);
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArgument() adds a InputArgument object');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage An argument with name "foo" already exists.
     */
    public function testArgumentsMustHaveDifferentNames()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument($this->foo);
        $definition->addArgument($this->foo1);
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage Cannot add an argument after an array argument.
     */
    public function testArrayArgumentHasToBeLast()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument(new InputArgument('fooarray', InputArgument::IS_ARRAY));
        $definition->addArgument(new InputArgument('anotherbar'));
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage Cannot add a required argument after an optional one.
     */
    public function testRequiredArgumentCannotFollowAnOptionalOne()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument($this->foo);
        $definition->addArgument($this->foo2);
    }

    public function testGetArgument()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArguments(array($this->foo));
        $this->assertEquals($this->foo, $definition->getArgument('foo'), '->getArgument() returns a InputArgument by its name');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "bar" argument does not exist.
     */
    public function testGetInvalidArgument()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArguments(array($this->foo));
        $definition->getArgument('bar');
    }

    public function testHasArgument()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArguments(array($this->foo));

        $this->assertTrue($definition->hasArgument('foo'), '->hasArgument() returns true if a InputArgument exists for the given name');
        $this->assertFalse($definition->hasArgument('bar'), '->hasArgument() returns false if a InputArgument exists for the given name');
    }

    public function testGetArgumentRequiredCount()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument($this->foo2);
        $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments');
        $definition->addArgument($this->foo);
        $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments');
    }

    public function testGetArgumentCount()
    {
        $this->initializeArguments();

        $definition = new InputDefinition();
        $definition->addArgument($this->foo2);
        $this->assertEquals(1, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments');
        $definition->addArgument($this->foo);
        $this->assertEquals(2, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments');
    }

    public function testGetArgumentDefaults()
    {
        $definition = new InputDefinition(array(
            new InputArgument('foo1', InputArgument::OPTIONAL),
            new InputArgument('foo2', InputArgument::OPTIONAL, '', 'default'),
            new InputArgument('foo3', InputArgument::OPTIONAL | InputArgument::IS_ARRAY),
        //  new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)),
        ));
        $this->assertEquals(array('foo1' => null, 'foo2' => 'default', 'foo3' => array()), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument');

        $definition = new InputDefinition(array(
            new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)),
        ));
        $this->assertEquals(array('foo4' => array(1, 2)), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument');
    }

    public function testSetOptions()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->setOptions() sets the array of InputOption objects');
        $definition->setOptions(array($this->bar));
        $this->assertEquals(array('bar' => $this->bar), $definition->getOptions(), '->setOptions() clears all InputOption objects');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "-f" option does not exist.
     */
    public function testSetOptionsClearsOptions()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $definition->setOptions(array($this->bar));
        $definition->getOptionForShortcut('f');
    }

    public function testAddOptions()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOptions() adds an array of InputOption objects');
        $definition->addOptions(array($this->bar));
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOptions() does not clear existing InputOption objects');
    }

    public function testAddOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition();
        $definition->addOption($this->foo);
        $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOption() adds a InputOption object');
        $definition->addOption($this->bar);
        $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOption() adds a InputOption object');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage An option named "foo" already exists.
     */
    public function testAddDuplicateOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition();
        $definition->addOption($this->foo);
        $definition->addOption($this->foo2);
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage An option with shortcut "f" already exists.
     */
    public function testAddDuplicateShortcutOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition();
        $definition->addOption($this->foo);
        $definition->addOption($this->foo1);
    }

    public function testGetOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertEquals($this->foo, $definition->getOption('foo'), '->getOption() returns a InputOption by its name');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "--bar" option does not exist.
     */
    public function testGetInvalidOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $definition->getOption('bar');
    }

    public function testHasOption()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertTrue($definition->hasOption('foo'), '->hasOption() returns true if a InputOption exists for the given name');
        $this->assertFalse($definition->hasOption('bar'), '->hasOption() returns false if a InputOption exists for the given name');
    }

    public function testHasShortcut()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertTrue($definition->hasShortcut('f'), '->hasShortcut() returns true if a InputOption exists for the given shortcut');
        $this->assertFalse($definition->hasShortcut('b'), '->hasShortcut() returns false if a InputOption exists for the given shortcut');
    }

    public function testGetOptionForShortcut()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $this->assertEquals($this->foo, $definition->getOptionForShortcut('f'), '->getOptionForShortcut() returns a InputOption by its shortcut');
    }

    public function testGetOptionForMultiShortcut()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->multi));
        $this->assertEquals($this->multi, $definition->getOptionForShortcut('m'), '->getOptionForShortcut() returns a InputOption by its shortcut');
        $this->assertEquals($this->multi, $definition->getOptionForShortcut('mmm'), '->getOptionForShortcut() returns a InputOption by its shortcut');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "-l" option does not exist.
     */
    public function testGetOptionForInvalidShortcut()
    {
        $this->initializeOptions();

        $definition = new InputDefinition(array($this->foo));
        $definition->getOptionForShortcut('l');
    }

    public function testGetOptionDefaults()
    {
        $definition = new InputDefinition(array(
            new InputOption('foo1', null, InputOption::VALUE_NONE),
            new InputOption('foo2', null, InputOption::VALUE_REQUIRED),
            new InputOption('foo3', null, InputOption::VALUE_REQUIRED, '', 'default'),
            new InputOption('foo4', null, InputOption::VALUE_OPTIONAL),
            new InputOption('foo5', null, InputOption::VALUE_OPTIONAL, '', 'default'),
            new InputOption('foo6', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY),
            new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)),
        ));
        $defaults = array(
            'foo1' => null,
            'foo2' => null,
            'foo3' => 'default',
            'foo4' => null,
            'foo5' => 'default',
            'foo6' => array(),
            'foo7' => array(1, 2),
        );
        $this->assertEquals($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options');
    }

    public function testGetSynopsis()
    {
        $definition = new InputDefinition(array(new InputOption('foo')));
        $this->assertEquals('[--foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputOption('foo', 'f')));
        $this->assertEquals('[-f|--foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)));
        $this->assertEquals('[-f|--foo="..."]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)));
        $this->assertEquals('[-f|--foo[="..."]]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');

        $definition = new InputDefinition(array(new InputArgument('foo')));
        $this->assertEquals('[foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED)));
        $this->assertEquals('foo', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::IS_ARRAY)));
        $this->assertEquals('[foo1] ... [fooN]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
        $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED | InputArgument::IS_ARRAY)));
        $this->assertEquals('foo1 ... [fooN]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options');
    }

    public function testAsText()
    {
        $definition = new InputDefinition(array(
            new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'),
            new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true),
            new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('http://foo.com/')),
            new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'),
            new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false),
            new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'),
            new InputOption('qux', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux option', array('http://foo.com/', 'bar')),
            new InputOption('qux2', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux2 option', array('foo' => 'bar')),
        ));
        $this->assertStringEqualsFile(self::$fixtures.'/definition_astext.txt', $definition->asText(), '->asText() returns a textual representation of the InputDefinition');
    }

    public function testAsXml()
    {
        $definition = new InputDefinition(array(
            new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'),
            new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true),
            new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('bar')),
            new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'),
            new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false),
            new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'),
        ));
        $this->assertXmlStringEqualsXmlFile(self::$fixtures.'/definition_asxml.txt', $definition->asXml(), '->asText() returns a textual representation of the InputDefinition');
    }

    protected function initializeArguments()
    {
        $this->foo = new InputArgument('foo');
        $this->bar = new InputArgument('bar');
        $this->foo1 = new InputArgument('foo');
        $this->foo2 = new InputArgument('foo2', InputArgument::REQUIRED);
    }

    protected function initializeOptions()
    {
        $this->foo = new InputOption('foo', 'f');
        $this->bar = new InputOption('bar', 'b');
        $this->foo1 = new InputOption('fooBis', 'f');
        $this->foo2 = new InputOption('foo', 'p');
        $this->multi = new InputOption('multi', 'm|mm|mmm');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Input;

use Symfony\Component\Console\Input\InputOption;

class InputOptionTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $option = new InputOption('foo');
        $this->assertEquals('foo', $option->getName(), '__construct() takes a name as its first argument');
        $option = new InputOption('--foo');
        $this->assertEquals('foo', $option->getName(), '__construct() removes the leading -- of the option name');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.
     */
    public function testArrayModeWithoutValue()
    {
        new InputOption('foo', 'f', InputOption::VALUE_IS_ARRAY);
    }

    public function testShortcut()
    {
        $option = new InputOption('foo', 'f');
        $this->assertEquals('f', $option->getShortcut(), '__construct() can take a shortcut as its second argument');
        $option = new InputOption('foo', '-f|-ff|fff');
        $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts');
        $option = new InputOption('foo', array('f', 'ff', '-fff'));
        $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts');
        $option = new InputOption('foo');
        $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null by default');
    }

    public function testModes()
    {
        $option = new InputOption('foo', 'f');
        $this->assertFalse($option->acceptValue(), '__construct() gives a "InputOption::VALUE_NONE" mode by default');
        $this->assertFalse($option->isValueRequired(), '__construct() gives a "InputOption::VALUE_NONE" mode by default');
        $this->assertFalse($option->isValueOptional(), '__construct() gives a "InputOption::VALUE_NONE" mode by default');

        $option = new InputOption('foo', 'f', null);
        $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode');
        $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode');
        $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode');

        $option = new InputOption('foo', 'f', InputOption::VALUE_NONE);
        $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode');
        $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode');
        $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode');

        $option = new InputOption('foo', 'f', InputOption::VALUE_REQUIRED);
        $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode');
        $this->assertTrue($option->isValueRequired(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode');
        $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode');

        $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL);
        $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode');
        $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode');
        $this->assertTrue($option->isValueOptional(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode');
    }

    /**
     * @dataProvider provideInvalidModes
     */
    public function testInvalidModes($mode)
    {
        $this->setExpectedException('InvalidArgumentException', sprintf('Option mode "%s" is not valid.', $mode));

        new InputOption('foo', 'f', $mode);
    }

    public function provideInvalidModes()
    {
        return array(
            array('ANOTHER_ONE'),
            array(-1)
        );
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testEmptyNameIsInvalid()
    {
        new InputOption('');
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testDoubleDashNameIsInvalid()
    {
        new InputOption('--');
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testSingleDashOptionIsInvalid()
    {
        new InputOption('foo', '-');
    }

    public function testIsArray()
    {
        $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY);
        $this->assertTrue($option->isArray(), '->isArray() returns true if the option can be an array');
        $option = new InputOption('foo', null, InputOption::VALUE_NONE);
        $this->assertFalse($option->isArray(), '->isArray() returns false if the option can not be an array');
    }

    public function testGetDescription()
    {
        $option = new InputOption('foo', 'f', null, 'Some description');
        $this->assertEquals('Some description', $option->getDescription(), '->getDescription() returns the description message');
    }

    public function testGetDefault()
    {
        $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', 'default');
        $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value');

        $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default');
        $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value');

        $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED);
        $this->assertNull($option->getDefault(), '->getDefault() returns null if no default value is configured');

        $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY);
        $this->assertEquals(array(), $option->getDefault(), '->getDefault() returns an empty array if option is an array');

        $option = new InputOption('foo', null, InputOption::VALUE_NONE);
        $this->assertFalse($option->getDefault(), '->getDefault() returns false if the option does not take a value');
    }

    public function testSetDefault()
    {
        $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default');
        $option->setDefault(null);
        $this->assertNull($option->getDefault(), '->setDefault() can reset the default value by passing null');
        $option->setDefault('another');
        $this->assertEquals('another', $option->getDefault(), '->setDefault() changes the default value');

        $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY);
        $option->setDefault(array(1, 2));
        $this->assertEquals(array(1, 2), $option->getDefault(), '->setDefault() changes the default value');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage Cannot set a default value when using InputOption::VALUE_NONE mode.
     */
    public function testDefaultValueWithValueNoneMode()
    {
        $option = new InputOption('foo', 'f', InputOption::VALUE_NONE);
        $option->setDefault('default');
    }

    /**
     * @expectedException        \LogicException
     * @expectedExceptionMessage A default value for an array option must be an array.
     */
    public function testDefaultValueWithIsArrayMode()
    {
        $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY);
        $option->setDefault('default');
    }

    public function testEquals()
    {
        $option = new InputOption('foo', 'f', null, 'Some description');
        $option2 = new InputOption('foo', 'f', null, 'Alternative description');
        $this->assertTrue($option->equals($option2));

        $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description');
        $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description', true);
        $this->assertFalse($option->equals($option2));

        $option = new InputOption('foo', 'f', null, 'Some description');
        $option2 = new InputOption('bar', 'f', null, 'Some description');
        $this->assertFalse($option->equals($option2));

        $option = new InputOption('foo', 'f', null, 'Some description');
        $option2 = new InputOption('foo', '', null, 'Some description');
        $this->assertFalse($option->equals($option2));

        $option = new InputOption('foo', 'f', null, 'Some description');
        $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description');
        $this->assertFalse($option->equals($option2));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Input;

use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class InputTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'))));
        $this->assertEquals('foo', $input->getArgument('name'), '->__construct() takes a InputDefinition as an argument');
    }

    public function testOptions()
    {
        $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'))));
        $this->assertEquals('foo', $input->getOption('name'), '->getOption() returns the value for the given option');

        $input->setOption('name', 'bar');
        $this->assertEquals('bar', $input->getOption('name'), '->setOption() sets the value for a given option');
        $this->assertEquals(array('name' => 'bar'), $input->getOptions(), '->getOptions() returns all option values');

        $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default'))));
        $this->assertEquals('default', $input->getOption('bar'), '->getOption() returns the default value for optional options');
        $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getOptions(), '->getOptions() returns all option values, even optional ones');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "foo" option does not exist.
     */
    public function testSetInvalidOption()
    {
        $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default'))));
        $input->setOption('foo', 'bar');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "foo" option does not exist.
     */
    public function testGetInvalidOption()
    {
        $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default'))));
        $input->getOption('foo');
    }

    public function testArguments()
    {
        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'))));
        $this->assertEquals('foo', $input->getArgument('name'), '->getArgument() returns the value for the given argument');

        $input->setArgument('name', 'bar');
        $this->assertEquals('bar', $input->getArgument('name'), '->setArgument() sets the value for a given argument');
        $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->getArguments() returns all argument values');

        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default'))));
        $this->assertEquals('default', $input->getArgument('bar'), '->getArgument() returns the default value for optional arguments');
        $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getArguments(), '->getArguments() returns all argument values, even optional ones');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "foo" argument does not exist.
     */
    public function testSetInvalidArgument()
    {
        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default'))));
        $input->setArgument('foo', 'bar');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The "foo" argument does not exist.
     */
    public function testGetInvalidArgument()
    {
        $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default'))));
        $input->getArgument('foo');
    }

    /**
     * @expectedException        \RuntimeException
     * @expectedExceptionMessage Not enough arguments.
     */
    public function testValidateWithMissingArguments()
    {
        $input = new ArrayInput(array());
        $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED))));
        $input->validate();
    }

    public function testValidate()
    {
        $input = new ArrayInput(array('name' => 'foo'));
        $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED))));

        $this->assertNull($input->validate());
    }

    public function testSetGetInteractive()
    {
        $input = new ArrayInput(array());
        $this->assertTrue($input->isInteractive(), '->isInteractive() returns whether the input should be interactive or not');
        $input->setInteractive(false);
        $this->assertFalse($input->isInteractive(), '->setInteractive() changes the interactive flag');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Input;

use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StringInput;

class StringInputTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getTokenizeData
     */
    public function testTokenize($input, $tokens, $message)
    {
        $input = new StringInput($input);
        $r = new \ReflectionClass('Symfony\Component\Console\Input\ArgvInput');
        $p = $r->getProperty('tokens');
        $p->setAccessible(true);
        $this->assertEquals($tokens, $p->getValue($input), $message);
    }

    public function testInputOptionWithGivenString()
    {
        $definition = new InputDefinition(
            array(new InputOption('foo', null, InputOption::VALUE_REQUIRED))
        );

        // call to bind
        $input = new StringInput('--foo=bar');
        $input->bind($definition);
        $this->assertEquals('bar', $input->getOption('foo'));

        // definition in constructor
        $input = new StringInput('--foo=bar', $definition);
        $this->assertEquals('bar', $input->getOption('foo'));
    }

    public function getTokenizeData()
    {
        return array(
            array('', array(), '->tokenize() parses an empty string'),
            array('foo', array('foo'), '->tokenize() parses arguments'),
            array('  foo  bar  ', array('foo', 'bar'), '->tokenize() ignores whitespaces between arguments'),
            array('"quoted"', array('quoted'), '->tokenize() parses quoted arguments'),
            array("'quoted'", array('quoted'), '->tokenize() parses quoted arguments'),
            array("'a\rb\nc\td'", array("a\rb\nc\td"), '->tokenize() parses whitespace chars in strings'),
            array("'a'\r'b'\n'c'\t'd'", array('a','b','c','d'), '->tokenize() parses whitespace chars between args as spaces'),
            array('\"quoted\"', array('"quoted"'), '->tokenize() parses escaped-quoted arguments'),
            array("\'quoted\'", array('\'quoted\''), '->tokenize() parses escaped-quoted arguments'),
            array('-a', array('-a'), '->tokenize() parses short options'),
            array('-azc', array('-azc'), '->tokenize() parses aggregated short options'),
            array('-awithavalue', array('-awithavalue'), '->tokenize() parses short options with a value'),
            array('-a"foo bar"', array('-afoo bar'), '->tokenize() parses short options with a value'),
            array('-a"foo bar""foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'),
            array('-a\'foo bar\'', array('-afoo bar'), '->tokenize() parses short options with a value'),
            array('-a\'foo bar\'\'foo bar\'', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'),
            array('-a\'foo bar\'"foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'),
            array('--long-option', array('--long-option'), '->tokenize() parses long options'),
            array('--long-option=foo', array('--long-option=foo'), '->tokenize() parses long options with a value'),
            array('--long-option="foo bar"', array('--long-option=foo bar'), '->tokenize() parses long options with a value'),
            array('--long-option="foo bar""another"', array('--long-option=foo baranother'), '->tokenize() parses long options with a value'),
            array('--long-option=\'foo bar\'', array('--long-option=foo bar'), '->tokenize() parses long options with a value'),
            array("--long-option='foo bar''another'", array("--long-option=foo baranother"), '->tokenize() parses long options with a value'),
            array("--long-option='foo bar'\"another\"", array("--long-option=foo baranother"), '->tokenize() parses long options with a value'),
            array('foo -a -ffoo --long bar', array('foo', '-a', '-ffoo', '--long', 'bar'), '->tokenize() parses when several arguments and options'),
        );
    }

    public function testToString()
    {
        $input = new StringInput('-f foo');
        $this->assertEquals('-f foo', (string) $input);

        $input = new StringInput('-f --bar=foo "a b c d"');
        $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d'), (string) $input);

        $input = new StringInput('-f --bar=foo \'a b c d\' '."'A\nB\\'C'");
        $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $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\Tests\Output;

use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\Output;

class ConsoleOutputTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $output = new ConsoleOutput(Output::VERBOSITY_QUIET, true);
        $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Output;

use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

class NullOutputTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $output = new NullOutput();

        ob_start();
        $output->write('foo');
        $buffer = ob_get_clean();

        $this->assertSame('', $buffer, '->write() does nothing (at least nothing is printed)');
        $this->assertFalse($output->isDecorated(), '->isDecorated() returns false');
    }

    public function testVerbosity()
    {
        $output = new NullOutput();
        $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() returns VERBOSITY_QUIET for NullOutput by default');

        $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
        $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() always returns VERBOSITY_QUIET for NullOutput');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Output;

use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class OutputTest extends \PHPUnit_Framework_TestCase
{
    public function testConstructor()
    {
        $output = new TestOutput(Output::VERBOSITY_QUIET, true);
        $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument');
        $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument');
    }

    public function testSetIsDecorated()
    {
        $output = new TestOutput();
        $output->setDecorated(true);
        $this->assertTrue($output->isDecorated(), 'setDecorated() sets the decorated flag');
    }

    public function testSetGetVerbosity()
    {
        $output = new TestOutput();
        $output->setVerbosity(Output::VERBOSITY_QUIET);
        $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '->setVerbosity() sets the verbosity');
    }

    public function testWriteWithVerbosityQuiet()
    {
        $output = new TestOutput(Output::VERBOSITY_QUIET);
        $output->writeln('foo');
        $this->assertEquals('', $output->output, '->writeln() outputs nothing if verbosity is set to VERBOSITY_QUIET');
    }

    public function testWriteAnArrayOfMessages()
    {
        $output = new TestOutput();
        $output->writeln(array('foo', 'bar'));
        $this->assertEquals("foo\nbar\n", $output->output, '->writeln() can take an array of messages to output');
    }

    /**
     * @dataProvider provideWriteArguments
     */
    public function testWriteRawMessage($message, $type, $expectedOutput)
    {
        $output = new TestOutput();
        $output->writeln($message, $type);
        $this->assertEquals($expectedOutput, $output->output);
    }

    public function provideWriteArguments()
    {
        return array(
            array('<info>foo</info>', Output::OUTPUT_RAW, "<info>foo</info>\n"),
            array('<info>foo</info>', Output::OUTPUT_PLAIN, "foo\n"),
        );
    }

    public function testWriteWithDecorationTurnedOff()
    {
        $output = new TestOutput();
        $output->setDecorated(false);
        $output->writeln('<info>foo</info>');
        $this->assertEquals("foo\n", $output->output, '->writeln() strips decoration tags if decoration is set to false');
    }

    public function testWriteDecoratedMessage()
    {
        $fooStyle = new OutputFormatterStyle('yellow', 'red', array('blink'));
        $output = new TestOutput();
        $output->getFormatter()->setStyle('FOO', $fooStyle);
        $output->setDecorated(true);
        $output->writeln('<foo>foo</foo>');
        $this->assertEquals("\033[33;41;5mfoo\033[0m\n", $output->output, '->writeln() decorates the output');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage Unknown output type given (24)
     */
    public function testWriteWithInvalidOutputType()
    {
        $output = new TestOutput();
        $output->writeln('<foo>foo</foo>', 24);
    }

    public function testWriteWithInvalidStyle()
    {
        $output = new TestOutput();

        $output->clear();
        $output->write('<bar>foo</bar>');
        $this->assertEquals('<bar>foo</bar>', $output->output, '->write() do nothing when a style does not exist');

        $output->clear();
        $output->writeln('<bar>foo</bar>');
        $this->assertEquals("<bar>foo</bar>\n", $output->output, '->writeln() do nothing when a style does not exist');
    }
}

class TestOutput extends Output
{
    public $output = '';

    public function clear()
    {
        $this->output = '';
    }

    protected function doWrite($message, $newline)
    {
        $this->output .= $message.($newline ? "\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\Tests\Output;

use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\StreamOutput;

class StreamOutputTest extends \PHPUnit_Framework_TestCase
{
    protected $stream;

    protected function setUp()
    {
        $this->stream = fopen('php://memory', 'a', false);
    }

    protected function tearDown()
    {
        $this->stream = null;
    }

    public function testConstructor()
    {
        $output = new StreamOutput($this->stream, Output::VERBOSITY_QUIET, true);
        $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument');
        $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument');
    }

    /**
     * @expectedException        \InvalidArgumentException
     * @expectedExceptionMessage The StreamOutput class needs a stream as its first argument.
     */
    public function testStreamIsRequired()
    {
        new StreamOutput('foo');
    }

    public function testGetStream()
    {
        $output = new StreamOutput($this->stream);
        $this->assertEquals($this->stream, $output->getStream(), '->getStream() returns the current stream');
    }

    public function testDoWrite()
    {
        $output = new StreamOutput($this->stream);
        $output->writeln('foo');
        rewind($output->getStream());
        $this->assertEquals('foo'.PHP_EOL, stream_get_contents($output->getStream()), '->doWrite() writes to the 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\Tests\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Tester\ApplicationTester;

class ApplicationTesterTest extends \PHPUnit_Framework_TestCase
{
    protected $application;
    protected $tester;

    protected function setUp()
    {
        $this->application = new Application();
        $this->application->setAutoExit(false);
        $this->application->register('foo')
            ->addArgument('foo')
            ->setCode(function ($input, $output) { $output->writeln('foo'); })
        ;

        $this->tester = new ApplicationTester($this->application);
        $this->tester->run(array('command' => 'foo', 'foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE));
    }

    protected function tearDown()
    {
        $this->application = null;
        $this->tester = null;
    }

    public function testRun()
    {
        $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option');
        $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option');
        $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option');
    }

    public function testGetInput()
    {
        $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance');
    }

    public function testGetOutput()
    {
        rewind($this->tester->getOutput()->getStream());
        $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance');
    }

    public function testGetDisplay()
    {
        $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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\Tests\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Tester\CommandTester;

class CommandTesterTest extends \PHPUnit_Framework_TestCase
{
    protected $command;
    protected $tester;

    protected function setUp()
    {
        $this->command = new Command('foo');
        $this->command->addArgument('command');
        $this->command->addArgument('foo');
        $this->command->setCode(function ($input, $output) { $output->writeln('foo'); });

        $this->tester = new CommandTester($this->command);
        $this->tester->execute(array('foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE));
    }

    protected function tearDown()
    {
        $this->command = null;
        $this->tester = null;
    }

    public function testExecute()
    {
        $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option');
        $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option');
        $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option');
    }

    public function testGetInput()
    {
        $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance');
    }

    public function testGetOutput()
    {
        rewind($this->tester->getOutput()->getStream());
        $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance');
    }

    public function testGetDisplay()
    {
        $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution');
    }
}
{
    "name": "symfony/console",
    "type": "library",
    "description": "Symfony Console Component",
    "keywords": [],
    "homepage": "http://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "http://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "require-dev": {
        "symfony/event-dispatcher": "~2.1"
    },
    "suggest": {
        "symfony/event-dispatcher": ""
    },
    "autoload": {
        "psr-0": { "Symfony\\Component\\Console\\": "" }
    },
    "target-dir": "Symfony/Component/Console",
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "2.3-dev"
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="Symfony Console Component Test Suite">
            <directory>./Tests/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>./</directory>
            <exclude>
                <directory>./Resources</directory>
                <directory>./Tests</directory>
                <directory>./vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>
#!/usr/bin/env php
<?php
/**
 * JSON schema validator
 *
 * @author Christian Weiske <christian.weiske@netresearch.de>
 */

/**
 * Dead simple autoloader
 *
 * @param string $className Name of class to load
 *
 * @return void
 */
function __autoload($className)
{
    $className = ltrim($className, '\\');
    $fileName  = '';
    $namespace = '';
    if ($lastNsPos = strrpos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    }
    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
    require_once $fileName;
}

/**
 * Show the json parse error that happened last
 *
 * @return void
 */
function showJsonError()
{
    $constants = get_defined_constants(true);
    $json_errors = array();
    foreach ($constants['json'] as $name => $value) {
        if (!strncmp($name, 'JSON_ERROR_', 11)) {
            $json_errors[$value] = $name;
        }
    }

    echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n";
}


// support running this tool from git checkout
if (is_dir(__DIR__ . '/../src/JsonSchema')) {
    set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path());
}

if ($argc < 3) {
    echo "Usage: validate-json schema.json data.json\n";
    exit(1);
}

$pathSchema = $argv[1];
$pathData   = $argv[2];

if (!is_readable($pathSchema)) {
    echo "Schema file is not readable.\n";
    exit(2);
}

if (!is_readable($pathData)) {
    echo "Data file is not readable.\n";
    exit(3);
}

$data = json_decode(file_get_contents($pathData));

if ($data === null) {
    echo "Error loading JSON data file\n";
    showJsonError();
    exit(5);
}

$schema = json_decode(file_get_contents($pathSchema));

if ($schema === null) {
    echo "Error loading JSON schema file\n";
    showJsonError();
    exit(6);
}

$validator = new JsonSchema\Validator();
$validator->check($data, $schema);

if ($validator->isValid()) {
    echo "OK. The supplied JSON validates against the schema.\n";
} else {
    echo "JSON does not validate. Violations:\n";
    foreach ($validator->getErrors() as $error) {
        echo sprintf("[%s] %s\n", $error['property'], $error['message']);
    }
    exit(23);
}
#!/usr/bin/env php
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) 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.
 */

if ((!@include __DIR__.'/../../../.composer/autoload.php') && (!@include __DIR__.'/../vendor/.composer/autoload.php')) {
    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
        'php composer.phar install'.PHP_EOL);
}

use Seld\JsonLint\JsonParser;

if (!isset($_SERVER['argv'][1])) {
    echo 'Usage: jsonlint file'.PHP_EOL;
    exit;
}

$file = $_SERVER['argv'][1];
if (!preg_match('{^https?://}i', $file)) {
    if (!file_exists($file)) {
        echo 'File not found: '.$file.PHP_EOL;
        exit(1);
    }
    if (!is_readable($file)) {
        echo 'File not readable: '.$file.PHP_EOL;
        exit(1);
    }
}

$parser = new JsonParser();
if ($err = $parser->lint(file_get_contents($file))) {
    echo $err->getMessage().PHP_EOL;
    exit(1);
}
echo 'Valid JSON'.PHP_EOL;#!/usr/bin/env php
<?php

/**
 * The project (or Phar) base path.
 *
 * @var string
 */
define('BOX_PATH', dirname(__DIR__));

if (extension_loaded('phar') && ($uri = Phar::running())) {
    require "$uri/src/vendors/autoload.php";
} elseif (class_exists('Extract')) {
    require __DIR__ . '/../src/vendors/autoload.php';
} else {
    loadComposerClassloader(realpath($_SERVER['argv'][0]));
}

$app = new KevinGH\Box\Application();
$app->run();

/**
 * Finds the Composer autoloader and returns it.
 *
 * @param null $dir  The starting directory.
 * @param int  $skip The number of occurrences to skip.
 *
 * @return Composer\Autoload\ClassLoader The class loader.
 *
 * @throws RuntimeException If the loader could not be loaded.
 */
function loadComposerClassloader($dir = null, $skip = 0)
{
    $up = $dir;
    $skips = 0;

    do {
        $dir = $up;

        if (file_exists("$dir/composer.json")) {
            if ($skip > $skips) {
                $skips++;

                continue;
            }

            $path = realpath("$dir/composer.json");
        }
    } while ($dir !== ($up = dirname($dir)));

    if (empty($path)) {
        throw new RuntimeException(
            'The composer.json file could not be found.'
        );
    }

    if (false === ($json = file_get_contents($path))) {
        throw new RuntimeException(
            sprintf(
                'The file "%s" could not be read.',
                $path
            )
        );
    }

    $json = json_decode($json);

    if (JSON_ERROR_NONE !== ($code = json_last_error())) {
        throw new RuntimeException(
            sprintf(
                'The file "%s" could not be parsed [%d].',
                $path,
                $code
            )
        );
    }

    $path = dirname($path);

    if (isset($json->config) && isset($json->config->{'vendor-dir'})) {
        $path .= DIRECTORY_SEPARATOR . $json->config->{'vendor-dir'};
    } else {
        $path .= DIRECTORY_SEPARATOR . 'vendor';
    }

    $path .= DIRECTORY_SEPARATOR . 'autoload.php';

    if (false === file_exists($path)) {
        throw new RuntimeException(
            sprintf(
                'The Composer class loader "%s" could not be found.',
                $path
            )
        );
    }

    return include $path;
}
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit4dc88d039c3303ecad17ebc1942bf9aa::getLoader();
<?php

namespace Sstalle\php7cc;

abstract class AbstractBaseMessage
{
    /**
     * @var string
     */
    protected $rawText;

    /**
     * @var string
     */
    protected $text;

    /**
     * @var int
     */
    protected $line;

    /**
     * @param string   $text
     * @param int|null $line
     */
    public function __construct($text, $line = null)
    {
        $this->rawText = $text;
        $this->line = $line;
        $this->text = $this->generateText();
    }

    /**
     * @return string
     */
    public function getRawText()
    {
        return $this->rawText;
    }

    /**
     * @return string
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * @return int
     */
    public function getLine()
    {
        return $this->line;
    }

    /**
     * @return string
     */
    abstract protected function generateText();
}
<?php

namespace Sstalle\php7cc;

interface CLIOutputInterface
{
    /**
     * @param string $string
     */
    public function write($string);

    /**
     * @param string $string
     */
    public function writeln($string);
}
<?php

namespace Sstalle\php7cc;

use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
use Sstalle\php7cc\CompatibilityViolation\CheckMetadata;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;
use Sstalle\php7cc\CompatibilityViolation\Message;

class CLIResultPrinter implements ResultPrinterInterface
{
    /**
     * @var array
     */
    private static $colors = array(
        Message::LEVEL_INFO => null,
        Message::LEVEL_WARNING => 'yellow',
        Message::LEVEL_ERROR => 'red',
    );

    /**
     * @var array
     */
    private static $levelText = array(
        Message::LEVEL_INFO => null,
        Message::LEVEL_WARNING => 'Warning',
        Message::LEVEL_ERROR => 'Error',
    );

    /**
     * @var CLIOutputInterface
     */
    protected $output;

    /**
     * @var StandardPrettyPrinter
     */
    protected $prettyPrinter;

    /**
     * @var NodeStatementsRemover
     */
    protected $nodeStatementsRemover;

    /**
     * @param CLIOutputInterface    $output
     * @param StandardPrettyPrinter $prettyPrinter
     * @param NodeStatementsRemover $nodeStatementsRemover
     */
    public function __construct(
        CLIOutputInterface $output,
        StandardPrettyPrinter $prettyPrinter,
        NodeStatementsRemover $nodeStatementsRemover
    ) {
        $this->output = $output;
        $this->prettyPrinter = $prettyPrinter;
        $this->nodeStatementsRemover = $nodeStatementsRemover;
    }

    /**
     * {@inheritdoc}
     */
    public function printContext(ContextInterface $context)
    {
        $this->output->writeln('');
        $this->output->writeln(sprintf('File: <fg=cyan>%s</fg=cyan>', $context->getCheckedResourceName()));

        foreach ($context->getMessages() as $message) {
            $this->output->writeln(
                $this->formatMessage($message)
            );
        }

        foreach ($context->getErrors() as $error) {
            $this->output->writeln(
                sprintf(
                    '> <fg=red>[Error] %s</fg=red>',
                    $error->getText()
                )
            );
        }

        $this->output->writeln('');
    }

    /**
     * {@inheritdoc}
     */
    public function printMetadata(CheckMetadata $metadata)
    {
        $checkedFileCount = $metadata->getCheckedFileCount();
        $elapsedTime = $metadata->getElapsedTime();

        $this->output->writeln(
            sprintf(
                'Checked <fg=green>%d</fg=green> file%s in <fg=green>%.3f</fg=green> second%s',
                $checkedFileCount,
                $checkedFileCount > 1 ? 's' : '',
                $elapsedTime,
                $elapsedTime > 1 ? 's' : ''
            )
        );
    }

    /**
     * @param Message $message
     *
     * @return string
     */
    private function formatMessage(Message $message)
    {
        $nodes = $this->nodeStatementsRemover->removeInnerStatements($message->getNodes());
        $prettyPrintedNodes = str_replace("\n", "\n    ", $this->prettyPrinter->prettyPrint($nodes));

        $text = $message->getRawText();
        $color = self::$colors[$message->getLevel()];
        $levelText = self::$levelText[$message->getLevel()];

        if ($color) {
            $text = sprintf(
                '<fg=%s>[%s] %s</fg=%s>',
                $color,
                $levelText,
                $text,
                $color
            );
        }

        return sprintf(
            "> Line <fg=cyan>%s</fg=cyan>: %s\n    %s",
            $message->getLine(),
            $text,
            $prettyPrintedNodes
        );
    }
}
<?php

namespace Sstalle\php7cc\CodePreprocessor;

interface PreprocessorInterface
{
    /**
     * @param string $code
     *
     * @return string
     */
    public function preprocess($code);
}
<?php

namespace Sstalle\php7cc\CodePreprocessor;

class ShebangRemover implements PreprocessorInterface
{
    const SHEBANG_REGEXP = '/\A#!.*\r?\n/';

    /**
     * {@inheritdoc}
     */
    public function preprocess($code)
    {
        $matches = array();
        preg_match(static::SHEBANG_REGEXP, $code, $matches);

        return $matches ? substr($code, strlen($matches[0])) : $code;
    }
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

use Sstalle\php7cc\Error\CheckError;

abstract class AbstractContext implements ContextInterface
{
    /**
     * @var array|Message[]
     */
    protected $messages = array();

    /**
     * @var CheckError[]
     */
    protected $errors = array();

    /**
     * @param Message $message
     */
    public function addMessage(Message $message)
    {
        $this->messages[] = $message;
    }

    /**
     * @return array|Message[]
     */
    public function getMessages()
    {
        return $this->messages;
    }

    /**
     * {@inheritdoc}
     */
    public function addError(CheckError $error)
    {
        $this->errors[] = $error;
    }

    /**
     * {@inheritdoc}
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * {@inheritdoc}
     */
    public function hasMessagesOrErrors()
    {
        return $this->messages || $this->errors;
    }
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

class CheckMetadata
{
    /**
     * @var float
     */
    protected $startTime;

    /**
     * @var float|null
     */
    protected $endTime;

    /**
     * @var int
     */
    protected $checkedFileCount = 0;

    public function __construct()
    {
        $this->startTime = microtime(true);
    }

    public function endCheck()
    {
        $this->endTime = microtime(true);
    }

    /**
     * @return float In seconds
     */
    public function getElapsedTime()
    {
        $endTime = $this->endTime;
        if ($endTime === null) {
            $endTime = microtime(true);
        }

        return $endTime - $this->startTime;
    }

    /**
     * @return int
     */
    public function getCheckedFileCount()
    {
        return $this->checkedFileCount;
    }

    public function incrementCheckedFileCount()
    {
        ++$this->checkedFileCount;
    }
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

use Sstalle\php7cc\Error\CheckError;

interface ContextInterface
{
    /**
     * @param Message $message
     */
    public function addMessage(Message $message);

    /**
     * @return Message[]
     */
    public function getMessages();

    /**
     * @param CheckError $error
     */
    public function addError(CheckError $error);

    /**
     * @return CheckError[]
     */
    public function getErrors();

    /**
     * @return bool
     */
    public function hasMessagesOrErrors();

    /**
     * @return string
     */
    public function getCheckedResourceName();

    /**
     * @return string
     */
    public function getCheckedCode();
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

use Symfony\Component\Finder\SplFileInfo;

class FileContext extends AbstractContext
{
    /**
     * @var SplFileInfo
     */
    protected $file;

    /**
     * @var bool
     */
    protected $useRelativePaths;

    /**
     * @param SplFileInfo $file
     * @param bool        $useRelativePaths
     */
    public function __construct(SplFileInfo $file, $useRelativePaths)
    {
        $this->file = $file;
        $this->useRelativePaths = $useRelativePaths;
    }

    /**
     * @return SplFileInfo
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * {@inheritdoc}
     */
    public function getCheckedResourceName()
    {
        $file = $this->getFile();

        return $this->useRelativePaths ? $file->getRelativePathname() : $file->getRealPath();
    }

    /**
     * {@inheritdoc}
     */
    public function getCheckedCode()
    {
        return $this->getFile()->getContents();
    }
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

use PhpParser\Node;
use Sstalle\php7cc\AbstractBaseMessage;

class Message extends AbstractBaseMessage
{
    const LEVEL_INFO = 0;
    const LEVEL_WARNING = 1;
    const LEVEL_ERROR = 2;

    /**
     * @var int
     */
    protected $level;

    /**
     * @var Node[]
     */
    protected $nodes;

    /**
     * @param string   $text
     * @param int|null $line
     * @param int      $level
     * @param Node[]   $nodes
     */
    public function __construct($text, $line = null, $level = self::LEVEL_INFO, array $nodes = array())
    {
        parent::__construct($text, $line);
        $this->level = $level;
        $this->nodes = $nodes;
    }

    /**
     * @return int
     */
    public function getLevel()
    {
        return $this->level;
    }

    /**
     * @return Node[]
     */
    public function getNodes()
    {
        return $this->nodes;
    }

    /**
     * {@inheritdoc}
     */
    protected function generateText()
    {
        return sprintf('Line %d. %s', $this->getLine(), $this->getRawText());
    }
}
<?php

namespace Sstalle\php7cc\CompatibilityViolation;

class StringContext extends AbstractContext
{
    /**
     * @var string
     */
    protected $checkedCode;

    /**
     * @var string
     */
    protected $checkedResourceName;

    /**
     * @param string $checkedCode
     * @param string $checkedResourceName
     */
    public function __construct($checkedCode, $checkedResourceName)
    {
        $this->checkedCode = $checkedCode;
        $this->checkedResourceName = $checkedResourceName;
    }

    /**
     * {@inheritdoc}
     */
    public function getCheckedResourceName()
    {
        return $this->checkedResourceName;
    }

    /**
     * {@inheritdoc}
     */
    public function getCheckedCode()
    {
        return $this->checkedCode;
    }
}
<?php

namespace Sstalle\php7cc;

use PhpParser\Parser;
use Sstalle\php7cc\CodePreprocessor\PreprocessorInterface;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;
use Sstalle\php7cc\CompatibilityViolation\FileContext;
use Sstalle\php7cc\Error\CheckError;
use Sstalle\php7cc\Lexer\ExtendedLexer;
use Sstalle\php7cc\NodeTraverser\Traverser;

class ContextChecker
{
    /**
     * @var Parser
     */
    protected $parser;

    /**
     * @var ExtendedLexer
     */
    protected $lexer;

    /**
     * @var Traverser
     */
    protected $traverser;

    /**
     * @var PreprocessorInterface
     */
    protected $preprocessor;

    /**
     * @param Parser                $parser
     * @param ExtendedLexer         $lexer
     * @param Traverser             $traverser
     * @param PreprocessorInterface $preprocessor
     */
    public function __construct(
        Parser $parser,
        ExtendedLexer $lexer,
        Traverser $traverser,
        PreprocessorInterface $preprocessor
    ) {
        $this->parser = $parser;
        $this->lexer = $lexer;
        $this->traverser = $traverser;
        $this->preprocessor = $preprocessor;
    }

    /**
     * @param ContextInterface $context
     *
     * @return FileContext
     */
    public function checkContext(ContextInterface $context)
    {
        try {
            $checkedCode = $this->preprocessor->preprocess($context->getCheckedCode());
            $parsedStatements = $this->parser->parse($checkedCode);
            $this->traverser->traverse($parsedStatements, $context, $this->lexer->getTokens());
        } catch (\Exception $e) {
            $context->addError(new CheckError($e->getMessage()));
        } catch (\ParseException $e) {
            $context->addError(new CheckError($e->getMessage(), $e->getLine()));
        }
    }
}
<?php

namespace Sstalle\php7cc\Error;

use Sstalle\php7cc\AbstractBaseMessage;

class CheckError extends AbstractBaseMessage
{
    /**
     * {@inheritdoc}
     */
    protected function generateText()
    {
        $text = $this->getRawText();
        if ($this->getLine()) {
            $text = sprintf('Line %d. %s', $this->getLine(), $text);
        }

        return $text . '. Processing aborted.';
    }
}
<?php

namespace Sstalle\php7cc;

use Sstalle\php7cc\Helper\Path\PathHelperInterface;

class ExcludedPathCanonicalizer
{
    /**
     * @var PathHelperInterface
     */
    protected $pathHelper;

    /**
     * @param PathHelperInterface $pathHelper
     */
    public function __construct(PathHelperInterface $pathHelper)
    {
        $this->pathHelper = $pathHelper;
    }

    /**
     * Makes all excluded paths absolute. Non-existent paths are removed.
     *
     * @param string[] $checkedPaths
     * @param string[] $excludedPaths
     *
     * @return \string[]
     */
    public function canonicalize(array $checkedPaths, array $excludedPaths)
    {
        $checkedDirectories = array_filter($checkedPaths, function ($path) {
            return is_dir($path);
        });
        $canonicalizedPaths = array();

        foreach ($excludedPaths as $path) {
            if (!$this->pathHelper->isDirectoryRelative($path) && ($canonicalizedPath = realpath($path))) {
                $canonicalizedPaths[] = $canonicalizedPath;
            } else {
                foreach ($checkedDirectories as $checkedDirectory) {
                    $nestedExcludedDirectory = realpath(realpath($checkedDirectory) . DIRECTORY_SEPARATOR . $path);
                    $nestedExcludedDirectory && $canonicalizedPaths[] = $nestedExcludedDirectory;
                }
            }
        }

        return $canonicalizedPaths;
    }
}
<?php

namespace Sstalle\php7cc\Helper;

class OSDetector
{
    /**
     * @return bool
     */
    public function isWindows()
    {
        return DIRECTORY_SEPARATOR == '\\';
    }
}
<?php

namespace Sstalle\php7cc\Helper\Path;

use Sstalle\php7cc\Helper\OSDetector;

class PathHelperFactory
{
    /**
     * @var OSDetector
     */
    protected $osDetector;

    /**
     * @param OSDetector $osDetector
     */
    public function __construct(OSDetector $osDetector)
    {
        $this->osDetector = $osDetector;
    }

    /**
     * @return PathHelperInterface
     */
    public function createPathHelper()
    {
        return $this->osDetector->isWindows() ? new WindowsPathHelper() : new UnixPathHelper();
    }
}
<?php

namespace Sstalle\php7cc\Helper\Path;

interface PathHelperInterface
{
    /**
     * @param string $path
     *
     * @return bool
     */
    public function isAbsolute($path);

    /**
     * @param string $path
     *
     * @return bool
     */
    public function isDirectoryRelative($path);
}
<?php

namespace Sstalle\php7cc\Helper\Path;

class UnixPathHelper implements PathHelperInterface
{
    /**
     * {@inheritdoc}
     */
    public function isAbsolute($path)
    {
        return $path && $path[0] === '/';
    }

    /**
     * {@inheritdoc}
     */
    public function isDirectoryRelative($path)
    {
        return !$this->isAbsolute($path);
    }
}
<?php

namespace Sstalle\php7cc\Helper\Path;

class WindowsPathHelper implements PathHelperInterface
{
    /**
     * {@inheritdoc}
     */
    public function isAbsolute($path)
    {
        return $path && (preg_match('#^(\\\\\\\\|[a-zA-Z]\\:\\\\|\\\\.+)#', $path) === 1);
    }

    /**
     * {@inheritdoc}
     */
    public function isDirectoryRelative($path)
    {
        return $path && (!$this->isAbsolute($path) && preg_match('#^[a-zA-Z]\\:(?!\\\\)#', $path) === 0);
    }
}
<?php

namespace Sstalle\php7cc\Helper\RegExp;

class RegExp
{
    /**
     * @var string[string]
     */
    protected static $delimiterPairs = array(
        '(' => ')',
        '[' => ']',
        '{' => '}',
        '<' => '>',
    );

    /**
     * @var string
     */
    protected $startDelimiter;

    /**
     * @var string
     */
    protected $endDelimiter;

    /**
     * @var string
     */
    protected $expression;

    /**
     * @var string
     */
    protected $modifiers;

    /**
     * @param string $startDelimiter
     * @param string $endDelimiter
     * @param string $expression
     * @param string $modifiers
     */
    public function __construct($startDelimiter, $endDelimiter, $expression, $modifiers)
    {
        if (!$startDelimiter || !$endDelimiter) {
            throw new \InvalidArgumentException('Delimiter must not be empty');
        }

        foreach (array($startDelimiter, $endDelimiter) as $delimiter) {
            if (preg_match('/[\\\\a-z0-9\s+]/', strtolower($delimiter)) === 1) {
                throw new \InvalidArgumentException(sprintf('Invalid delimiter %s used', $startDelimiter));
            }
        }

        $hasPairedDelimiter = isset(static::$delimiterPairs[$startDelimiter]);
        if (($hasPairedDelimiter && static::$delimiterPairs[$startDelimiter] !== $endDelimiter)
            || (!$hasPairedDelimiter && $startDelimiter !== $endDelimiter)
        ) {
            throw new \InvalidArgumentException(
                sprintf('Start delimiter %s does not match end delimiter %s', $startDelimiter, $endDelimiter)
            );
        }

        $this->startDelimiter = $startDelimiter;
        $this->endDelimiter = $endDelimiter;
        $this->expression = $expression;
        $this->modifiers = $modifiers;
    }

    /**
     * @return string
     */
    public function getStartDelimiter()
    {
        return $this->startDelimiter;
    }

    /**
     * @return string
     */
    public function getEndDelimiter()
    {
        return $this->endDelimiter;
    }

    /**
     * @return string
     */
    public function getExpression()
    {
        return $this->expression;
    }

    /**
     * @return string
     */
    public function getModifiers()
    {
        return $this->modifiers;
    }

    /**
     * @param string $modifier
     *
     * @return bool
     */
    public function hasModifier($modifier)
    {
        return strpos($this->getModifiers(), $modifier) !== false;
    }

    /**
     * @return string
     */
    public static function getDelimiterPairs()
    {
        return self::$delimiterPairs;
    }
}
<?php

namespace Sstalle\php7cc\Helper\RegExp;

class RegExpParser
{
    /**
     * @param string $regExp
     *
     * @return RegExp
     */
    public function parse($regExp)
    {
        if (!$regExp) {
            throw new \InvalidArgumentException('RegExp is empty');
        }

        $startDelimiter = $regExp[0];
        $delimiterPairs = RegExp::getDelimiterPairs();
        $endDelimiter = isset($delimiterPairs[$startDelimiter])
            ? $delimiterPairs[$startDelimiter]
            : $startDelimiter;
        $endDelimiterPosition = strrpos($regExp, $endDelimiter);
        if (!$endDelimiterPosition) {
            throw new \InvalidArgumentException(sprintf('Closing delimiter %s not found', $startDelimiter));
        }

        return new RegExp(
            $startDelimiter,
            $endDelimiter,
            substr($regExp, 1, $endDelimiterPosition - 1),
            substr($regExp, $endDelimiterPosition + 1)
        );
    }
}
<?php

namespace Sstalle\php7cc\Infrastructure;

use Symfony\Component\Console\Input\InputInterface;

class Application extends \Symfony\Component\Console\Application
{
    const VERSION = '1.2.1';

    /**
     * {@inheritdoc}
     */
    public function __construct()
    {
        parent::__construct('PHP 7 Compatibility Checker', static::VERSION);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        $inputDefinition = parent::getDefinition();
        $inputDefinition->setArguments();

        return $inputDefinition;
    }

    /**
     * {@inheritdoc}
     */
    protected function getCommandName(InputInterface $input)
    {
        return PHP7CCCommand::COMMAND_NAME;
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultCommands()
    {
        $defaultCommands = parent::getDefaultCommands();
        $defaultCommands[] = new PHP7CCCommand();

        return $defaultCommands;
    }
}
<?php

namespace Sstalle\php7cc\Infrastructure;

use Sstalle\php7cc\CLIOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CLIOutputBridge implements CLIOutputInterface
{
    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * @param OutputInterface $output
     */
    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * {@inheritdoc}
     */
    public function write($string)
    {
        $this->output->write($string);
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($string)
    {
        $this->output->writeln($string);
    }
}
<?php

namespace Sstalle\php7cc\Infrastructure;

use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use Pimple\Container;
use Sstalle\php7cc\CLIResultPrinter;
use Sstalle\php7cc\ContextChecker;
use Sstalle\php7cc\CodePreprocessor\ShebangRemover;
use Sstalle\php7cc\ExcludedPathCanonicalizer;
use Sstalle\php7cc\Helper\OSDetector;
use Sstalle\php7cc\Helper\Path\PathHelperFactory;
use Sstalle\php7cc\Helper\RegExp\RegExpParser;
use Sstalle\php7cc\JsonResultPrinter;
use Sstalle\php7cc\Lexer\ExtendedLexer;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;
use Sstalle\php7cc\NodeStatementsRemover;
use Sstalle\php7cc\NodeTraverser\Traverser;
use Sstalle\php7cc\NodeVisitor\Resolver;
use Sstalle\php7cc\PathChecker;
use Sstalle\php7cc\PathCheckExecutor;
use Sstalle\php7cc\PathTraversableFactory;
use Sstalle\php7cc\ResultPrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;
use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;

class ContainerBuilder
{
    const BITWISE_SHIFT_VISITOR_ID = 'visitor.bitwiseShift';

    /**
     * @var array
     */
    protected $checkerVisitors = array(
        'visitor.removedFunctionCall' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\RemovedFunctionCallVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.reservedClassName' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\ReservedClassNameVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.duplicateFunctionParameter' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\DuplicateFunctionParameterVisitor',
        ),
        'visitor.list' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\ListVisitor',
        ),
        'visitor.globalVariableVariable' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\GlobalVariableVariableVisitor',
        ),
        'visitor.indirectVariableOrMethodAccess' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\IndirectVariableOrMethodAccessVisitor',
        ),
        'visitor.funcGetArgs' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\FuncGetArgsVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.continueBreakOutsideLoop' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\ContinueBreakOutsideLoopVisitor',
        ),
        'visitor.foreach' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\ForeachVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.invalidOctalLiteral' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\InvalidOctalLiteralVisitor',
        ),
        'visitor.hexadecimalNumberString' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\HexadecimalNumberStringVisitor',
        ),
        'visitor.escapedUnicodeCodepoint' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\EscapedUnicodeCodepointVisitor',
        ),
        'visitor.arrayOrObjectValueAssignmentByReference' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\ArrayOrObjectValueAssignmentByReferenceVisitor',
        ),
        self::BITWISE_SHIFT_VISITOR_ID => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\BitwiseShiftVisitor',
            'arguments' => array(),
        ),
        'visitor.newAssignmentByReference' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\NewAssignmentByReferenceVisitor',
        ),
        'visitor.httpRawPostData' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\HTTPRawPostDataVisitor',
        ),
        'visitor.pregReplaceEval' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\PregReplaceEvalVisitor',
            'dependencies' => array('regExpParser', 'nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.yieldExpression' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\YieldExpressionVisitor',
        ),
        'visitor.yieldInExpressionContext' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\YieldInExpressionContextVisitor',
        ),
        'visitor.mktime' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\MktimeVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.multipleSwitchDefaults' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\MultipleSwitchDefaultsVisitor',
        ),
        'visitor.passwordHashSalt' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\PasswordHashSaltVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.newClass' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\NewClassVisitor',
        ),
        'visitor.php4Constructor' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\PHP4ConstructorVisitor',
        ),
        'visitor.namespacedNewFunction' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\NamespacedNewFunctionVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.globalNewFunction' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\GlobalNewFunctionVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.divisionModuloByZero' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\DivisionModuloByZeroVisitor',
        ),
        'visitor.sessionSetSaveHandler' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\SessionSetSaveHandlerVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
        'visitor.setcookieEmptyName' => array(
            'class' => '\\Sstalle\\php7cc\\NodeVisitor\\SetcookieEmptyNameVisitor',
            'dependencies' => array('nodeAnalyzer.functionAnalyzer'),
        ),
    );

    /**
     * @var string
     */
    private $outputFormat;

    /**
     * @param OutputInterface $output
     * @param int             $intSize
     *
     * @return Container
     */
    public function buildContainer(OutputInterface $output, $intSize)
    {
        $this->checkerVisitors[static::BITWISE_SHIFT_VISITOR_ID]['arguments'][] = $intSize;

        $container = new Container();

        $container['lexer'] = function () {
            return new ExtendedLexer(array(
                'usedAttributes' => array(
                    'startLine',
                    'endLine',
                    'startTokenPos',
                    'endTokenPos',
                ),
            ));
        };
        $container['parser'] = function ($c) {
            return new Parser($c['lexer']);
        };

        $this->addVisitors($container);

        $visitors = $this->checkerVisitors;
        $container['traverser'] = function () {
            $traverser = new Traverser(false);
            // Resolve fully qualified name (class, interface, function, etc) to ease some process
            $traverser->addVisitor(new NameResolver());

            return $traverser;
        };

        $container['nodeVisitorResolver'] = function ($c) use ($visitors) {
            $visitorInstances = array();
            foreach (array_keys($visitors) as $visitorServiceName) {
                $visitorInstances[] = $c[$visitorServiceName];
            }

            return new Resolver($visitorInstances);
        };
        $container['nodeAnalyzer.functionAnalyzer'] = function () {
            return new FunctionAnalyzer();
        };
        $container['codePreprocessor.shebangRemover'] = function () {
            return new ShebangRemover();
        };
        $container['contextChecker'] = function ($c) {
            return new ContextChecker(
                $c['parser'],
                $c['lexer'],
                $c['traverser'],
                $c['codePreprocessor.shebangRemover']
            );
        };
        $container['output'] = function () use ($output) {
            return new CLIOutputBridge($output);
        };
        $container['nodePrinter'] = function () {
            return new StandardPrettyPrinter();
        };

        $outputFormat = $this->outputFormat;
        $container['resultPrinter'] = function ($c) use ($outputFormat) {
            switch ($outputFormat) {
                case ResultPrinterInterface::PLAIN_FORMAT:
                    return new CLIResultPrinter($c['output'], $c['nodePrinter'], $c['nodeStatementsRemover']);
                case ResultPrinterInterface::JSON_FORMAT:
                    return new JsonResultPrinter($c['output']);
                default: throw new \InvalidArgumentException('Invalid output format: ' . $outputFormat);
            }
        };

        $container['pathChecker'] = function ($c) {
            return new PathChecker($c['contextChecker'], $c['resultPrinter']);
        };
        $container['nodeStatementsRemover'] = function () {
            return new NodeStatementsRemover();
        };
        $container['pathTraversableFactory'] = function ($c) {
            return new PathTraversableFactory($c['excludedPathCanonicalizer']);
        };
        $container['pathCheckExecutor'] = function ($c) {
            return new PathCheckExecutor($c['pathTraversableFactory'], $c['pathChecker'], $c['traverser'], $c['nodeVisitorResolver']);
        };
        $container['excludedPathCanonicalizer'] = function ($c) {
            return new ExcludedPathCanonicalizer($c['pathHelper']);
        };
        $container['osDetector'] = function () {
            return new OSDetector();
        };
        $container['pathHelperFactory'] = function ($c) {
            return new PathHelperFactory($c['osDetector']);
        };
        $container['pathHelper'] = function ($c) {
            /** @var PathHelperFactory $pathHelperFactory */
            $pathHelperFactory = $c['pathHelperFactory'];

            return $pathHelperFactory->createPathHelper();
        };
        $container['regExpParser'] = function () {
            return new RegExpParser();
        };

        return $container;
    }

    /**
     * @param Container $container
     */
    protected function addVisitors(Container $container)
    {
        foreach ($this->checkerVisitors as $visitorServiceName => $visitorParameters) {
            $container[$visitorServiceName] = function ($c) use ($visitorParameters) {
                $visitorDependencyServiceNames = isset($visitorParameters['dependencies'])
                    ? $visitorParameters['dependencies']
                    : array();
                $otherVisitorArguments = isset($visitorParameters['arguments'])
                    ? $visitorParameters['arguments']
                    : array();
                $visitorClassName = $visitorParameters['class'];
                if (!$visitorDependencyServiceNames && !$otherVisitorArguments) {
                    /* This early return is required, because a ReflectionException is thrown
                     * from ReflectionClass::newInstanceArgs for classes without constructors
                     * on PHP 5.3.3
                     */
                    return new $visitorClassName();
                }

                $visitorClassReflection = new \ReflectionClass($visitorClassName);
                $visitorDependencies = array();
                foreach ($visitorDependencyServiceNames as $serviceName) {
                    $visitorDependencies[] = $c[$serviceName];
                }

                return $visitorClassReflection->newInstanceArgs(
                    array_merge($visitorDependencies, $otherVisitorArguments)
                );
            };
        }
    }

    /**
     * @param string $outputFormat
     *
     * @return ContainerBuilder
     */
    public function withOutputFormat($outputFormat)
    {
        $this->outputFormat = $outputFormat;

        return $this;
    }
}
<?php

namespace Sstalle\php7cc\Infrastructure;

use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeVisitor\BitwiseShiftVisitor;
use Sstalle\php7cc\PathCheckSettings;
use Sstalle\php7cc\ResultPrinterInterface;
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;

class PHP7CCCommand extends Command
{
    const COMMAND_NAME = 'php7cc';

    const PATHS_ARGUMENT_NAME = 'paths';
    const EXTENSIONS_OPTION_NAME = 'extensions';
    const EXCEPT_OPTION_NAME = 'except';
    const MESSAGE_LEVEL_OPTION_NAME = 'level';
    const RELATIVE_PATHS_OPTION_NAME = 'relative-paths';
    const INT_SIZE_OPTION_NAME = 'integer-size';
    const OUTPUT_FORMAT_OPTION_NAME = 'output-format';

    /**
     * @var string[]
     */
    protected static $messageLevelMap = array(
        'info' => Message::LEVEL_INFO,
        'warning' => Message::LEVEL_WARNING,
        'error' => Message::LEVEL_ERROR,
    );

    /**
     * @var string[]
     */
    protected static $validOutputFormats = array(
        ResultPrinterInterface::PLAIN_FORMAT, ResultPrinterInterface::JSON_FORMAT,
    );

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName(static::COMMAND_NAME)
            ->setDescription('Checks PHP 5.3 - 5.6 code for compatibility with PHP7')
            ->addArgument(
                static::PATHS_ARGUMENT_NAME,
                InputArgument::REQUIRED | InputArgument::IS_ARRAY,
                'Which file or directory do you want to check?'
            )->addOption(
                static::EXTENSIONS_OPTION_NAME,
                'e',
                InputOption::VALUE_OPTIONAL,
                'Which file extensions do you want to check (separate multiple extensions with commas)?',
                'php'
            )->addOption(
                static::EXCEPT_OPTION_NAME,
                'x',
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
                'Excluded files and directories',
                array()
            )->addOption(
                static::MESSAGE_LEVEL_OPTION_NAME,
                'l',
                InputOption::VALUE_REQUIRED,
                'Only show messages having this or higher severity level (can be info, message or warning)',
                'info'
            )->addOption(
                static::RELATIVE_PATHS_OPTION_NAME,
                'r',
                InputOption::VALUE_NONE,
                'Output paths relative to a checked directory instead of full paths to files'
            )->addOption(
                static::INT_SIZE_OPTION_NAME,
                null,
                InputOption::VALUE_REQUIRED,
                'Target system\'s integer size in bits (needed for bitwise shift checks)',
                BitwiseShiftVisitor::MIN_INT_SIZE
            )->addOption(
                static::OUTPUT_FORMAT_OPTION_NAME,
                'o',
                InputOption::VALUE_OPTIONAL,
                'Output format (plain, json)',
                'plain'
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $paths = $input->getArgument(static::PATHS_ARGUMENT_NAME);
        foreach ($paths as $path) {
            if (!is_file($path) && !is_dir($path)) {
                $output->writeln(sprintf('Path %s must be a file or a directory', $path));

                return;
            }
        }

        $extensionsArgumentValue = $input->getOption(static::EXTENSIONS_OPTION_NAME);
        $extensions = explode(',', $extensionsArgumentValue);
        if (!is_array($extensions)) {
            $output->writeln(
                sprintf(
                    'Something went wrong while parsing file extensions you specified. ' .
                    'Check that %s is a comma-separated list of extensions',
                    $extensionsArgumentValue
                )
            );

            return;
        }

        $messageLevelName = $input->getOption(static::MESSAGE_LEVEL_OPTION_NAME);
        if (!isset(static::$messageLevelMap[$messageLevelName])) {
            $output->writeln(sprintf('Unknown message level %s', $messageLevelName));

            return;
        }
        $messageLevel = static::$messageLevelMap[$messageLevelName];

        $intSize = (int) $input->getOption(static::INT_SIZE_OPTION_NAME);
        if ($intSize <= 0) {
            $output->writeln('Integer size must be greater than 0');

            return;
        }

        $outputFormat = $input->getOption(static::OUTPUT_FORMAT_OPTION_NAME);
        if (!in_array($outputFormat, static::$validOutputFormats)) {
            $output->writeln('Invalid output format: ' . $outputFormat);

            return;
        }

        $containerBuilder = new ContainerBuilder();
        $container = $containerBuilder
            ->withOutputFormat($outputFormat)
            ->buildContainer($output, $intSize);

        $checkSettings = new PathCheckSettings($paths, $extensions);
        $checkSettings->setExcludedPaths($input->getOption(static::EXCEPT_OPTION_NAME));
        $checkSettings->setMessageLevel($messageLevel);
        $checkSettings->setUseRelativePaths($input->getOption(static::RELATIVE_PATHS_OPTION_NAME));

        $container['pathCheckExecutor']->check($checkSettings);
    }
}
<?php

namespace Sstalle\php7cc\Iterator;

use RecursiveIterator;

abstract class AbstractRecursiveFilterIterator extends \RecursiveFilterIterator
{
    /**
     * {@inheritdoc}
     */
    public function __construct(RecursiveIterator $iterator)
    {
        if ($iterator instanceof \RecursiveDirectoryIterator) {
            $iteratorFlags = $iterator->getFlags();
            if ($iteratorFlags & \RecursiveDirectoryIterator::CURRENT_AS_PATHNAME
                || $iteratorFlags & \RecursiveDirectoryIterator::CURRENT_AS_SELF
            ) {
                throw new \InvalidArgumentException(
                    'This iterator requires \RecursiveDirectoryIterator with CURRENT_AS_FILEINFO flag set'
                );
            }

            if ($iteratorFlags & \RecursiveDirectoryIterator::KEY_AS_FILENAME) {
                throw new \InvalidArgumentException(
                    'This iterator requires \RecursiveDirectoryIterator with KEY_AS_PATHNAME flag set'
                );
            }
        }

        parent::__construct($iterator);
    }
}
<?php

namespace Sstalle\php7cc\Iterator;

class ExcludedPathFilteringRecursiveIterator extends AbstractRecursiveFilterIterator
{
    /**
     * @var string[]
     */
    protected $excludedPaths = array();

    /**
     * @param \RecursiveIterator $iterator
     * @param \string[]          $excludedPaths
     */
    public function __construct(\RecursiveIterator $iterator, array $excludedPaths)
    {
        parent::__construct($iterator);
        $this->excludedPaths = array_flip($excludedPaths);
    }

    /**
     * {@inheritdoc}
     */
    public function accept()
    {
        return !isset($this->excludedPaths[realpath($this->key())]);
    }

    /**
     * {@inheritdoc}
     */
    public function getChildren()
    {
        return new static($this->getInnerIterator()->getChildren(), array_flip($this->excludedPaths));
    }
}
<?php

namespace Sstalle\php7cc\Iterator;

class ExtensionFilteringRecursiveIterator extends AbstractRecursiveFilterIterator
{
    /**
     * @var string[]
     */
    protected $allowedExtensions = array();

    /**
     * @var string[]
     */
    protected $alwaysAllowedFiles = array();

    /**
     * @param \RecursiveIterator $iterator
     * @param string[]           $allowedExtensions
     * @param array              $alwaysAllowedFiles
     */
    public function __construct(
        \RecursiveIterator $iterator,
        array $allowedExtensions = array(),
        array $alwaysAllowedFiles = array()
    ) {
        parent::__construct($iterator);
        $this->allowedExtensions = $allowedExtensions;
        $this->alwaysAllowedFiles = array_flip($alwaysAllowedFiles);
    }

    /**
     * {@inheritdoc}
     */
    public function accept()
    {
        $currentKey = $this->key();
        $isFile = $currentKey && is_file($currentKey);

        if ($isFile && isset($this->alwaysAllowedFiles[realpath($currentKey)])) {
            return true;
        }

        return !$isFile || in_array(pathinfo($currentKey, PATHINFO_EXTENSION), $this->allowedExtensions);
    }

    /**
     * {@inheritdoc}
     */
    public function getChildren()
    {
        return new static(
            $this->getInnerIterator()->getChildren(),
            $this->allowedExtensions,
            array_flip($this->alwaysAllowedFiles)
        );
    }
}
<?php

namespace Sstalle\php7cc\Iterator;

use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
use Symfony\Component\Finder\SplFileInfo;

class FileDirectoryListRecursiveIterator implements \RecursiveIterator
{
    /**
     * @var string[]
     */
    protected $data;

    /**
     * @var int
     */
    protected $position = 0;

    /**
     * @param \string[] $data
     */
    public function __construct(array $data)
    {
        foreach ($data as $fileOrDirectory) {
            if (!is_file($fileOrDirectory) && !is_dir($fileOrDirectory)) {
                throw new \UnexpectedValueException(sprintf('%s is not file or directory', $fileOrDirectory));
            }
        }

        $this->data = $data;
    }

    /**
     * {@inheritdoc}
     */
    public function current()
    {
        $fileName = realpath($this->data[$this->position]);

        return new SplFileInfo($fileName, '', basename($fileName));
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        ++$this->position;
    }

    /**
     * {@inheritdoc}
     */
    public function key()
    {
        return $this->data[$this->position];
    }

    /**
     * {@inheritdoc}
     */
    public function valid()
    {
        return isset($this->data[$this->position]);
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        $this->position = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function hasChildren()
    {
        return is_dir($this->data[$this->position]);
    }

    /**
     * {@inheritdoc}
     */
    public function getChildren()
    {
        return new RecursiveDirectoryIterator(
            $this->data[$this->position],
            \RecursiveDirectoryIterator::KEY_AS_PATHNAME
            | \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO
            | \RecursiveDirectoryIterator::SKIP_DOTS
        );
    }
}
<?php

namespace Sstalle\php7cc;

use Bcn\Component\Json\Writer;
use Sstalle\php7cc\CompatibilityViolation\CheckMetadata;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;

class JsonResultPrinter implements ResultPrinterInterface
{
    /**
     * @var CLIOutputInterface
     */
    private $output;
    /**
     * @var Writer
     */
    private $writer;
    /**
     * @var resource
     */
    private $jsonFile;

    public function __construct(CLIOutputInterface $output)
    {
        $this->output = $output;
        $this->jsonFile = fopen('php://temp', 'wb');
        $this->writer = new Writer($this->jsonFile);
        $this->writer->enter(Writer::TYPE_OBJECT);
        $this->writer->enter('files', Writer::TYPE_ARRAY);
    }

    /**
     * @param ContextInterface $context
     */
    public function printContext(ContextInterface $context)
    {
        $resource = $context->getCheckedResourceName();

        $extractMessageDataCallback = $this->buildCallback();

        $this->writer->write(null, array(
            'name' => $resource,
            'errors' => array_map($extractMessageDataCallback, $context->getErrors()),
            'warnings' => array_map($extractMessageDataCallback, $context->getMessages()),
        ));
    }
    /**
     * @param CheckMetadata $metadata
     */
    public function printMetadata(CheckMetadata $metadata)
    {
        $this->writer->leave();
        $this->writer->enter('summary', Writer::TYPE_OBJECT);
        $this->writer->write('checkedFiles', $metadata->getCheckedFileCount());
        $this->writer->write('elapsedTime', $metadata->getElapsedTime());
        $this->writer->leave();
        $this->writer->leave();

        rewind($this->jsonFile);
        $this->output->writeln(stream_get_contents($this->jsonFile));
    }

    /**
     * @return \Closure
     */
    private function buildCallback()
    {
        return function (AbstractBaseMessage $m) {
            return array('line' => $m->getLine(), 'text' => $m->getRawText());
        };
    }

    public function __destruct()
    {
        fclose($this->jsonFile);
    }
}
<?php

namespace Sstalle\php7cc\Lexer;

use PhpParser\Lexer;
use PhpParser\Parser;

class ExtendedLexer extends Lexer\Emulative
{
    /**
     * {@inheritdoc}
     */
    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null)
    {
        $tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);

        if ($tokenId == Parser::T_CONSTANT_ENCAPSED_STRING // non-interpolated string
            || $tokenId == Parser::T_LNUMBER               // integer
            || $tokenId == Parser::T_DNUMBER               // floating point number
        ) {
            // could also use $startAttributes, doesn't really matter here
            $endAttributes['originalValue'] = $value;
        }

        if ($tokenId == Parser::T_CONSTANT_ENCAPSED_STRING) {
            $endAttributes['isDoubleQuoted'] = $value[0] === '"';
        }

        if ($tokenId == Parser::T_END_HEREDOC) {
            $endAttributes['isHereDoc'] = true;
        }

        return $tokenId;
    }
}
<?php

namespace Sstalle\php7cc\NodeAnalyzer;

use PhpParser\Node;

class FunctionAnalyzer
{
    /**
     * @param Node            $node
     * @param string|string[] $checkedFunctionName If an array as passed, function names should be keys
     *
     * @return bool
     */
    public function isFunctionCallByStaticName(Node $node, $checkedFunctionName)
    {
        $isFunctionCallByStaticName = $node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name;
        if (!$isFunctionCallByStaticName) {
            return $isFunctionCallByStaticName;
        }

        $calledFunctionName = strtolower($node->name->toString());

        return is_array($checkedFunctionName)
            ? isset($checkedFunctionName[$calledFunctionName])
            : $calledFunctionName === $checkedFunctionName;
    }
}
<?php

namespace Sstalle\php7cc;

use PhpParser\Node;

class NodeStatementsRemover
{
    /**
     * @param Node[] $nodes
     * @param bool   $cloneNodes
     *
     * @return \PhpParser\Node[]
     */
    public function removeInnerStatements($nodes, $cloneNodes = true)
    {
        $resultNodes = array();

        foreach ($nodes as $node) {
            if ($cloneNodes) {
                $node = clone $node;
            }

            if (property_exists($node, 'stmts')) {
                $node->stmts = array();
            }

            if ($node instanceof Node\Stmt\Switch_) {
                $node->cases = array();
            }

            $resultNodes[] = $node;
        }

        return $resultNodes;
    }
}
<?php

namespace Sstalle\php7cc\NodeTraverser;

use PhpParser\NodeTraverser;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;
use Sstalle\php7cc\NodeVisitor\VisitorInterface;
use Sstalle\php7cc\Token\TokenCollection;

class Traverser extends NodeTraverser
{
    /**
     * {@inheritdoc}
     */
    public function traverse(array $nodes, ContextInterface $context = null, array $tokens = array())
    {
        if ($context) {
            $tokenCollection = new TokenCollection($tokens);

            foreach ($this->visitors as $visitor) {
                if ($visitor instanceof VisitorInterface) {
                    $visitor->initializeContext($context);
                    $visitor->setTokenCollection($tokenCollection);
                }
            }
        }

        return parent::traverse($nodes);
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;

abstract class AbstractNestedLoopVisitor extends AbstractVisitor
{
    /**
     * @var \SplStack
     */
    private $loopStacks;

    /**
     * {@inheritdoc}
     */
    public function beforeTraverse(array $nodes)
    {
        $this->loopStacks = new \SplStack();
        $this->loopStacks->push(new \SplStack());
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\FunctionLike) {
            $this->loopStacks->push(new \SplStack());
        } elseif ($this->isTargetLoopNode($node)) {
            $this->getCurrentLoopStack()->push($node);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof Node\FunctionLike) {
            $this->loopStacks->pop();
        } elseif ($this->isTargetLoopNode($node)) {
            $this->getCurrentLoopStack()->pop();
        }
    }

    /**
     * @return \SplStack
     */
    protected function getCurrentLoopStack()
    {
        return $this->loopStacks->top();
    }

    /**
     * @param Node $node
     *
     * @return bool
     */
    abstract protected function isTargetLoopNode(Node $node);
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

abstract class AbstractNewFunctionVisitor extends AbstractVisitor
{
    const FUNCTION_EXISTS_FUNCTION_NAME = 'function_exists';

    /**
     * @var string[]
     */
    private static $newFunctions = array(
        'random_bytes',
        'random_int',

        'error_clear_last',

        'gmp_random_seed',

        'intdiv',

        'preg_replace_callback_array',

        'posix_setrlimit',

        'inflate_add',
        'inflate_init',
        'deflate_add',
        'deflate_init',
    );

    /**
     * @var string[]
     */
    private static $lowerCasedNewFunctions = array();

    /**
     * @var \SplStack
     */
    private $ifStatementStack;

    /**
     * @var FunctionAnalyzer
     */
    private $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        foreach (self::$newFunctions as $function) {
            self::$lowerCasedNewFunctions[strtolower($function)] = $function;
        }

        $this->ifStatementStack = new \SplStack();
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Stmt\If_) {
            $this->ifStatementStack->push($node);

            return;
        }

        if ($this->isNewFunctionCall($node)
            && !$this->isFunctionExistenceChecked($node)
            && $this->accepts($node)
        ) {
            $declaredFunctionName = $this->extractNormalizedFunctionName($node);
            $this->addContextMessage($this->getMessageText(self::$lowerCasedNewFunctions[$declaredFunctionName]), $node);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function leaveNode(Node $node)
    {
        if (!$this->ifStatementStack->isEmpty() && $this->ifStatementStack->top() === $node) {
            $this->ifStatementStack->pop();
        }
    }

    /**
     * @param Node\Stmt\Function_ $node
     *
     * @return bool
     */
    abstract protected function accepts(Node\Stmt\Function_ $node);

    /**
     * @param string $functionName
     *
     * @return string
     */
    abstract protected function getMessageText($functionName);

    /**
     * @param Node $node
     *
     * @return bool
     */
    private function isNewFunctionCall(Node $node)
    {
        return $node instanceof Node\Stmt\Function_
            && ($lowerCasedFunction = $this->extractNormalizedFunctionName($node))
            && array_key_exists($lowerCasedFunction, self::$lowerCasedNewFunctions);
    }

    /**
     * @param Node\Stmt\Function_ $declaredFunction
     *
     * @return bool
     */
    private function isFunctionExistenceChecked(Node\Stmt\Function_ $declaredFunction)
    {
        /** @var Node\Stmt\If_ $ifStatement */
        foreach ($this->ifStatementStack as $ifStatement) {
            $condition = $ifStatement->cond;

            $isConditionNegatedFunctionExistsCall = $condition
                && ($condition instanceof Node\Expr\BooleanNot)
                && $this->functionAnalyzer->isFunctionCallByStaticName($condition->expr, static::FUNCTION_EXISTS_FUNCTION_NAME);
            if (!$isConditionNegatedFunctionExistsCall) {
                continue;
            }

            /** @var Node\Expr\FuncCall $conditionFunction */
            $conditionFunction = $condition->expr;
            $checkedFunctionName = isset($conditionFunction->args[0]) ? $conditionFunction->args[0] : null;
            $isCheckedFunctionNameScalarString = $checkedFunctionName
                && ($checkedFunctionName instanceof Node\Arg)
                && ($checkedFunctionName->value instanceof Node\Scalar\String_);
            if (!$isCheckedFunctionNameScalarString) {
                continue;
            }

            $checkedFunctionName = $this->normalizeFunctionName($checkedFunctionName->value->value);
            if ($checkedFunctionName && $checkedFunctionName[0] === '\\') {
                $checkedFunctionName = substr($checkedFunctionName, 1);
            }

            $declaredFunctionName = $this->extractNormalizedFunctionName($declaredFunction);
            if ($checkedFunctionName === $declaredFunctionName) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param Node\Stmt\Function_ $function
     *
     * @return string
     */
    private function extractNormalizedFunctionName(Node\Stmt\Function_ $function)
    {
        return $this->normalizeFunctionName($function->name);
    }

    /**
     * @param string $name
     *
     * @return string
     */
    private function normalizeFunctionName($name)
    {
        return strtolower($name);
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\Token\TokenCollection;

abstract class AbstractVisitor extends NodeVisitorAbstract implements VisitorInterface
{
    const LEVEL = Message::LEVEL_INFO;

    /**
     * @var ContextInterface
     */
    protected $context;

    /**
     * @var TokenCollection
     */
    protected $tokenCollection;

    protected $tokens;

    /**
     * @param ContextInterface $context
     */
    public function initializeContext(ContextInterface $context)
    {
        $this->context = $context;
    }

    /**
     * {@inheritdoc}
     */
    public function setTokenCollection(TokenCollection $tokenCollection)
    {
        $this->tokenCollection = $tokenCollection;
    }

    /**
     * {@inheritdoc}
     */
    public function getLevel()
    {
        return static::LEVEL;
    }

    /**
     * @param string $text
     * @param Node   $node
     */
    protected function addContextMessage($text, Node $node)
    {
        $this->context->addMessage(new Message($text, $node->getAttribute('startLine'), $this->getLevel(), array($node)));
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class ArrayOrObjectValueAssignmentByReferenceVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Expr\AssignRef) {
            return;
        }

        $this->checkArrayValueByReferenceCreation($node) || $this->checkObjectPropertyByReferenceCreation($node);
    }

    /**
     * @param Node\Expr\AssignRef $node
     *
     * @return bool
     */
    protected function checkArrayValueByReferenceCreation(Node\Expr\AssignRef $node)
    {
        if ($node->var instanceof Node\Expr\ArrayDimFetch && $node->var->dim
            && $node->expr instanceof Node\Expr\ArrayDimFetch && $node->expr->dim
        ) {
            $this->addContextMessage(
                'Possible array element creation during by-reference assignment',
                $node
            );

            return true;
        }

        return false;
    }

    /**
     * @param Node\Expr\AssignRef $node
     *
     * @return bool
     */
    protected function checkObjectPropertyByReferenceCreation(Node\Expr\AssignRef $node)
    {
        if ($node->var instanceof Node\Expr\PropertyFetch && $node->expr instanceof Node\Expr\PropertyFetch) {
            $this->addContextMessage(
                'Possible object property creation during by-reference assignment',
                $node
            );

            return true;
        }

        return false;
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class BitwiseShiftVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;
    const MIN_INT_SIZE = 32;

    /**
     * @var int
     */
    protected $intSize;

    /**
     * @param $intSize
     */
    public function __construct($intSize = self::MIN_INT_SIZE)
    {
        if ($intSize <= 0) {
            throw new \InvalidArgumentException(sprintf('Integer size must be greater than 0, %d given', $intSize));
        }

        $this->intSize = $intSize;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        $isLeftShift = $node instanceof Node\Expr\BinaryOp\ShiftLeft;
        $isRightShift = $node instanceof Node\Expr\BinaryOp\ShiftRight;
        if (!$isLeftShift && !$isRightShift) {
            return;
        }

        $rightOperand = $node->right;
        if ($rightOperand instanceof Node\Expr\UnaryMinus && $rightOperand->expr instanceof Node\Scalar\LNumber
            && $rightOperand->expr->value > 0
        ) {
            $this->addContextMessage(
                'Bitwise shift by a negative number',
                $node
            );
        } elseif ($rightOperand instanceof Node\Scalar\LNumber && $rightOperand->value >= $this->intSize) {
            $this->addContextMessage(
                sprintf('Bitwise shift by %d bits', $rightOperand->value),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class ContinueBreakOutsideLoopVisitor extends AbstractNestedLoopVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        parent::enterNode($node);

        if (($node instanceof Node\Stmt\Break_ || $node instanceof Node\Stmt\Continue_)
            && $this->getCurrentLoopStack()->isEmpty()
        ) {
            $messageText = sprintf(
                '%s not in the loop or switch context',
                $node instanceof Node\Stmt\Break_ ? 'break' : 'continue'
            );
            $this->addContextMessage($messageText, $node);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function isTargetLoopNode(Node $node)
    {
        return $node instanceof Node\Stmt\While_ || $node instanceof Node\Stmt\Do_
            || $node instanceof Node\Stmt\Foreach_ || $node instanceof Node\Stmt\For_
            || $node instanceof Node\Stmt\Switch_;
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class DivisionModuloByZeroVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        $isDivision = $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div;
        $isModulo = $node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Node\Expr\AssignOp\Mod;

        if (!($isDivision || $isModulo)) {
            return;
        }

        $divisor = $node instanceof Node\Expr\BinaryOp ? $node->right : $node->expr;
        if ($divisor instanceof Node\Scalar\LNumber && $divisor->value == 0) {
            $this->addContextMessage(
                sprintf('%s by zero', $isDivision ? 'Division' : 'Modulo'),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class DuplicateFunctionParameterVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\FunctionLike) {
            return;
        }

        $parametersNames = array();
        foreach ($node->getParams() as $parameter) {
            $currentParameterName = $parameter->name;
            if (!isset($parametersNames[$currentParameterName])) {
                $parametersNames[$currentParameterName] = false;
            } elseif (!$parametersNames[$currentParameterName]) {
                $this->addContextMessage(
                    sprintf('Duplicate function parameter name "%s"', $currentParameterName),
                    $node
                );

                $parametersNames[$currentParameterName] = true;
            }
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class EscapedUnicodeCodepointVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Scalar\String_) {
            return;
        }

        $unquotedStringValue = null;
        if ($node->getAttribute('isDoubleQuoted')) {
            $unquotedStringValue = substr($node->getAttribute('originalValue'), 1, -1);
        } elseif ($node->getAttribute('isHereDoc')) {
            // Skip T_START_HEREDOC, T_END_HEREDOC
            $unquotedStringValue = '';
            foreach (range($node->getAttribute('startTokenPos') + 1, $node->getAttribute('endTokenPos') - 1) as $i) {
                $unquotedStringValue .= $this->tokenCollection->getTokenStringValueAt($i);
            }
        }

        if (!$unquotedStringValue) {
            return;
        }

        $matches = array();
        if (preg_match('/((?<!\\\\)\\\\u{.*})/', $unquotedStringValue, $matches)) {
            $this->addContextMessage(
                sprintf('Unicode codepoint escaping "%s" in a string', $matches[0]),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class ForeachVisitor extends AbstractNestedLoopVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * @var array
     */
    protected $arrayPointerModifyingFunctions = array(
        'current',
        'end',
        'reset',
        'prev',
        'next',
        'each',
    );

    /**
     * @var array
     */
    protected $arrayModifyingFunctions = array(
        'array_pop',
        'array_push',
        'array_shift',
        'array_unshift',
    );

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
        $this->arrayPointerModifyingFunctions = array_flip($this->arrayPointerModifyingFunctions);
        $this->arrayModifyingFunctions = array_flip($this->arrayModifyingFunctions);
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        parent::enterNode($node);

        if ($this->isTargetLoopNode($node)) {
            $this->checkNestedByReferenceForeach($node);
        } elseif (!$this->getCurrentLoopStack()->isEmpty()) {
            $this->checkInternalArrayPointerAccessInByValueForeach($node);
            $this->checkArrayModificationByFunctionInByReferenceForeach($node);
            $this->checkAddingToArrayInByReferenceForeach($node);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function isTargetLoopNode(Node $node)
    {
        return $node instanceof Node\Stmt\Foreach_;
    }

    /**
     * @param Node $node
     */
    protected function checkInternalArrayPointerAccessInByValueForeach(Node $node)
    {
        if ($this->hasFunctionCallWithForeachArgument($node, $this->arrayPointerModifyingFunctions, true)) {
            $this->addContextMessage(
                'Possible internal array pointer access/modification in a by-value foreach loop',
                $node
            );
        }
    }

    /**
     * @param Node $node
     */
    protected function checkArrayModificationByFunctionInByReferenceForeach(Node $node)
    {
        if ($this->hasFunctionCallWithForeachArgument($node, $this->arrayModifyingFunctions, false)) {
            $this->addContextMessage(
                'Possible array modification using internal function in a by-reference foreach loop',
                $node
            );
        }
    }

    /**
     * @param Node      $node
     * @param array     $functions
     * @param null|bool $skippedByRefType Reference type (by value/by reference) to skip
     *
     * @return bool
     */
    protected function hasFunctionCallWithForeachArgument(Node $node, array $functions, $skippedByRefType = null)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, $functions)) {
            return false;
        }

        /** @var Node\Expr\FuncCall $node */
        foreach ($node->args as $argument) {
            /** @var Node\Stmt\Foreach_ $foreach */
            foreach ($this->getCurrentLoopStack() as $foreach) {
                if ($skippedByRefType !== null && $foreach->byRef === $skippedByRefType) {
                    continue;
                }

                if ($argument->value instanceof Node\Expr\Variable
                    && $argument->value->name === $this->getForeachVariableName($foreach)
                ) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param Node $node
     */
    protected function checkAddingToArrayInByReferenceForeach(Node $node)
    {
        if (!($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef)
            || !$node->var instanceof Node\Expr\ArrayDimFetch || !$node->var->var instanceof Node\Expr\Variable
        ) {
            return;
        }

        /** @var Node\Stmt\Foreach_ $foreach */
        foreach ($this->getCurrentLoopStack() as $foreach) {
            if (!$foreach->byRef) {
                continue;
            }

            if ($node->var->var->name === $this->getForeachVariableName($foreach)) {
                $this->addContextMessage(
                    'Possible adding to array on the last iteration of a by-reference foreach loop',
                    $node
                );
            }
        }
    }

    /**
     * @param Node\Stmt\Foreach_ $foreach
     */
    protected function checkNestedByReferenceForeach(Node\Stmt\Foreach_ $foreach)
    {
        if (!$foreach->byRef) {
            return;
        }

        /** @var Node\Stmt\Foreach_ $ancestorForeach */
        foreach ($this->getCurrentLoopStack() as $ancestorForeach) {
            if ($ancestorForeach === $foreach) {
                continue;
            }

            if ($ancestorForeach->byRef) {
                $this->addContextMessage(
                    'Nested by-reference foreach loop, make sure that array modifications (if any) do what you expect',
                    $foreach
                );

                return;
            }
        }
    }

    /**
     * @param Node\Stmt\Foreach_ $foreach
     *
     * @return Node\Expr|string|void
     */
    protected function getForeachVariableName(Node\Stmt\Foreach_ $foreach)
    {
        if ($foreach->expr instanceof Node\Expr\Variable) {
            return $foreach->expr->name;
        } elseif (($foreach->expr instanceof Node\Expr\Assign || $foreach->expr instanceof Node\Expr\AssignRef)
            && $foreach->expr->var instanceof Node\Expr\Variable
        ) {
            return $foreach->expr->var->name;
        }

        return;
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class FuncGetArgsVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * @var string[]
     */
    protected $possiblyArgumentModifyingClasses = array(
        'PhpParser\\Node\\Stmt\\Foreach_',
        'PhpParser\\Node\\Stmt\\Global_',
        'PhpParser\\Node\\Stmt\\Unset_',
        'PhpParser\\Node\\Expr\\Assign',
        'PhpParser\\Node\\Expr\\AssignOp',
        'PhpParser\\Node\\Expr\\AssignRef',
        'PhpParser\\Node\\Expr\\FuncCall',
        'PhpParser\\Node\\Expr\\List',
        'PhpParser\\Node\\Expr\\MethodCall',
        'PhpParser\\Node\\Expr\\PostDec',
        'PhpParser\\Node\\Expr\\PostInc',
        'PhpParser\\Node\\Expr\\PreDec',
        'PhpParser\\Node\\Expr\\PreInc',
        'PhpParser\\Node\\Expr\\StaticCall',
    );

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * If current function's arguments could have been modified, value on top of the stack is true.
     * Otherwise false.
     *
     * @var \SplStack
     */
    protected $argumentModificationStack;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function beforeTraverse(array $nodes)
    {
        $this->argumentModificationStack = new \SplStack();
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        $isCurrentNodeFunctionLike = $node instanceof Node\FunctionLike;
        if ($isCurrentNodeFunctionLike || $this->argumentModificationStack->isEmpty()
            || !$this->argumentModificationStack->top()
            || !$this->functionAnalyzer->isFunctionCallByStaticName($node, array_flip(array('func_get_arg', 'func_get_args')))
        ) {
            $isCurrentNodeFunctionLike && $this->argumentModificationStack->push(false);

            return;
        }

        /** @var Node\Expr\FuncCall $node */
        $functionName = $node->name->toString();
        $this->addContextMessage(
            sprintf('Function argument(s) returned by "%s" might have been modified', $functionName),
            $node
        );
    }

    /**
     * {@inheritdoc}
     */
    public function leaveNode(Node $node)
    {
        if ($this->argumentModificationStack->isEmpty()) {
            return;
        }

        if ($node instanceof Node\FunctionLike) {
            $this->argumentModificationStack->pop();

            return;
        }

        foreach ($this->possiblyArgumentModifyingClasses as $class) {
            if ($node instanceof $class) {
                $this->argumentModificationStack->pop();
                $this->argumentModificationStack->push(true);

                return;
            }
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class GlobalNewFunctionVisitor extends AbstractNewFunctionVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    protected function accepts(Node\Stmt\Function_ $node)
    {
        return isset($node->namespacedName) && count($node->namespacedName->parts) == 1;
    }

    /**
     * {@inheritdoc}
     */
    protected function getMessageText($functionName)
    {
        return sprintf('Cannot redeclare global function "%s"', $functionName);
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class GlobalVariableVariableVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Stmt\Global_) {
            return;
        }

        foreach ($node->vars as $globalVariable) {
            if (!(
                $globalVariable->name instanceof Node\Expr\PropertyFetch
                || $globalVariable->name instanceof Node\Expr\StaticPropertyFetch
                || $globalVariable->name instanceof Node\Expr\ArrayDimFetch
                )
            ) {
                continue;
            }

            $startTokenPosition = $globalVariable->getAttribute('startTokenPos') + 1;
            $endTokenPosition = $globalVariable->getAttribute('endTokenPos');
            if ($this->tokenCollection->isTokenEqualToOrPrecededBy($startTokenPosition, '{')
                && $this->tokenCollection->isTokenEqualToOrFollowedBy($endTokenPosition, '}')
            ) {
                continue;
            }

            $this->addContextMessage(
                'Complex variable without curly braces in global keyword',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class HTTPRawPostDataVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;
    const HTTP_RAW_POST_DATA_VARIABLE_NAME = 'HTTP_RAW_POST_DATA';

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        $isVariableAccessedByName = $node instanceof Node\Expr\Variable
            && $node->name === static::HTTP_RAW_POST_DATA_VARIABLE_NAME;
        $isVariableAccessedThroughGlobals = $node instanceof Node\Expr\ArrayDimFetch
            && $node->var instanceof Node\Expr\Variable
            && $node->var->name == 'GLOBALS'
            && $node->dim instanceof Node\Scalar\String_
            && $node->dim->value === static::HTTP_RAW_POST_DATA_VARIABLE_NAME;

        if ($isVariableAccessedByName || $isVariableAccessedThroughGlobals) {
            $this->addContextMessage(
                sprintf(
                    'Removed "%s" variable used',
                    static::HTTP_RAW_POST_DATA_VARIABLE_NAME
                ),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class HexadecimalNumberStringVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Scalar\String_) {
            return;
        }

        if (preg_match('/^0x[a-fA-F0-9]+$/', $node->value)) {
            $this->addContextMessage(
                'String containing number in hexadecimal notation',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class IndirectVariableOrMethodAccessVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!($node instanceof Node\Expr\PropertyFetch
                || $node instanceof Node\Expr\MethodCall
                || $node instanceof Node\Expr\StaticCall
                || $node instanceof Node\Expr\Variable
            ) || !$node->name instanceof Node\Expr\ArrayDimFetch
        ) {
            return;
        }

        $nodeName = $node->name;
        if ($this->tokenCollection->isTokenEqualToOrPrecededBy($nodeName->getAttribute('startTokenPos') - 1, '{')
            && $this->tokenCollection->isTokenEqualToOrFollowedBy($nodeName->getAttribute('endTokenPos') + 1, '}')
        ) {
            return;
        }

        $this->addContextMessage(
            'Indirect variable, property or method access',
            $node
        );
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class InvalidOctalLiteralVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Scalar\LNumber) {
            return;
        }

        $originalNumberValue = $node->getAttribute('originalValue', '');

        if (preg_match('/^0[0-7]*[89]+/', $originalNumberValue)) {
            $this->addContextMessage(
                sprintf('Invalid octal literal %s', $originalNumberValue),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class ListVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Expr\List_) {
            $hasNonNullVar = false;
            foreach ($node->vars as $var) {
                if ($var !== null) {
                    $hasNonNullVar = true;
                    break;
                }
            }

            if (!$hasNonNullVar) {
                $this->addContextMessage(
                    'Empty list assignment',
                    $node
                );
            }
        }

        if ($node instanceof Node\Expr\Assign && $node->var instanceof Node\Expr\List_
            && ($node->expr instanceof Node\Scalar\String_ || $node->expr instanceof Node\Expr\Cast\String_)
        ) {
            $this->addContextMessage(
                'list unpacking string',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class MktimeVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * @var string[]
     */
    protected $mktimeFamilyFunctions = array('mktime', 'gmmktime');

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
        $this->mktimeFamilyFunctions = array_flip($this->mktimeFamilyFunctions);
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, $this->mktimeFamilyFunctions)
            || count($node->args) < 7
        ) {
            return;
        }

        $this->addContextMessage(
            sprintf('Removed argument $is_dst used for function "%s"', $node->name->__toString()),
            $node
        );
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class MultipleSwitchDefaultsVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Node\Stmt\Switch_) {
            return;
        }

        $defaultCaseCount = 0;
        foreach ($node->cases as $case) {
            if ($case->cond === null) {
                ++$defaultCaseCount;
            }
        }

        if ($defaultCaseCount > 1) {
            $this->addContextMessage(
                'Multiple default cases defined for the switch statement',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class NamespacedNewFunctionVisitor extends AbstractNewFunctionVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * {@inheritdoc}
     */
    protected function accepts(Node\Stmt\Function_ $node)
    {
        return isset($node->namespacedName) && count($node->namespacedName->parts) > 1;
    }

    /**
     * {@inheritdoc}
     */
    protected function getMessageText($functionName)
    {
        return sprintf(
            'Your namespaced function "%s" could replace the new global function added in PHP 7',
            $functionName
        );
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class NewAssignmentByReferenceVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Expr\AssignRef && $node->expr instanceof Node\Expr\New_) {
            $this->addContextMessage(
                'Result of new is assigned by reference',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class NewClassVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * @var string[]
     */
    private static $newClasses = array(
        'IntlChar',

        'ReflectionGenerator',
        'ReflectionType',

        'SessionUpdateTimestampHandlerInterface',

        'Throwable',
        'Error',
        'TypeError',
        'ParseError',
        'AssertionError',
        'ArithmeticError',
        'DivisionByZeroError',
    );

    /**
     * @var string[]
     */
    private static $lowerCasedNewClasses = array();

    public function __construct()
    {
        foreach (self::$newClasses as $className) {
            self::$lowerCasedNewClasses[strtolower($className)] = $className;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Stmt\ClassLike
            && isset($node->namespacedName) // Property set by the NameResolver visitor
            && count($node->namespacedName->parts) === 1
            && ($lowerCasedClassName = strtolower($node->name))
            && array_key_exists($lowerCasedClassName, self::$lowerCasedNewClasses)) {
            $this->addContextMessage(
                sprintf(
                    'Class/trait/interface "%s" was added in the global namespace',
                    self::$lowerCasedNewClasses[$lowerCasedClassName]
                ),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class PHP4ConstructorVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Stmt\Class_) {
            $currentClassName = $node->name;
            $hasPhp4Constructor = false;
            $hasPhp5Constructor = false;
            $php4ConstructorNode = null;

            // Anonymous class can't use php4 constructor by definition
            if (empty($currentClassName)) {
                return;
            }

            // Checks if class is namespaced (property namespacedName was set by the NameResolver visitor)
            if (count($node->namespacedName->parts) > 1) {
                return;
            }

            foreach ($node->stmts as $stmt) {
                // Check for constructors
                if ($stmt instanceof Node\Stmt\ClassMethod) {
                    if ($stmt->name === '__construct') {
                        $hasPhp5Constructor = true;
                    }

                    if ($stmt->name === $currentClassName) {
                        $hasPhp4Constructor = true;
                        $php4ConstructorNode = $stmt;
                    }
                }
            }

            if ($hasPhp4Constructor && !$hasPhp5Constructor) {
                $this->addContextMessage(
                    'PHP 4 constructors are now deprecated',
                    $php4ConstructorNode
                );
            }
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class PasswordHashSaltVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;
    const PASSWORD_HASH_OPTIONS_ARGUMENT_INDEX = 2;

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, array('password_hash' => true))
            || !isset($node->args[static::PASSWORD_HASH_OPTIONS_ARGUMENT_INDEX])
            || !($node->args[static::PASSWORD_HASH_OPTIONS_ARGUMENT_INDEX]->value instanceof Node\Expr\Array_)
        ) {
            return;
        }

        /** @var Node\Expr\Array_ $passwordHashOptions */
        $passwordHashOptions = $node->args[static::PASSWORD_HASH_OPTIONS_ARGUMENT_INDEX]->value;
        /** @var $node Node\Expr\FuncCall */
        foreach ($passwordHashOptions->items as $option) {
            if ($option->key instanceof Node\Scalar\String_ && $option->key->value === 'salt') {
                $this->addContextMessage(
                    'Deprecated option "salt" passed to password_hash function',
                    $node
                );

                break;
            }
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;
use Sstalle\php7cc\Helper\RegExp\RegExpParser;

class PregReplaceEvalVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;
    const PREG_REPLACE_EVAL_MODIFIER = 'e';

    /**
     * @var RegExpParser
     */
    protected $regExpParser;

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param RegExpParser     $regExpParser
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(RegExpParser $regExpParser, FunctionAnalyzer $functionAnalyzer)
    {
        $this->regExpParser = $regExpParser;
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, 'preg_replace')) {
            return;
        }

        /** @var Node\Expr\FuncCall $node */
        $regExpPatternArgument = $node->args[0];
        if (!$regExpPatternArgument->value instanceof Node\Scalar\String_) {
            return;
        }

        $regExp = $this->regExpParser->parse($regExpPatternArgument->value->value);
        if ($regExp->hasModifier(static::PREG_REPLACE_EVAL_MODIFIER)) {
            $this->addContextMessage(
                sprintf('Removed regular expression modifier "%s" used', static::PREG_REPLACE_EVAL_MODIFIER),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class RemovedFunctionCallVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * @var string[]
     */
    protected $removedFunctionNames = array(
        // Removed in favor of call_user_func* functions
        'call_user_method',
        'call_user_method_array',

        // Removed in favor of the stream_set_blocking() function
        'set_socket_blocking',

        // Removed in favor of the datefmt_set_timezone()
        'datefmt_set_timezone_id',

        // Removed in favor of the mcrypt_generic_deinit() function
        'mcrypt_generic_end',

        // Replaced by mcrypt_decrypt() with a MCRYPT_MODE_* constant.
        'mcrypt_ecb',
        'mcrypt_cbc',
        'mcrypt_cfb',
        'mcrypt_ofb',

        // Magic quotes no longer available
        'set_magic_quotes_runtime',
        'magic_quotes_runtime',

        // The ereg extension was removed
        'ereg_replace',
        'ereg',
        'eregi_replace',
        'eregi',
        'split',
        'spliti',
        'sql_regcase',

        // Support for PostScript Type1 fonts has been removed from the GD extension
        'imagepsbbox',
        'imagepsencodefont',
        'imagepsextendedfont',
        'imagepsfreefont',
        'imagepsloadfont',
        'imagepsslantfont',
        'imagepstext',

        // The mysql extension removed
        'mysql_affected_rows',
        'mysql_client_encoding',
        'mysql_close',
        'mysql_connect',
        'mysql_create_db',
        'mysql_data_seek',
        'mysql_db_name',
        'mysql_db_query',
        'mysql_drop_db',
        'mysql_errno',
        'mysql_error',
        'mysql_escape_string',
        'mysql_fetch_array',
        'mysql_fetch_assoc',
        'mysql_fetch_field',
        'mysql_fetch_lengths',
        'mysql_fetch_object',
        'mysql_fetch_row',
        'mysql_field_flags',
        'mysql_field_len',
        'mysql_field_name',
        'mysql_field_seek',
        'mysql_field_table',
        'mysql_field_type',
        'mysql_free_result',
        'mysql_get_client_info',
        'mysql_get_host_info',
        'mysql_get_proto_info',
        'mysql_get_server_info',
        'mysql_info',
        'mysql_insert_id',
        'mysql_list_dbs',
        'mysql_list_fields',
        'mysql_list_processes',
        'mysql_list_tables',
        'mysql_num_fields',
        'mysql_num_rows',
        'mysql_pconnect',
        'mysql_ping',
        'mysql_query',
        'mysql_real_escape_string',
        'mysql_result',
        'mysql_select_db',
        'mysql_set_charset',
        'mysql_stat',
        'mysql_tablename',
        'mysql_thread_id',
        'mysql_unbuffered_query',

        // The mssql extension was removed
        'mssql_bind',
        'mssql_close',
        'mssql_connect',
        'mssql_data_seek',
        'mssql_execute',
        'mssql_fetch_array',
        'mssql_fetch_assoc',
        'mssql_fetch_batch',
        'mssql_fetch_field',
        'mssql_fetch_object',
        'mssql_fetch_row',
        'mssql_field_length',
        'mssql_field_name',
        'mssql_field_seek',
        'mssql_field_type',
        'mssql_free_result',
        'mssql_free_statement',
        'mssql_get_last_message',
        'mssql_guid_string',
        'mssql_init',
        'mssql_min_error_severity',
        'mssql_min_message_severity',
        'mssql_next_result',
        'mssql_num_fields',
        'mssql_num_rows',
        'mssql_pconnect',
        'mssql_query',
        'mssql_result',
        'mssql_rows_affected',
        'mssql_select_db',
    );

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
        $this->removedFunctionNames = array_flip($this->removedFunctionNames);
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, $this->removedFunctionNames)) {
            return;
        }

        /** @var Node\Expr\FuncCall $node */
        $this->addContextMessage(
            sprintf('Removed function "%s" called', $node->name->toString()),
            $node
        );
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class ReservedClassNameVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;
    const RESERVED_NAME_MESSAGE = 'Reserved name "%s" used %s ';
    const FUTURE_RESERVED_NAME_MESSAGE = <<<'MSG'
Name "%s" that is reserved for future use (does not cause an error in PHP 7) used %s
MSG;

    /**
     * @var string[]
     */
    protected $reservedClassNames = array(
        'bool',
        'int',
        'float',
        'string',
        'null',
        'false',
        'true',
    );

    /**
     * @var string[]
     */
    protected $futureReservedClassNames = array(
        'resource',
        'object',
        'mixed',
        'numeric',
    );

    /**
     * @var array
     */
    protected $reservedNamesToMessagesMap = array();

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
        $this->reservedNamesToMessagesMap = array_merge(
            array_fill_keys(
                $this->reservedClassNames,
                static::RESERVED_NAME_MESSAGE
            ),
            array_fill_keys(
                $this->futureReservedClassNames,
                static::FUTURE_RESERVED_NAME_MESSAGE
            )
        );
    }

    public function enterNode(Node $node)
    {
        $checkedName = '';
        $usagePatternName = null;

        if ($node instanceof Node\Stmt\ClassLike) {
            $checkedName = $node->name;
            $usagePatternName = 'as a class, interface or trait name';
        } elseif ($this->functionAnalyzer->isFunctionCallByStaticName($node, 'class_alias')) {
            /** @var Node\Expr\FuncCall $node */
            $secondArgument = isset($node->args[1]) ? $node->args[1] : null;

            if (!$secondArgument || !$secondArgument->value instanceof Node\Scalar\String_) {
                return;
            }

            $checkedName = $secondArgument->value->value;
            $usagePatternName = 'as a class alias';
        } elseif ($node instanceof Node\Stmt\UseUse) {
            $checkedName = $node->alias;
            $usagePatternName = 'as a use statement alias';
        }

        $checkedName = strtolower($checkedName);
        if ($checkedName && isset($this->reservedNamesToMessagesMap[$checkedName])) {
            $this->addContextMessage(
                sprintf($this->reservedNamesToMessagesMap[$checkedName], $checkedName, $usagePatternName),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use Sstalle\php7cc\CompatibilityViolation\Message;

class Resolver implements ResolverInterface
{
    /**
     * @var VisitorInterface[]
     */
    protected $visitors = array();

    /**
     * @var int
     */
    protected $level;

    /**
     * @param array $visitors
     * @param int   $level
     */
    public function __construct($visitors = array(), $level = Message::LEVEL_INFO)
    {
        foreach ($visitors as $visitor) {
            $this->addVisitor($visitor);
        }
        $this->level = $level;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve()
    {
        $level = $this->level;

        return array_values(array_filter($this->visitors, function (VisitorInterface $visitor) use ($level) {
            return $visitor->getLevel() >= $level;
        }));
    }

    /**
     * @param int $level
     */
    public function setLevel($level)
    {
        $this->level = $level;
    }

    /**
     * @param VisitorInterface $visitor
     */
    protected function addVisitor(VisitorInterface $visitor)
    {
        $this->visitors[] = $visitor;
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

interface ResolverInterface
{
    /**
     * @return VisitorInterface[]
     */
    public function resolve();

    /**
     * @param int $level
     */
    public function setLevel($level);
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class SessionSetSaveHandlerVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($this->functionAnalyzer->isFunctionCallByStaticName($node, array('session_set_save_handler' => true))) {
            $this->addContextMessage(
                'Check that callbacks that are passed to "session_set_save_handler" '
                . 'and return false or -1 (if any) operate correctly',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;
use Sstalle\php7cc\NodeAnalyzer\FunctionAnalyzer;

class SetcookieEmptyNameVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_ERROR;

    /**
     * @var array
     */
    protected static $setcookieFamilyFunctions = array(
        'setcookie' => true,
        'setrawcookie' => true,
    );

    /**
     * @var FunctionAnalyzer
     */
    protected $functionAnalyzer;

    /**
     * @param FunctionAnalyzer $functionAnalyzer
     */
    public function __construct(FunctionAnalyzer $functionAnalyzer)
    {
        $this->functionAnalyzer = $functionAnalyzer;
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!$this->functionAnalyzer->isFunctionCallByStaticName($node, self::$setcookieFamilyFunctions)) {
            return;
        }

        /** @var Node\Expr\FuncCall $node */
        $cookieNameArgumentValue = isset($node->args[0]) ? $node->args[0]->value : null;
        $isEmptyString = $cookieNameArgumentValue && $cookieNameArgumentValue instanceof Node\Scalar\String_
            && $cookieNameArgumentValue->value === '';
        $isEmptyConstant = $cookieNameArgumentValue && $cookieNameArgumentValue instanceof Node\Expr\ConstFetch
            && in_array(strtolower($cookieNameArgumentValue->name->toString()), array('null', 'false'), true);

        if ($isEmptyConstant || $isEmptyString) {
            $this->addContextMessage(
                sprintf('Function "%s" called with an empty cookie name', $node->name->toString()),
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\NodeVisitor;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;
use Sstalle\php7cc\Token\TokenCollection;

interface VisitorInterface extends NodeVisitor
{
    /**
     * @param ContextInterface $context
     */
    public function initializeContext(ContextInterface $context);

    /**
     * @param TokenCollection $tokenCollection
     */
    public function setTokenCollection(TokenCollection $tokenCollection);

    /**
     * @return int
     */
    public function getLevel();
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class YieldExpressionVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * @var string[]
     */
    protected $lowerPrecedenceExpressionClasses = array(
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd',
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr',
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor',
    );

    public function __construct()
    {
        $this->lowerPrecedenceExpressionClasses = array_flip($this->lowerPrecedenceExpressionClasses);
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if (!($node instanceof Node\Expr\Yield_ && $node->value && $node->value instanceof Node\Expr)) {
            return;
        }

        $valueClass = get_class($node->value);
        if (isset($this->lowerPrecedenceExpressionClasses[$valueClass])) {
            $this->addContextMessage(
                'Yielding expression with precedence lower than "yield"',
                $node
            );
        }
    }
}
<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\CompatibilityViolation\Message;

class YieldInExpressionContextVisitor extends AbstractVisitor
{
    const LEVEL = Message::LEVEL_WARNING;

    /**
     * @var \SplStack
     */
    protected $expressionStack;

    /**
     * {@inheritdoc}
     */
    public function beforeTraverse(array $nodes)
    {
        $this->expressionStack = new \SplStack();
    }

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Node\Expr\Yield_ && !$this->expressionStack->isEmpty()) {
            $startTokenPosition = $node->getAttribute('startTokenPos');
            $endTokenPosition = $node->getAttribute('endTokenPos');

            if (!$this->tokenCollection->isTokenPrecededBy($startTokenPosition, '(')
                || !$this->tokenCollection->isTokenFollowedBy($endTokenPosition, ')')
            ) {
                $this->addContextMessage(
                    '"yield" usage in expression context',
                    $this->expressionStack->top()
                );
            }
        } elseif ($node instanceof Node\Expr && !$this->isIgnoredExpressionType($node)) {
            $this->expressionStack->push($node);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function leaveNode(Node $node)
    {
        if (!$this->expressionStack->isEmpty() && $node === $this->expressionStack->top()) {
            $this->expressionStack->pop();
        }
    }

    /**
     * @param Node\Expr $expr
     *
     * @return bool
     */
    private function isIgnoredExpressionType(Node\Expr $expr)
    {
        /**
         * We don't care about assignments, because:
         * 1. If the assigned expression ($expr->expr) is a yield expression, then:
         *  a. It had been added before moving to 7.x and is parenthesized (assigning
         *     yield expressions without parenthesis causes a syntax error in 5.x).
         *     And parenthesized yields have the same meaning in both 5.x and 7.x
         *  b. It had been added after moving to 7.x and should be left alone
         * 2. If the assigned expression is not a yield expression, then:
         *  a. It has a nested yield expression which will get caught by the enterNode method
         *  b. It has no nested yield expression and is not relevant to this visitor.
         */
        $isAssignment = $expr instanceof Node\Expr\Assign || $expr instanceof Node\Expr\AssignOp;
        $isClosureDeclaration = $expr instanceof Node\Expr\Closure;
        $isAnonymousClassInstantiation = $expr instanceof Node\Expr\New_
            && $expr->class instanceof Node\Stmt\Class_
            && !$expr->class->name;

        return $isAssignment
            || $isClosureDeclaration
            || $isAnonymousClassInstantiation
        ;
    }
}
<?php

namespace Sstalle\php7cc;

use PhpParser\NodeTraverserInterface;
use Sstalle\php7cc\NodeVisitor\ResolverInterface;

class PathCheckExecutor
{
    /**
     * @var PathTraversableFactory
     */
    protected $pathTraversableFactory;

    /**
     * @var PathChecker
     */
    protected $pathChecker;

    /**
     * @var NodeTraverserInterface
     */
    protected $traverser;

    /**
     * @var ResolverInterface
     */
    protected $visitorResolver;

    /**
     * @param PathTraversableFactory $pathTraversableFactory
     * @param PathChecker            $pathChecker
     * @param NodeTraverserInterface $traverser
     * @param ResolverInterface      $visitorResolver
     */
    public function __construct(
        PathTraversableFactory $pathTraversableFactory,
        PathChecker $pathChecker,
        NodeTraverserInterface $traverser,
        ResolverInterface $visitorResolver
    ) {
        $this->pathTraversableFactory = $pathTraversableFactory;
        $this->pathChecker = $pathChecker;
        $this->traverser = $traverser;
        $this->visitorResolver = $visitorResolver;
    }

    /**
     * @param PathCheckSettings $checkSettings
     */
    public function check(PathCheckSettings $checkSettings)
    {
        $this->visitorResolver->setLevel($checkSettings->getMessageLevel());
        foreach ($this->visitorResolver->resolve() as $visitor) {
            $this->traverser->addVisitor($visitor);
        }

        $this->pathChecker->check(
            $this->pathTraversableFactory->createTraversable(
                $checkSettings->getCheckedPaths(),
                $checkSettings->getCheckedFileExtensions(),
                $checkSettings->getExcludedPaths()
            ),
            $checkSettings->getUseRelativePaths()
        );
    }
}
<?php

namespace Sstalle\php7cc;

use Sstalle\php7cc\CompatibilityViolation\Message;

class PathCheckSettings
{
    /**
     * @var array
     */
    protected $checkedPaths;

    /**
     * @var array
     */
    protected $checkedFileExtensions;

    /**
     * @var array
     */
    protected $excludedPaths = array();

    /**
     * @var int
     */
    protected $messageLevel = Message::LEVEL_INFO;

    /**
     * @var bool
     */
    protected $useRelativePaths = false;

    /**
     * @param array $checkedPaths
     * @param array $checkedFileExtensions
     */
    public function __construct(array $checkedPaths, array $checkedFileExtensions)
    {
        if (!$checkedPaths) {
            throw new \InvalidArgumentException('At least 1 path to check must be specified');
        }

        if (!$checkedFileExtensions) {
            throw new \InvalidArgumentException('At least 1 file extension to check must be specified');
        }

        $this->checkedPaths = $checkedPaths;
        $this->checkedFileExtensions = $checkedFileExtensions;
    }

    /**
     * @return array
     */
    public function getCheckedPaths()
    {
        return $this->checkedPaths;
    }

    /**
     * @return array
     */
    public function getCheckedFileExtensions()
    {
        return $this->checkedFileExtensions;
    }

    /**
     * @return array
     */
    public function getExcludedPaths()
    {
        return $this->excludedPaths;
    }

    /**
     * @param array $excludedPaths
     */
    public function setExcludedPaths($excludedPaths)
    {
        $this->excludedPaths = $excludedPaths;
    }

    /**
     * @return int
     */
    public function getMessageLevel()
    {
        return $this->messageLevel;
    }

    /**
     * @param int $messageLevel
     */
    public function setMessageLevel($messageLevel)
    {
        $this->messageLevel = $messageLevel;
    }

    /**
     * @return bool
     */
    public function getUseRelativePaths()
    {
        return $this->useRelativePaths;
    }

    /**
     * @param bool $useRelativePaths
     */
    public function setUseRelativePaths($useRelativePaths)
    {
        $this->useRelativePaths = $useRelativePaths;
    }
}
<?php

namespace Sstalle\php7cc;

use Sstalle\php7cc\CompatibilityViolation\CheckMetadata;
use Sstalle\php7cc\CompatibilityViolation\FileContext;
use Symfony\Component\Finder\SplFileInfo;

class PathChecker
{
    /**
     * @var ContextChecker
     */
    protected $contextChecker;

    /**
     * @var ResultPrinterInterface
     */
    protected $resultPrinter;

    /**
     * @param ContextChecker         $fileChecker
     * @param ResultPrinterInterface $resultPrinter
     */
    public function __construct(ContextChecker $fileChecker, ResultPrinterInterface $resultPrinter)
    {
        $this->contextChecker = $fileChecker;
        $this->resultPrinter = $resultPrinter;
    }

    /**
     * @param \Traversable $traversablePaths
     * @param bool         $useRelativePaths
     */
    public function check(\Traversable $traversablePaths, $useRelativePaths)
    {
        $checkMetadata = new CheckMetadata();

        /** @var SplFileInfo $fileInfo */
        foreach ($traversablePaths as $fileInfo) {
            $this->checkFile($checkMetadata, $fileInfo, $useRelativePaths);
        }

        $checkMetadata->endCheck();

        $this->resultPrinter->printMetadata($checkMetadata);
    }

    /**
     * @param CheckMetadata $checkMetadata
     * @param SplFileInfo   $fileInfo
     * @param bool          $useRelativePaths
     */
    protected function checkFile(CheckMetadata $checkMetadata, SplFileInfo $fileInfo, $useRelativePaths)
    {
        $context = new FileContext($fileInfo, $useRelativePaths);
        $this->contextChecker->checkContext($context);

        if ($context->hasMessagesOrErrors()) {
            $this->resultPrinter->printContext($context);
        }

        $checkMetadata->incrementCheckedFileCount();
    }
}
<?php

namespace Sstalle\php7cc;

use Sstalle\php7cc\Iterator\ExcludedPathFilteringRecursiveIterator;
use Sstalle\php7cc\Iterator\ExtensionFilteringRecursiveIterator;
use Sstalle\php7cc\Iterator\FileDirectoryListRecursiveIterator;

class PathTraversableFactory
{
    /**
     * @var ExcludedPathCanonicalizer
     */
    protected $excludedPathCanonicalizer;

    /**
     * @param ExcludedPathCanonicalizer $excludedPathCanonicalizer
     */
    public function __construct(ExcludedPathCanonicalizer $excludedPathCanonicalizer)
    {
        $this->excludedPathCanonicalizer = $excludedPathCanonicalizer;
    }

    /**
     * @param string[] $paths             Files and/or directories to check
     * @param string[] $checkedExtensions Only files having these extensions will be checked
     * @param string[] $excludedPaths
     *
     * @return \Traversable
     */
    public function createTraversable(array $paths, array $checkedExtensions, array $excludedPaths)
    {
        $directlyPassedFiles = array();
        $excludedPaths = $this->excludedPathCanonicalizer->canonicalize($paths, $excludedPaths);
        foreach ($paths as $path) {
            if (is_dir($path) && !$checkedExtensions) {
                throw new \DomainException('At least 1 extension must be specified to check a directory');
            } elseif (is_file($path)) {
                $directlyPassedFiles[] = realpath($path);
            }
        }

        $fileDirectoryIterator = new FileDirectoryListRecursiveIterator($paths);
        $extensionFilteringIterator = new ExtensionFilteringRecursiveIterator(
            $fileDirectoryIterator,
            $checkedExtensions,
            $directlyPassedFiles
        );
        $excludedPathFilteringIterator = new ExcludedPathFilteringRecursiveIterator(
            $extensionFilteringIterator,
            $excludedPaths
        );

        return new \RecursiveIteratorIterator(
            $excludedPathFilteringIterator,
            \RecursiveIteratorIterator::LEAVES_ONLY
        );
    }
}
<?php

namespace Sstalle\php7cc;

use Sstalle\php7cc\CompatibilityViolation\CheckMetadata;
use Sstalle\php7cc\CompatibilityViolation\ContextInterface;

interface ResultPrinterInterface
{
    const PLAIN_FORMAT = 'plain';
    const JSON_FORMAT = 'json';

    /**
     * @param ContextInterface $context
     */
    public function printContext(ContextInterface $context);

    /**
     * @param CheckMetadata $metadata
     */
    public function printMetadata(CheckMetadata $metadata);
}
<?php

namespace Sstalle\php7cc\Token;

class TokenCollection
{
    const TOKEN_ORIGINAL_VALUE_OFFSET = 1;

    /**
     * @var array
     */
    protected $tokens = array();

    /**
     * @param array $rawTokens Tokens as returned by token_get_all
     */
    public function __construct(array $rawTokens)
    {
        foreach ($rawTokens as $i => $rawToken) {
            if (is_array($rawToken) && count($rawToken) < 3) {
                throw new \InvalidArgumentException(sprintf('Array token at index %d has less than 3 elements', $i));
            }
        }

        $this->tokens = $rawTokens;
    }

    /**
     * @param int $tokenPosition
     *
     * @return string
     */
    public function getTokenStringValueAt($tokenPosition)
    {
        if (!isset($this->tokens[$tokenPosition])) {
            throw new \OutOfBoundsException(sprintf('Token at offset %d does not exist', $tokenPosition));
        }

        $originalToken = $this->tokens[$tokenPosition];

        return is_string($originalToken) ? $originalToken : $originalToken[static::TOKEN_ORIGINAL_VALUE_OFFSET];
    }

    /**
     * @param int    $tokenPosition
     * @param string $stringValue
     *
     * @return bool
     */
    public function isTokenEqualTo($tokenPosition, $stringValue)
    {
        return $this->getTokenStringValueAt($tokenPosition) === $stringValue;
    }

    /**
     * @param int    $tokenPosition
     * @param string $stringValue
     *
     * @return bool
     */
    public function isTokenPrecededBy($tokenPosition, $stringValue)
    {
        return $this->isNextNonWhitespaceTokenEqualTo($tokenPosition, $stringValue, false);
    }

    /**
     * @param int    $tokenPosition
     * @param string $stringValue
     *
     * @return bool
     */
    public function isTokenFollowedBy($tokenPosition, $stringValue)
    {
        return $this->isNextNonWhitespaceTokenEqualTo($tokenPosition, $stringValue, true);
    }

    /**
     * @param int    $tokenPosition
     * @param string $stringValue
     *
     * @return bool
     */
    public function isTokenEqualToOrPrecededBy($tokenPosition, $stringValue)
    {
        return $this->isTokenEqualTo($tokenPosition, $stringValue)
            || $this->isTokenPrecededBy($tokenPosition, $stringValue);
    }

    /**
     * @param int $tokenPosition
     * @param int $stringValue
     *
     * @return bool
     */
    public function isTokenEqualToOrFollowedBy($tokenPosition, $stringValue)
    {
        return $this->isTokenEqualTo($tokenPosition, $stringValue)
            || $this->isTokenFollowedBy($tokenPosition, $stringValue);
    }

    /**
     * Whitespace tokens are ignored when $stringValue is not whitespace.
     *
     * @param int    $tokenPosition
     * @param string $stringValue
     * @param bool   $scanForward   Scan forward if true, otherwise backward
     *
     * @return bool
     */
    protected function isNextNonWhitespaceTokenEqualTo($tokenPosition, $stringValue, $scanForward)
    {
        $ignoreWhitespace = !ctype_space($stringValue);

        while (isset($this->tokens[$scanForward ? ++$tokenPosition : --$tokenPosition])) {
            $currentTokenString = $this->getTokenStringValueAt($tokenPosition);
            if ($ignoreWhitespace && ctype_space($currentTokenString)) {
                continue;
            }

            return $stringValue === $currentTokenString;
        }

        return false;
    }
}
#!/usr/bin/env php
<?php

require __DIR__ . '/php7cc.php';<?php

$autoloadFiles = array(
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
);

$loader = null;
foreach ($autoloadFiles as $autoloadFile) {
    if (file_exists($autoloadFile)) {
        $loader = require $autoloadFile;
        break;
    }
}

if (!$loader) {
    exit('Autoloader not found. Try installing dependencies using composer install.');
}

$app = new \Sstalle\php7cc\Infrastructure\Application();
$app->run();
The MIT License (MIT)

Copyright (c) 2015 sstalle

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

# PHP 7 Compatibility Checker(php7cc)
#### Introduction
php7cc is a command line tool designed to make migration from PHP 5.3-5.6 to PHP 7 easier.
It searches for potentially troublesome statements in existing code and generates reports containing
file names, line numbers and short problem descriptions. It does not automatically fix
code to work with the new PHP version.

#### What kind of problems does it detect?
There are 2 types of issues reported by php7cc:

1. **Errors** that will definitely cause some kind of trouble (a fatal, a syntax error, a notice, etc.) on PHP 7. These are highlighted in red.
2. **Warnings** that may or may not lead to logical errors. For example, statements that are legal in both PHP 5 and PHP 7, but change their behaviour between versions fall into this category. Warnings are highlighted in yellow.    

A list of statements that may cause errors or warnings to be reported can be found in the [php-src repository](https://github.com/php/php-src/blob/PHP-7.0/UPGRADING).

***Although php7cc tries to detect as much problems as accurately as possible, sometimes 100% reliable detection
is very hard to achieve. That's why you should also run a comprehensive test suite for the code
you are going to migrate.***

# Prerequisites
To run php7cc, you need php installed, minimum required version is 5.3.3. PHP 7 is supported,
 but files with syntax errors (for example, invalid numeric literals
 or invalid UTF-8 codepoint escape sequences) can't be processed. You will only get the
 warning message about the first syntax error for such files.
 
You may also need [composer](https://getcomposer.org/) to install php7cc.

# Installation
#### Phar package
You can download a phar package for any stable version from the Github
 [releases](https://github.com/sstalle/php7cc/releases) page.

#### Composer (globally)
Make sure you have composer installed. Then execute the following command:
```bash
composer global require sstalle/php7cc
```
It is also recommended to add global Composer binaries directory to your ```PATH``` environment
variable. The location of this directory depends on the operating system you use
(see [Composer documentation](https://getcomposer.org/doc/03-cli.md#composer-home)
if you want to know more). The following command should work for some *nix systems:
```bash
export PATH="$PATH:$HOME/.config/composer/vendor/bin"
```
This makes it possible to run php7cc by entering just the executable name.

#### Composer (locally, per project)
Make sure you have composer installed. Then execute the following command from your project
directory:
```bash
composer require sstalle/php7cc --dev
```

#### Docker image
A docker image is available on [Docker Hub](https://hub.docker.com/r/ypereirareis/php7cc/)
 (contributed and maintained by [ypereirareis](https://github.com/ypereirareis)).

# Usage
Examples in this section assume that you have installed php7cc globally using composer
and that you have added it's vendor binaries directory to your ```PATH```. If this is not
the case, just substitute ```php7cc``` with the correct path to the binary of phar package.
For local per project installation the executable will be located at ```<your_project_path>/vendor/bin/php7cc```.

#### Getting help
To see the full list of available options, run:
```bash
php7cc --help
```

#### Checking a single file or directory
To check a file or a directory, pass its name as the first argument. Directories are checked
recursively.
 
So, to check a file you could run:
```bash
php7cc /path/to/my/file.php
```
To check a directory:
```bash
php7cc /path/to/my/directory/
```

#### Specifying file extensions to check
When checking a directory, you can also specify a comma-separated list of file extensions that
should be checked. By default, only .php files are processed.
 
For example, if you want to check .php, .inc and .lib files, you could run:
```bash
php7cc --extensions=php,inc,lib /path/to/my/directory/
```

#### Excluding file or directories
You can specify a list of absolute or relative paths to exclude from checking.
Relative paths are relative to the checked directories.

So, if you want to exclude vendor and test directories, you could run:
```bash
php7cc --except=vendor --except=/path/to/my/directory/test /path/to/my/directory/
```
In this example, directories ```/path/to/my/directory/vendor```,  ```/path/to/my/directory/test``` and their contents will not be checked.

#### Specifying minimum issue level
If you set a minimum issue level, only issues having that or higher severity level will be
reported by `php7cc`. There are 3 issue levels: "info", "warning" and "error". "info" is
reserved for future use and is the same as "warning".

Example usage:
```bash
php7cc --level=error /path/to/my/directory/
```
Only errors, but not warnings will be shown in this case.

#### Specifying output format
There are two output format available: `plain` and `json`.

`output-format` command-line option, `o` in short form, can be used in order to change the output format:

```bash
php7cc -o json /path/to/my/directory/ | json_pp
```

Would output:

```json
{
   "summary" : {
      "elapsedTime" : 0.0060338973999023,
      "checkedFiles" : 3
   },
   "files" : [
      {
         "errors" : {},
         "name" : "/path/to/my/directory/myfile.php",
         "warnings" : [
            {
               "text" : "String containing number in hexadecimal notation",
               "line" : 13
            }
         ]
      },
      {
         "warnings" : [
            {
               "line" : 6,
               "text" : "Reserved name \"string\" used as a class, interface or trait name "
            }
         ],
         "name" : "/path/to/my/directory/myfile.php",
         "errors" : {}
      }
   ]
}

```

# Troubleshooting
#### Maximum function nesting level of 100/250/N reached, aborting!
You should increase maximum function nesting level in your PHP or Xdebug config file like this:
```cfg
xdebug.max_nesting_level = 1000
```

#### Allowed memory size of N bytes exhausted 
You should increase amount of memory available to CLI PHP scripts or disable PHP memory limit.
The latter can be done by setting the `memory_limit` PHP option to -1. This option can be set by editing
`php.ini` or by passing a command-line argument to PHP executable like this:
```bash
php -d memory_limit=-1 php7cc.php /path/to/my/directory
```

# Other useful links
#### Contributing
Please read the [contributing guidelines](CONTRIBUTING.md).
#### Credits
[The list of contributors](https://github.com/sstalle/php7cc/graphs/contributors) is available on the corresponding
 Github page.
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Random Number Generator
 *
 * PHP versions 4 and 5
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include('Crypt/Random.php');
 *
 *    echo crypt_random();
 * ?>
 * </code>
 *
 * LICENSE: 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.
 *
 * @category   Crypt
 * @package    Crypt_Random
 * @author     Jim Wigginton <terrafrost@php.net>
 * @copyright  MMVII Jim Wigginton
 * @license    http://www.opensource.org/licenses/mit-license.html  MIT License
 * @version    $Id: Random.php,v 1.9 2010/04/24 06:40:48 terrafrost Exp $
 * @link       http://phpseclib.sourceforge.net
 */

/**
 * Generate a random value.
 *
 * On 32-bit machines, the largest distance that can exist between $min and $max is 2**31.
 * If $min and $max are farther apart than that then the last ($max - range) numbers.
 *
 * Depending on how this is being used, it may be worth while to write a replacement.  For example,
 * a PHP-based web app that stores its data in an SQL database can collect more entropy than this function
 * can.
 *
 * @param optional Integer $min
 * @param optional Integer $max
 * @return Integer
 * @access public
 */
function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
    if ($min == $max) {
        return $min;
    }

    if (function_exists('openssl_random_pseudo_bytes')) {
        // openssl_random_pseudo_bytes() is slow on windows per the following:
        // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
        if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
            extract(unpack('Nrandom', openssl_random_pseudo_bytes(4)));

            return abs($random) % ($max - $min) + $min; 
        }
    }

    // see http://en.wikipedia.org/wiki//dev/random
    static $urandom = true;
    if ($urandom === true) {
        // Warning's will be output unles the error suppression operator is used.  Errors such as
        // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
        $urandom = @fopen('/dev/urandom', 'rb');
    }
    if (!is_bool($urandom)) {
        extract(unpack('Nrandom', fread($urandom, 4)));

        // say $min = 0 and $max = 3.  if we didn't do abs() then we could have stuff like this:
        // -4 % 3 + 0 = -1, even though -1 < $min
        return abs($random) % ($max - $min) + $min;
    }

    /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
       Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:

       http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

       The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:

       http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
    if (version_compare(PHP_VERSION, '5.2.5', '<=')) { 
        static $seeded;
        if (!isset($seeded)) {
            $seeded = true;
            mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
        }
    }

    static $crypto;

    // The CSPRNG's Yarrow and Fortuna periodically reseed.  This function can be reseeded by hitting F5
    // in the browser and reloading the page.

    if (!isset($crypto)) {
        $key = $iv = '';
        for ($i = 0; $i < 8; $i++) {
            $key.= pack('n', mt_rand(0, 0xFFFF));
            $iv .= pack('n', mt_rand(0, 0xFFFF));
        }
        switch (true) {
            case class_exists('Crypt_AES'):
                $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR);
                break;
            case class_exists('Crypt_TripleDES'):
                $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
                break;
            case class_exists('Crypt_DES'):
                $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR);
                break;
            case class_exists('Crypt_RC4'):
                $crypto = new Crypt_RC4();
                break;
            default:
                extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF)))));
                return abs($random) % ($max - $min) + $min;
        }
        $crypto->setKey($key);
        $crypto->setIV($iv);
        $crypto->enableContinuousBuffer();
    }

    extract(unpack('Nrandom', $crypto->encrypt("\0\0\0\0")));
    return abs($random) % ($max - $min) + $min;
}
<?php

/**
 * The flag used to control the use of the fourth argument for json_decode().
 *
 * @var boolean
 */
define('JSON_DECODE_FOURTH_ARG', version_compare(phpversion(), '5.4.0', '>='));<?php

/**
 * The manifest schema file.
 *
 * @var string
 */
define('PHAR_UPDATE_MANIFEST_SCHEMA', realpath(
    __DIR__ . '/../../res/schema.json'
));BuClB+WvvY   GBMB