<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpToZephir\Converter\SimplePrinter;

class ParamPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pParam';
    }

    /**
     * @param Node\Param $node
     *
     * @return string
     */
    public function convert(Node\Param $node)
    {
        if ($node->byRef) {
            $this->logger->logIncompatibility(
                'reference',
                'Reference not supported',
                $node,
                $this->dispatcher->getMetadata()->getClass()
            );
        }

        return ($node->type ? (is_string($node->type) ? $node->type : $this->dispatcher->p($node->type)).' ' : '')
             .($node->variadic ? '... ' : '')
             .''.$node->name
             .($node->default ? ' = '.$this->dispatcher->p($node->default) : '').'';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class ArrayPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_Array';
    }

    /**
     * @param Expr\Array_ $node
     * @param bool        $returnAsArray
     *
     * @return string|array
     */
    public function convert(Expr\Array_ $node, $returnAsArray = false)
    {
        $collected = $this->assignManipulator->collectAssignInCondition($node->items);
        $node->items = $this->assignManipulator->transformAssignInConditionTest($node->items);

        $collected->setExpr('['.$this->dispatcher->pCommaSeparated($node->items).']');

        if ($returnAsArray === true) {
            return $collected;
        } else {
            return $collected->getCollected().$collected->getExpr();
        }
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpParser\Node\Expr;
use PhpToZephir\Converter\SimplePrinter;

class StaticCallPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_StaticCall';
    }

    /**
     * @param Expr\StaticCall $node
     *
     * @return string
     */
    public function convert(Expr\StaticCall $node)
    {
        return (($node->class instanceof Expr\Variable) ? '{'.$this->dispatcher->p($node->class).'}' : $this->dispatcher->p($node->class)).'::'
             .($node->name instanceof Expr
                ? ($node->name instanceof Expr\Variable
                   || $node->name instanceof Expr\ArrayDimFetch
                   ? $this->dispatcher->p($node->name)
                   : '{'.$this->dispatcher->p($node->name).'}')
                : $node->name)
             .'('.$this->dispatcher->pCommaSeparated($node->args).')';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpParser\Node\Expr;
use PhpToZephir\Converter\SimplePrinter;

class PropertyFetchPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_PropertyFetch';
    }

    /**
     * @param Expr\PropertyFetch $node
     *
     * @return string
     */
    public function convert(Expr\PropertyFetch $node)
    {
        return $this->dispatcher->pVarOrNewExpr($node->var).'->'.$this->dispatcher->pObjectProperty($node->name);
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Stmt;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class WhilePrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_While';
    }

    /**
     * @param Stmt\While_ $node
     *
     * @return string
     */
    public function convert(Stmt\While_ $node)
    {
        $collected = $this->assignManipulator->collectAssignInCondition($node->cond);
        $node->cond = $this->assignManipulator->transformAssignInConditionTest($node->cond);

        return $collected->getCollected().'while ('.$this->dispatcher->p($node->cond).') {'
             .$this->dispatcher->pStmts($node->stmts)."\n".$collected->getCollected().'}';
    }
}


<?php

namespace PhpToZephir;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar\MagicConst;

class ClassCollector
{
    /**
     * @var NodeFetcher
     */
    private $nodeFetcher = null;
    /**
     * @var ReservedWordReplacer
     */
    private $reservedWordReplacer = null;
    /**
     * @var array
     */
    private $collected = array();

    /**
     * @param NodeFetcher          $nodeFetcher
     * @param ReservedWordReplacer $reservedWordReplacer
     */
    public function __construct(NodeFetcher $nodeFetcher, ReservedWordReplacer $reservedWordReplacer)
    {
        $this->nodeFetcher = $nodeFetcher;
        $this->reservedWordReplacer = $reservedWordReplacer;
    }

    /**
     * @param Node[]  $stmts
     * @param unknown $fileName
     *
     * @return string
     */
    public function collect(array $stmts, $fileName)
    {
        $namespace = null;
        $class = null;

        foreach ($this->nodeFetcher->foreachNodes($stmts) as $nodeData) {
            $node = $nodeData['node'];
            if ($node instanceof Stmt\Goto_) {
                throw new \Exception('Goto not supported in '.$fileName.' on line '.$node->getLine());
            } elseif ($node instanceof Stmt\InlineHTML) {
                throw new \Exception('InlineHTML not supported in '.$fileName.' on line '.$node->getLine());
            } elseif ($node instanceof Stmt\HaltCompiler) {
                throw new \Exception('HaltCompiler not supported in '.$fileName.' on line '.$node->getLine());
            } elseif ($node instanceof MagicConst\Trait_) {
                throw new \Exception('MagicConst\Trait_ not supported in '.$fileName.' on line '.$node->getLine());
            } elseif ($node instanceof Stmt\Trait_) {
                throw new \Exception('Trait not supported in '.$fileName.' on line '.$node->getLine());
            } elseif ($node instanceof Stmt\Namespace_ && !empty($node->name)) {
                $namespace = implode('\\', $node->name->parts);
            } elseif ($node instanceof Stmt\Interface_ || $node instanceof Stmt\Class_) {
                if ($class !== null) {
                    throw new \Exception('Multiple class find in '.$fileName);
                }
                $class = $namespace.'\\'.$this->reservedWordReplacer->replace($node->name);
            }
        }

        if ($namespace === null) {
            throw new \Exception('Namespace not found in '.$fileName);
        }

        if ($class === null) {
            throw new \Exception('No class found in '.$fileName);
        }

        $this->collected[$class] = $stmts;

        return $class;
    }

    /**
     * @return array
     */
    public function getCollected()
    {
        return $this->collected;
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpToZephir\Converter\SimplePrinter;

class ImplodePrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pImplode';
    }

    /**
     * @param array  $nodes
     * @param string $glue
     *
     * @return string
     */
    public function convert(array $nodes, $glue = '')
    {
        $pNodes = array();
        foreach ($nodes as $node) {
            $pNodes[] = $this->dispatcher->p($node);
        }

        return implode($glue, $pNodes);
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpToZephir\Converter\SimplePrinter;

class ConstPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pConst';
    }

    /**
     * @param Node\Const_ $node
     *
     * @return string
     */
    public function convert(Node\Const_ $node)
    {
        if ($node->value instanceof Node\Expr\Array_) {
            $this->logger->logNode(
                'Array not supported in const, transform as empty string (see #188)',
                $node,
                $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
            );
            $node->value = new Node\Scalar\String_('');
        }

        return $node->name.' = '.$this->dispatcher->p($node->value);
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\SimplePrinter;

class VarOrNewExprPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pVarOrNewExpr';
    }

    /**
     * @param Node $node
     *
     * @return string
     */
    public function convert(Node $node)
    {
        if ($node instanceof Expr\New_) {
            return '('.$this->dispatcher->p($node).')';
        } else {
            return $this->dispatcher->p($node);
        }
    }
}


<?php

namespace PhpToZephir\Converter;

use PhpParser\Node\Stmt;
use PhpToZephir\ReservedWordReplacer;
use PhpToZephir\NodeFetcher;

class ClassInformation
{
    /**
     * @var ReservedWordReplacer
     */
    private $reservedWordReplacer = null;
    /**
     * @var NodeFetcher
     */
    private $nodeFetcher = null;

    /**
     * @param ReservedWordReplacer $reservedWordReplacer
     * @param NodeFetcher          $nodeFetcher
     */
    public function __construct(ReservedWordReplacer $reservedWordReplacer, NodeFetcher $nodeFetcher)
    {
        $this->reservedWordReplacer = $reservedWordReplacer;
        $this->nodeFetcher = $nodeFetcher;
    }

    /**
     * @param array $nodes
     *
     * @return \PhpToZephir\Converter\ClassMetadata
     */
    public function getClassesMetdata(array $nodes)
    {
        $classMetadata = new ClassMetadata();

        $classMetadata = $this->build($nodes, $classMetadata);

        $namespace = $classMetadata->getNamespace();

        if ($namespace === null) {
            throw new \Exception('Namespace not found');
        }

        return $classMetadata;
    }

    /**
     * @param array         $nodes
     * @param ClassMetadata $classMetadata
     *
     * @return ClassMetadata
     */
    public function build(array $nodes, ClassMetadata $classMetadata)
    {
        $class = null;
        foreach ($this->nodeFetcher->foreachNodes($nodes) as $nodeData) {
            $node = $nodeData['node'];
            if ($node instanceof Stmt\UseUse) {
                $classMetadata->addUse($node);
                $classMetadata->addClasses($this->reservedWordReplacer->replace(implode('\\', $node->name->parts)));
                if ($node->name->getLast() !== $node->alias) {
                    $classMetadata->addClassesAlias(
                        $node->alias,
                        $this->reservedWordReplacer->replace(implode('\\', $node->name->parts))
                    );
                }
            } elseif ($node instanceof Stmt\Namespace_) {
                $classMetadata->setNamespace(implode('\\', $node->name->parts));
            } elseif ($node instanceof Stmt\Interface_ || $node instanceof Stmt\Class_) {
                if ($class !== null) {
                    throw new \Exception('Multiple class find in '.$fileName);
                }
                $class = $this->reservedWordReplacer->replace($node->name);
                $classMetadata->setClass($class);

                if ($node instanceof Stmt\Interface_ ) {

                } elseif ($node instanceof Stmt\Class_ && $node->implements !== null) {
                    $implementsClean = array();
                    foreach ($node->implements as $implement) {
                        $implementsClean[] = $this->reservedWordReplacer->replace(implode('\\', $implement->parts));
                    }
                    $classMetadata->setImplements($implementsClean);
                }
            } elseif ($node instanceof Stmt\Interface_ || $node instanceof Stmt\Class_) {
                if ($class !== null) {
                    throw new \Exception('Multiple class find in '.$fileName);
                }
                $class = $this->reservedWordReplacer->replace($node->name);
                $classMetadata->setClass($class);
            }
        }

        return $classMetadata;
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpParser\Node\Stmt;
use PhpToZephir\Converter\SimplePrinter;

class PropertyPropertyPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_PropertyProperty';
    }

    /**
     * @param Stmt\PropertyProperty $node
     *
     * @return string
     */
    public function convert(Stmt\PropertyProperty $node)
    {
        return $node->name.(null !== $node->default ? ' = '.$this->dispatcher->p($node->default) : '');
    }
}


<?php

namespace PhpToZephir\Converter\Manipulator;

use PhpParser\Node\Scalar;
use PhpParser\Node\Expr;
use PhpToZephir\NodeFetcher;
use PhpToZephir\Converter\Dispatcher;
use PhpParser\Node\Expr\BinaryOp;

class AssignManipulator
{
    /**
     * @var NodeFetcher
     */
    private $nodeFetcher = null;
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;

    /**
     * @param NodeFetcher $nodeFetcher
     * @param Dispatcher  $dispatcher
     */
    public function __construct(NodeFetcher $nodeFetcher, Dispatcher $dispatcher)
    {
        $this->nodeFetcher = $nodeFetcher;
        $this->dispatcher = $dispatcher;
    }

    /**
     * @param mixed $node
     *
     * @return ArrayDto
     */
    public function collectAssignInCondition($node, ArrayDto $arrayDto = null)
    {
        if ($arrayDto === null) {
            $arrayDto = new ArrayDto();
        }

        foreach ($this->nodeFetcher->foreachNodes($node) as $stmtData) {
            $collected = $this->extract($stmtData['node'], $arrayDto, $stmtData['parentClass']);
        }

        return $this->extract($node, $arrayDto);
    }

    /**
     * @param mixed  $stmt
     * @param ArrayDto  $arrayDto
     * @param string $parentClass
     *
     * @return array
     */
    private function extract($stmt, ArrayDto $arrayDto, array $parentClass = array())
    {
        if ($stmt instanceof Expr\Assign) {
            if ($stmt->expr instanceof Expr\BinaryOp) {
                $stmt->expr = $stmt->expr->left;
                $arrayDto->addCollected($this->dispatcher->pExpr_Assign($stmt));
            } else {
                $arrayDto->addCollected($this->dispatcher->pExpr_Assign($stmt));
            }
        } elseif ($this->isVarModification($stmt) && !in_array("PhpParser\Node\Expr\Assign", $parentClass)) {
            $arrayDto->addCollected($this->dispatcher->p($stmt));
        } elseif ($this->isVarCreation($stmt) && !in_array("PhpParser\Node\Expr\ArrayItem", $parentClass) && !in_array("PhpParser\Node\Expr\Assign", $parentClass)) {
            $arrayDto->addCollected('let tmpArray'.md5(serialize($stmt->items)).' = '.$this->dispatcher->p($stmt));
        }

        return $arrayDto;
    }

    /**
     * @param mixed  $primaryNode
     * @param string $parentClass
     *
     * @return mixed
     */
    public function transformAssignInConditionTest($primaryNode, array $parentClass = array())
    {
        if ($primaryNode instanceof BinaryOp && ($primaryNode instanceof BinaryOp\Concat === false)) {
            // this is yoda ! invert condition
            if ($primaryNode->left instanceof Expr\ConstFetch ||
                $primaryNode->left instanceof Scalar\String_ ||
                $primaryNode->left instanceof Expr\Array_
            ) {
                $left = $primaryNode->left;
                $right = $primaryNode->right;
                $primaryNode->left = $right;
                $primaryNode->right = $left;
            }
        }

        if ($primaryNode instanceof Expr\Assign) {
            if ($primaryNode->var instanceof Expr\List_) {
                $varName = '';
                foreach ($primaryNode->var->vars as $listVar) {
                    $varName .= ucfirst($listVar->name);
                }
                $primaryNode = new Expr\Variable("tmpList" . $varName);
            } else {
                $primaryNode = $primaryNode->var;
            }
        } elseif ($this->isVarModification($primaryNode)) {
            $primaryNode = $primaryNode->var;
        } elseif ($this->isVarCreation($primaryNode) && !in_array("PhpParser\Node\Expr\ArrayItem", $parentClass) ) {
            $primaryNode = new Expr\Variable('tmpArray'.md5(serialize($primaryNode->items)));
        } else {
            if (is_array($primaryNode) === true) {
                foreach ($primaryNode as $key => $node) {
                    $primaryNode[$key] = $this->transformAssignInConditionTest($node);
                }
            } elseif (is_object($primaryNode) === true && !empty($primaryNode->getSubNodeNames())) {
                foreach ($primaryNode->getSubNodeNames() as $key) {
                    $primaryNode->$key = $this->transformAssignInConditionTest($primaryNode->$key, array(get_class($primaryNode)));
                }
            }
        }

        return $primaryNode;
    }

    /**
     * @param mixed $stmt
     *
     * @return bool
     */
    private function isVarCreation($stmt)
    {
        return $stmt instanceof Expr\Array_;
    }

    /**
     * @param mixed $stmt
     *
     * @return bool
     */
    private function isVarModification($stmt)
    {
        return $stmt instanceof Expr\PostDec ||
        $stmt instanceof Expr\PostInc ||
        $stmt instanceof Expr\PreDec ||
        $stmt instanceof Expr\PreInc;
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class FuncCallPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_FuncCall';
    }

    /**
     * @param Expr\FuncCall $node
     *
     * @return string
     */
    public function convert(Expr\FuncCall $node)
    {
        $collected = $this->assignManipulator->collectAssignInCondition($node->args);
        $node->args = $this->assignManipulator->transformAssignInConditionTest($node->args);

        if ($node->name instanceof Expr\Variable) {
            $instanciation = '{'.$this->dispatcher->p($node->name).'}';
        } else {
            $instanciation = $this->dispatcher->p($node->name);
        }

        return $collected->getCollected() . $instanciation . '('.$this->dispatcher->pCommaSeparated($node->args).')';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class TernaryPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_Ternary';
    }

    /**
     * @param Expr\Ternary $node
     * @param string       $returnAsArray
     *
     * @return string
     */
    public function convert(Expr\Ternary $node, $returnAsArray = false)
    {
        // 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.

        $collected = $this->assignManipulator->collectAssignInCondition($node->cond);
        $node->cond = $this->assignManipulator->transformAssignInConditionTest($node->cond);

        $collected->setExpr($this->dispatcher->pInfixOp('Expr_Ternary',
            $node->cond, ' ?'.(null !== $node->if ? ' '.$this->dispatcher->p($node->if).' ' : ' ' . $this->dispatcher->p($node->cond)).' : ', $node->else
        ));

        if ($returnAsArray === true) {
            return $collected;
        } else {
            return $collected->getCollected()." ".$collected->getExpr();
        }
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpToZephir\Converter\SimplePrinter;

class InfixOpPrinter extends SimplePrinter
{
    public static function getType()
    {
        return 'pInfixOp';
    }

    /**
     * Pretty prints an array of nodes (statements) and indents them optionally.
     *
     *
     * @return string Pretty printed statements
     */
    public function convert($type, Node $leftNode, $operatorString, Node $rightNode)
    {
        list($precedence, $associativity) = $this->dispatcher->getPrecedenceMap($type);

        return $this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
             .$operatorString
             .$this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1);
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpToZephir\Converter\SimplePrinter;

class PrecPrinter extends SimplePrinter
{
    public static function getType()
    {
        return 'pPrec';
    }

    /**
     * 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
     */
    public function convert(Node $node, $parentPrecedence, $parentAssociativity, $childPosition)
    {
        $type = $node->getType();
        if ($this->dispatcher->issetPrecedenceMap($type) === true) {
            $childPrecedences = $this->dispatcher->getPrecedenceMap($type);
            $childPrecedence = $childPrecedences[0];
            if ($childPrecedence > $parentPrecedence
                || ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
            ) {
                return '('.$this->dispatcher->{'p'.$type}($node).')';
            }
        }

        return $this->dispatcher->{'p'.$type}($node);
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpParser\Node\Stmt;
use PhpToZephir\Converter\SimplePrinter;

class TryCatchPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_TryCatch';
    }

    /**
     * @param Stmt\TryCatch $node
     *
     * @return string
     */
    public function convert(Stmt\TryCatch $node)
    {
        return 'try {'.$this->dispatcher->pStmts($node->stmts)."\n".'}'
             .$this->dispatcher->pImplode($node->catches)
             .($node->finallyStmts !== null
                ? ' finally {'.$this->dispatcher->pStmts($node->finallyStmts)."\n".'}'
                : '');
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node;
use PhpToZephir\Converter\SimplePrinter;

class ArgPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pArg';
    }

    /**
     * @param Node\Arg $node
     *
     * @return string
     */
    public function convert(Node\Arg $node)
    {
        if ($node->byRef) {
            $this->logger->logIncompatibility(
                'reference',
                'Reference not supported',
                $node,
                $this->dispatcher->getMetadata()->getClass()
            );
        }

        return ($node->unpack ? '...' : '').$this->dispatcher->p($node->value);
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpToZephir\Converter\Dispatcher;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\SimplePrinter;

class StmtsPrinter extends SimplePrinter
{
    public static function getType()
    {
        return 'pStmts';
    }

    /**
     * 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
     */
    public function convert(array $nodes, $indent = true)
    {
        $result = '';
        foreach ($nodes as $node) {
            $result .= "\n"
                    .$this->pComments($node->getAttribute('comments', array()))
                    .$this->dispatcher->p($node)
                    .($node instanceof Expr ? ';' : '');
        }

        if ($indent) {
            return preg_replace('~\n(?!$|'.Dispatcher::noIndentToken.')~', "\n    ", $result);
        } else {
            return $result;
        }
    }

    /**
     * @param array $comments
     *
     * @return string
     */
    private function pComments(array $comments)
    {
        $result = '';

        foreach ($comments as $comment) {
            $result .= $comment->getReformattedText()."\n";
        }

        return $result;
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Arg;
use PhpToZephir\ReservedWordReplacer;
use PhpToZephir\TypeFinder;
use PhpToZephir\NodeFetcher;
use PhpToZephir\Converter\Printer;

class ClassMethodPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var ReservedWordReplacer
     */
    private $reservedWordReplacer = null;
    /**
     * @var TypeFinder
     */
    private $typeFinder = null;
    /**
     * @var NodeFetcher
     */
    private $nodeFetcher = null;
    /**
     * @var string
     */
    private $lastMethod = null;

    /**
     * @param Dispatcher           $dispatcher
     * @param Logger               $logger
     * @param ReservedWordReplacer $reservedWordReplacer
     * @param TypeFinder           $typeFinder
     * @param NodeFetcher          $nodeFetcher
     */
    public function __construct(
        Dispatcher $dispatcher,
        Logger $logger,
        ReservedWordReplacer $reservedWordReplacer,
        TypeFinder $typeFinder,
        NodeFetcher $nodeFetcher
    ) {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->reservedWordReplacer = $reservedWordReplacer;
        $this->typeFinder = $typeFinder;
        $this->nodeFetcher = $nodeFetcher;
    }

    /**
     * @param string $value
     */
    public function setLastMethod($value)
    {
        $this->lastMethod = $value;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_ClassMethod';
    }

    /**
     * @param Stmt\ClassMethod $node
     *
     * @return string
     */
    public function convert(Stmt\ClassMethod $node)
    {
        $types = $this->typeFinder->getTypes(
            $node,
            $this->dispatcher->getMetadata()
        );
        foreach ($node->params as $param) {
            if ($param->byRef === true) {
                $this->logger->logIncompatibility(
                    'reference',
                    sprintf('Reference not supported in parametter (var "%s")', $param->name),
                    $param,
                    $this->dispatcher->getMetadata()->getClass()
                );
            }
        }

        if ($node->byRef) {
            $this->logger->logIncompatibility(
                'reference',
                'Reference not supported',
                $node,
                $this->dispatcher->getMetadata()->getClass()
            );
        }

        $this->dispatcher->setLastMethod($node->name);

        $stmt = $this->dispatcher->pModifiers($node->type).'function '.$node->name.'(';
        $varsInMethodSign = array();

        if (isset($types['params']) === true) {
            $params = array();
            foreach ($types['params'] as $type) {
                $varsInMethodSign[] = $type['name'];
                $stringType = $this->printType($type);
                $params[] = ((!empty($stringType)) ? $stringType.' ' : '').''.$type['name'].(($type['default'] === null) ? '' : ' = '.$this->dispatcher->p($type['default']));
            }

            $stmt .= implode(', ', $params);
        }

        $stmt .= ')';
        $stmt .= $this->printReturn($node, $types);

        $stmt .= (null !== $node->stmts ? "\n{".$this->printVars($node, $varsInMethodSign).
             $this->dispatcher->pStmts($node->stmts)."\n}" : ';')."\n";

        return $stmt;
    }

    /**
     * @param Stmt\ClassMethod $node
     * @param array            $varsInMethodSign
     *
     * @return string
     */
    private function printVars(Stmt\ClassMethod $node, array $varsInMethodSign)
    {
        $var = '';
        $vars = array_diff(array_unique(array_filter($this->collectVars($node))), $varsInMethodSign);
        if (!empty($vars)) {
            $var .= "\n    var ".implode(', ', $vars).";\n";
        }

        // dirty...
        Printer\Expr\ArrayDimFetchPrinter::resetCreatedVars();

        return $var;
    }

    /**
     * @param Stmt\ClassMethod $node
     * @param array            $types
     *
     * @return string
     */
    private function printReturn(Stmt\ClassMethod $node, array $types)
    {
        $stmt = '';
        if (array_key_exists('return', $types) === false && $this->hasReturnStatement($node) === false) {
            $stmt .= ' -> void';
        } elseif (array_key_exists('return', $types) === true && empty($types['return']['type']['value']) === false) {
            $stmt .= ' -> '.$this->printType($types['return']);
        }

        return $stmt;
    }

    /**
     * @param Stmt\ClassMethod $nodes
     *
     * @return bool
     */
    private function hasReturnStatement($nodes)
    {
        foreach ($this->nodeFetcher->foreachNodes($nodes) as $nodeData) {
            $node = $nodeData['node'];
            if ($node instanceof Stmt\Return_) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param \ArrayIterator|array $node
     * @param array                $vars
     *
     * @return \ArrayIterator|array
     */
    private function collectVars($node, array $vars = array())
    {
        $noFetcher = new NodeFetcher();

        foreach ($noFetcher->foreachNodes($node) as &$stmt) {
            if ($stmt['node'] instanceof Expr\Assign) {
                if (($stmt['node']->var instanceof Expr\PropertyFetch) === false
                 && ($stmt['node']->var instanceof Expr\StaticPropertyFetch) === false
                 && ($stmt['node']->var instanceof Expr\ArrayDimFetch) === false
                 && ($stmt['node']->var instanceof Expr\List_) === false) {
                    if (is_object($stmt['node']->var->name) === false) { // if true it is a dynamic var
                        $vars[] = $stmt['node']->var->name;
                    }
                } elseif (($stmt['node']->var instanceof Expr\List_) === true) {
                    $varInList = array();
                    foreach ($stmt['node']->var->vars as $var) {
                        if (null !== $var) {
                            $varInList[] = ucfirst($this->dispatcher->p($var));
                            if (($var instanceof Expr\ArrayDimFetch) === false) {
                                $vars[] = $this->dispatcher->p($var);
                            }
                        }
                    }

                    $vars[] = 'tmpList' . str_replace(array('[', ']', '"'), '', implode('', $varInList));
                }
            } elseif ($stmt['node'] instanceof Stmt\Foreach_) {
                if (null !== $stmt['node']->keyVar) {
                    $vars[] = $stmt['node']->keyVar->name;
                }
                $vars[] = $stmt['node']->valueVar->name;
            } elseif ($stmt['node'] instanceof Stmt\For_) {
                foreach ($stmt['node']->init as $init) {
                    if ($init instanceof Expr\Assign) {
                        $vars[] = $init->var->name;
                    }
                }
            } elseif ($stmt['node'] instanceof Stmt\If_) {
                foreach ($this->nodeFetcher->foreachNodes($stmt['node']->cond) as $nodeData) {
                    $node = $nodeData['node'];
                    if ($node instanceof Expr\Array_) {
                        $vars[] = 'tmpArray'.md5(serialize($node->items));
                    }
                }
            } elseif ($stmt['node'] instanceof Stmt\Catch_) {
                $vars[] = $stmt['node']->var;
            } elseif ($stmt['node'] instanceof Stmt\Return_ && $stmt['node']->expr instanceof Expr\Array_) {
                $vars[] = 'tmpArray'.md5(serialize($stmt['node']->expr->items));
            } elseif ($stmt['node'] instanceof Stmt\Static_) {
            	foreach ($stmt['node']->vars as $var) {
                	$vars[] = $var->name;
            	}
            } elseif ($stmt['node'] instanceof Arg && $stmt['node']->value instanceof Expr\Array_) {
            	 $vars[] = 'tmpArray'.md5(serialize($stmt['node']->value->items));
            }

            if ($stmt['node'] instanceof Expr\ArrayDimFetch && !in_array("PhpParser\Node\Expr\ArrayDimFetch", $stmt['parentClass'])) {
                $varCreatedInArray = $this->dispatcher->pExpr_ArrayDimFetch($stmt['node'], true);
                foreach ($varCreatedInArray['vars'] as $var) {
                    $vars[] = $var;
                }
            }
        }

        $vars = array_map(array($this->reservedWordReplacer, 'replace'), $vars);

        return $vars;
    }

    /**
     * @param array $type
     *
     * @throws \Exception
     *
     * @return string
     */
    private function printType($type)
    {
        if (isset($type['type']) === false) {
            return '';
        }
        if (isset($type['type']['isClass']) === false) {
            throw new \Exception('isClass not found');
        }
        if (isset($type['type']['value']) === false) {
            throw new \Exception('value not found');
        }

        return ($type['type']['isClass'] === true) ? '<'.$type['type']['value'].'>' : $type['type']['value'];
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpToZephir\NodeFetcher;

class ClosurePrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;

    private static $converted = array();

    /**
     * @param Dispatcher $dispatcher
     * @param Logger     $logger
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_Closure';
    }

    /**
     * @param Expr\Closure $node
     *
     * @return string
     */
    public function convert(Expr\Closure $node)
    {
        $methodName = $this->dispatcher->getMetadata()->getClass().$this->dispatcher->getLastMethod();
        if (isset(self::$converted[$methodName])) {
            ++self::$converted[$methodName];
        } else {
            self::$converted[$methodName] = 1;
        }

        $name = $methodName.'Closure'.$this->N2L(count(self::$converted[$methodName]));

        $this->logger->logNode(
            sprintf('Closure does not exist in Zephir, class "%s" with __invoke is created', $name),
            $node,
            $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
        );

        return 'new '.$name.'('.$this->dispatcher->pCommaSeparated($node->uses).')';
    }

    /**
     * @param null|string $lastMethod
     * @param int         $number
     */
    public function createClosureClass(Expr\Closure $node, $lastMethod, $number)
    {
        $this->logger->trace(__METHOD__.' '.__LINE__, $node, $this->dispatcher->getMetadata()->getFullQualifiedNameClass());

        $name = $this->dispatcher->getMetadata()->getClass().$lastMethod.'Closure'.$this->N2L($number);

        $this->logger->logNode(
            sprintf('Closure does not exist in Zephir, class "%s" with __invoke is created', $name),
            $node,
            $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
        );

        return array(
         'name' => $name,
         'code' => $this->createClass($name, $this->dispatcher->getMetadata()->getNamespace(), $node),
        );
    }

    /**
     * @param string $name
     * @param string $namespace
     */
    private function createClass($name, $namespace, Expr\Closure $node)
    {
        $class = "namespace $namespace;

class $name
{
";

        foreach ($node->uses as $use) {
            $class .= '    private '.$use->var.";\n";
        }

        $class .= '
    public function __construct('.(!empty($node->uses) ? ''.$this->dispatcher->pCommaSeparated($node->uses) : '').')
    {
        ';
        foreach ($node->uses as $use) {
            $class .= '        let this->'.$use->var.' = '.$use->var.";\n";
        }
        $class .= '
    }

    public function __invoke('.$this->dispatcher->pCommaSeparated($node->params).')
    {'.$this->dispatcher->pStmts($this->convertUseToMemberAttribute($node->stmts, $node->uses)).'
    }
}
    ';

        return $class;
    }

    /**
     * @param Node[]            $node
     * @param Expr\ClosureUse[] $uses
     */
    private function convertUseToMemberAttribute($node, $uses)
    {
        $noFetcher = new NodeFetcher();

        foreach ($noFetcher->foreachNodes($node) as &$stmt) {
            if ($stmt['node'] instanceof Expr\Variable) {
                foreach ($uses as $use) {
                    if ($use->var === $stmt['node']->name) {
                        $stmt['node']->name = 'this->'.$stmt['node']->name;
                    }
                }
            }
        }

        return $node;
    }

    /**
     * @param int $number
     */
    private function N2L($number)
    {
        $result = array();
        $tens = floor($number / 10);
        $units = $number % 10;

        $words = array(
            'units' => array('', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eightteen', 'Nineteen'),
            'tens' => array('', '', 'Twenty', 'Thirty', 'Fourty', 'Fifty', 'Sixty', 'Seventy', 'Eigthy', 'Ninety'),
        );

        if ($tens < 2) {
            $result[] = $words['units'][$tens * 10 + $units];
        } else {
            $result[] = $words['tens'][$tens];

            if ($units > 0) {
                $result[count($result) - 1] .= '-'.$words['units'][$units];
            }
        }

        if (empty($result[0])) {
            $result[0] = 'Zero';
        }

        return trim(implode(' ', $result));
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Stmt;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class ReturnPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_Return';
    }

    /**
     * @param Stmt\Return_ $node
     *
     * @return string
     */
    public function convert(Stmt\Return_ $node)
    {
        $collected = $this->assignManipulator->collectAssignInCondition($node->expr);
        $node->expr = $this->assignManipulator->transformAssignInConditionTest($node->expr);

        return $collected->getCollected().'return'.(null !== $node->expr ? ' '.$this->dispatcher->p($node->expr) : '').';';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Expr;
use PhpToZephir\Converter\Manipulator\AssignManipulator;

class MethodCallPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(Dispatcher $dispatcher, Logger $logger, AssignManipulator $assignManipulator)
    {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->assignManipulator = $assignManipulator;
    }

    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_MethodCall';
    }

    /**
     * @param Expr\MethodCall $node
     *
     * @return string
     */
    public function convert(Expr\MethodCall $node)
    {
        $collected = $this->assignManipulator->collectAssignInCondition($node->args);
        $node->args = $this->assignManipulator->transformAssignInConditionTest($node->args);

        return $collected->getCollected() .
                $this->dispatcher->pVarOrNewExpr($node->var).'->'.$this->dispatcher->pObjectProperty($node->name)
             .'('.$this->dispatcher->pCommaSeparated($node->args).')';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpParser\Node\Stmt;
use PhpToZephir\Converter\SimplePrinter;

class BreakPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_Break';
    }

    /**
     * @param Stmt\Break_ $node
     *
     * @return string
     */
    public function convert(Stmt\Break_ $node)
    {
        return 'break'.($node->num !== null ? ' '.$this->dispatcher->p($node->num) : '').';';
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpParser\Node\Stmt;
use PhpToZephir\Converter\SimplePrinter;

class ForeachPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_Foreach';
    }

    /**
     * @param Stmt\Foreach_ $node
     *
     * @return string
     */
    public function convert(Stmt\Foreach_ $node)
    {
        return 'for '.(null !== $node->keyVar ? $this->dispatcher->p($node->keyVar).', ' : '').$this->dispatcher->p($node->valueVar).
               ' in '.$this->dispatcher->p($node->expr).' {'.
               $this->dispatcher->pStmts($node->stmts)."\n".'}';
    }
}


<?php

/**
 * This file is part of the PHP to Zephir package.
 *
 * (c) Stéphane Demonchaux <demonchaux.stephane@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace PhpToZephir\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use PhpToZephir\Engine;
use PhpToZephir\Logger;
use PhpToZephir\CodeCollector\DirectoryCodeCollector;
use PhpToZephir\CodeCollector\FileCodeCollector;
use PhpToZephir\Render\FileRender;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;

/**
 * Convert command.
 *
 * @author Stéphane Demonchaux
 */
class Convert extends Command
{
    /**
     * @var Engine
     */
    private $engine;
    /**
     * @var FileRender
     */
    private $fileRender;
    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @param Engine          $engine
     * @param FileRender      $fileRender
     * @param OutputInterface $output
     */
    public function __construct(Engine $engine, FileRender $fileRender, OutputInterface $output)
    {
        $this->engine = $engine;
        $this->fileRender = $fileRender;
        $this->output = $output;
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('phpToZephir:convert')
            ->setDescription('Convert a php file or directory to Zephir')
            ->addArgument('source', InputArgument::REQUIRED, 'Directory/File to convert')
            ->addOption('debug', null, null, "Debug output")
            ->addOption('v', null, null, "Verbose mode")
            ->addArgument('file', InputArgument::OPTIONAL, 'Ignore a file');
    }

    /* (non-PHPdoc)
     * @see \Symfony\Component\Console\Command\Command::execute()
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $source = $input->getArgument('source');

        if (is_dir($source) === false && is_file($source) === false) {
            throw new \InvalidArgumentException(sprintf('"%s" is not a file or a directory', $source));
        }

        $logger = new Logger($this->output, $input->getOption('debug'));

        if (is_dir($source) === true) {
            $collector = new DirectoryCodeCollector(array($source));
        } elseif (is_file($source) === true) {
            $collector = new FileCodeCollector(array($source));
        }

        foreach ($this->engine->convert($collector, $logger, $input->getArgument('file')) as $file) {
            $this->fileRender->render($file);
        }

        if (!empty($logger->getIncompatibility())) {
            $table = new Table($this->output);

            $table->setHeaders(array(
                array(new TableCell('Incompatibility', array('colspan' => 4))),
                array('Type', 'Message', 'Line', 'Class')
            ));
            $table->setRows($logger->getIncompatibility());
            $table->render();
        } else {
            $logger->log('No incompatibily found !');
        }

        if ($input->getOption('v') === true) {
            $table = new Table($this->output);

            $table->setHeaders(array(
                array(new TableCell('Log', array('colspan' => 3))),
                array('Message', 'Line', 'Class')
            ));
            $table->setRows($logger->getLogs());
            $table->render();
        }
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Stmt;

use PhpParser\Node;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr\BinaryOp;
use PhpToZephir\Converter\SimplePrinter;

class SwitchPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pStmt_Switch';
    }

    /**
     * @param Stmt\Switch_ $node
     *
     * @return string
     */
    public function convert(Stmt\Switch_ $node)
    {
        $transformToIf = false;
        foreach ($node->cases as $case) {
            if (($case->cond instanceof Scalar\String_) === false && $case->cond !== null) {
                $transformToIf = true;
            }
        }

        if ($transformToIf === true) {
            return $this->convertSwitchToIfelse($node);
        } else {
            return 'switch ('.$this->dispatcher->p($node->cond).') {'
             .$this->dispatcher->pStmts($node->cases)."\n".'}';
        }
    }

    /**
     * @return Node\Expr
     */
    private function removeBreakStmt($case)
    {
        if (is_array($case->stmts) && !empty($case->stmts)) {
            $key = array_keys($case->stmts);
            $breakStmt = $case->stmts[end($key)];
            if ($breakStmt instanceof Stmt\Break_) {
                unset($case->stmts[end($key)]);
            }
        }

        return $case;
    }

    /**
     * @param Stmt\Switch_ $node
     *
     * @return string
     */
    private function convertSwitchToIfelse(Stmt\Switch_ $node)
    {
        $stmt = array(
            'else' => null,
            'elseifs' => array(),
        );
        $if = null;
        $ifDefined = false;
        $left = null;
        foreach ($node->cases as $case) {
            $case = $this->removeBreakStmt($case);
            if (end($node->cases) === $case) {
                $stmt['else'] = new Stmt\Else_($case->stmts);
            } else {
                if (empty($case->stmts)) { // concatene empty statement
                    if ($left !== null) {
                        $left = new BinaryOp\BooleanOr($left, $case->cond);
                    } else {
                        $left = $case->cond;
                    }
                } elseif ($ifDefined === false) {
                    if ($left !== null) {
                        $lastLeft = new BinaryOp\BooleanOr($left, $case->cond);
                        $if = new Stmt\If_($lastLeft, array('stmts' => $case->stmts));
                        $left = null;
                    } else {
                        $if = new Stmt\If_($case->cond, array('stmts' => $case->stmts));
                    }
                    $ifDefined = true;
                } else {
                    if ($left !== null) {
                        $lastLeft = new BinaryOp\BooleanOr($left, $case->cond);
                        $stmt['elseifs'][] = new Stmt\ElseIf_($lastLeft, $case->stmts);
                        $left = null;
                    } else {
                        $stmt['elseifs'][] = new Stmt\ElseIf_($case->cond, $case->stmts);
                    }
                }
            }
        }
        $elseifs = array_reverse($stmt['elseifs']);
        $if = new Stmt\If_($if->cond, array(
            'stmts' => $if->stmts,
            'elseifs' => $elseifs,
            'else' => $stmt['else'],
        ));

        return $this->dispatcher->pStmt_If($if);
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpToZephir\Converter\Dispatcher;
use PhpToZephir\Logger;
use PhpParser\Node\Scalar;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpToZephir\Converter\Manipulator\ArrayManipulator;
use PhpToZephir\Converter\Manipulator\AssignManipulator;
use PhpToZephir\Converter\Manipulator\ArrayDto;
use PhpToZephir\Converter\Manipulator\PhpToZephir\Converter\Manipulator;

class AssignPrinter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var Logger
     */
    private $logger = null;
    /**
     * @var ArrayManipulator
     */
    private $arrayManipulator = null;
    /**
     * @var AssignManipulator
     */
    private $assignManipulator = null;

    /**
     * @param Dispatcher        $dispatcher
     * @param Logger            $logger
     * @param ArrayManipulator  $arrayManipulator
     * @param AssignManipulator $assignManipulator
     */
    public function __construct(
        Dispatcher $dispatcher,
        Logger $logger,
        ArrayManipulator $arrayManipulator,
        AssignManipulator $assignManipulator
    ) {
        $this->dispatcher = $dispatcher;
        $this->logger = $logger;
        $this->arrayManipulator = $arrayManipulator;
        $this->assignManipulator = $assignManipulator;
    }

    public static function getType()
    {
        return 'pExpr_Assign';
    }

    /**
     * @param Expr\Assign $node
     *
     * @return string
     */
    public function convert(Expr\Assign $node, $extract = true)
    {
        $type = 'Expr_Assign';
        $leftNode = $node->var;
        $operatorString = ' = ';
        $rightNode = $node->expr;

        list($precedence, $associativity) = $this->dispatcher->getPrecedenceMap($type);

        if ($rightNode instanceof Expr\Array_) {
            $this->logger->trace(
                self::getType().' '.__LINE__,
                $node,
                $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
            );
            $collect = $this->dispatcher->pExpr_Array($rightNode, true);

            return (($extract === true) ? $collect->getCollected() : '') .
                'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
                .$operatorString.' '.$collect->getExpr();
        } elseif (($rightNode instanceof Expr\MethodCall || $rightNode instanceof Expr\FuncCall) && ($leftNode instanceof Expr\List_) === false) {
            $collected = $this->convertCall($node, new ArrayDto());

            return $collected['extracted']->getCollected().
                'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
                .$operatorString.' '.$this->dispatcher->p($collected['node']->expr);
        } elseif ($rightNode instanceof Expr\BinaryOp\Concat) {
            $collected = $this->convertConcat($node->expr, new ArrayDto());

            return $collected['extracted']->getCollected().
                'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
                .$operatorString.' '.$this->dispatcher->p($collected['node']);
        } elseif ($rightNode instanceof Expr\Ternary) {
            $collect = $this->dispatcher->pExpr_Ternary($rightNode, true);

            return $collect->getCollected().
                   'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
            .$operatorString.' '.$collect->getExpr();
        } elseif ($leftNode instanceof Expr\List_) {
            return $this->convertListStmtToAssign($node);
        } elseif ($leftNode instanceof Expr\ArrayDimFetch || $rightNode instanceof Expr\ArrayDimFetch) {
            return $this->arrayDimFetchCase($node, $leftNode, $rightNode, $operatorString, $precedence, $associativity, $extract);
        } elseif ($rightNode instanceof Expr\Assign) { // multiple assign
            $valueToAssign = ' = '.$this->dispatcher->p($this->findValueToAssign($rightNode));
            $vars = array($this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1));
            foreach ($this->findVarToAssign($rightNode) as $nodeAssigned) {
                $vars[] = $nodeAssigned;
            }

            $toReturn = '';

            foreach ($vars as $var) {
                $toReturn .= 'let '.$var.$valueToAssign.";\n";
            }

            return $toReturn;
        } elseif ($rightNode instanceof Variable || $rightNode instanceof Scalar || $rightNode instanceof Array_) {
            $this->logger->trace(
                self::getType().' '.__LINE__,
                $node,
                $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
            );

            return 'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
            .$operatorString
            .$this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1);
        } else {
            $this->logger->trace(
                self::getType().' '.__LINE__,
                $node,
                $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
            );

            return 'let '.$this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1)
                   .$operatorString.' '.$this->dispatcher->p($rightNode);
        }
    }

    private function convertConcat(Expr\BinaryOp\Concat $concat, ArrayDto $collected)
    {
        if ($concat->left instanceof Expr\BinaryOp\Concat) {
            $extracted = $this->convertConcat($concat->left, $collected);
            $concat->left = $extracted['node'];
            $collected = $extracted['extracted'];
        }
        if ($concat->right instanceof Expr\BinaryOp\Concat) {
            $extracted = $this->convertConcat($concat->right, $collected);
            $concat->right = $extracted['node'];
            $collected = $extracted['extracted'];
        }

        return $this->convertCall($concat, $collected);
    }

    private function convertCall($node, ArrayDto $collected)
    {
        if (property_exists($node, 'left') === true && ($node->left instanceof Expr\MethodCall || $node->left  instanceof Expr\FuncCall)) {
            $collected = $this->assignManipulator->collectAssignInCondition($node->left->args, $collected);
            $node->left->args = $this->assignManipulator->transformAssignInConditionTest($node->left->args);
        }

        if (property_exists($node, 'right') === true && ($node->right instanceof Expr\MethodCall || $node->right  instanceof Expr\FuncCall)) {
            $collected = $this->assignManipulator->collectAssignInCondition($node->right->args, $collected);
            $node->right->args = $this->assignManipulator->transformAssignInConditionTest($node->right->args);
        }

        if (property_exists($node, 'expr') === true && ($node->expr instanceof Expr\MethodCall || $node->expr  instanceof Expr\FuncCall)) {
            $collected = $this->assignManipulator->collectAssignInCondition($node->expr->args, $collected);
            $node->expr->args = $this->assignManipulator->transformAssignInConditionTest($node->expr->args);
        }

        return array('extracted' => $collected, 'node' => $node);
    }

    private function isSomething($rightNode)
    {
        $node = 'PhpParser\Node\\';
        $expr = $node.'Expr\\';
        $binaryOp = $expr.'BinaryOp\\';

        $somethingList = array(
            $node.'Scalar',
            $expr.'Variable',
            $expr.'Array_',
            $expr.'UnaryMinus',
            $expr.'StaticCall',
            $expr.'FuncCall',
            $expr.'ConstFetch',
            $expr.'Clone_',
            $expr.'New_',
            $expr.'ClassConstFetch',
            $expr.'Ternary',
            $expr.'BooleanNot',
            $expr.'Cast',
            $expr.'MethodCall',
            $expr.'Isset_',
            $expr.'Empty_',
            $expr.'Closure',
            $expr.'ArrayDimFetch',
            $expr.'Include_',
            $expr.'PropertyFetch',
            $binaryOp.'Concat',
            $binaryOp.'BooleanOr',
            $binaryOp.'Minus',
            $binaryOp.'Plus',
            $binaryOp.'BitwiseOr',
            $binaryOp.'BitwiseAnd',
            $binaryOp.'Mul',
        );

        foreach ($somethingList as $something) {
            if ($rightNode instanceof $something) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param Assign $rightNode
     */
    private function findValueToAssign($rightNode)
    {
        if ($rightNode->expr instanceof Expr\Assign) {
            return $this->findValueToAssign($rightNode->expr);
        } else {
            return $rightNode->expr;
        }
    }

    /**
     * @param Assign $node
     *
     * @return string
     */
    private function convertListStmtToAssign($node)
    {
        $type = 'Expr_Assign';
        $rightNode = $node->expr;
        $pList = array();
        $listVarName = 'tmpList';

        list($precedence, $associativity) = $this->dispatcher->getPrecedenceMap($type);

        foreach ($node->var->vars as $count => $var) {
            if (null === $var) {
                $pList[] = '';
            } else {
                $listVarName .= ucfirst($this->dispatcher->p($var));
            }
        }

        $listVarName = str_replace(array('[', ']', '"'), '', $listVarName);

        $pList[] = 'let ' . $listVarName . ' = ' . $this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1) . '';

        foreach ($node->var->vars as $count => $var) {
            if (null === $var) {
                $pList[] = '';
            } else {
                $pList[] = 'let '.$this->dispatcher->p($var) . ' = ' . $listVarName.'['.$count.']';
            }
        }

        return implode(";\n", $pList);
    }

    /**
     * @param Assign $rightNode
     */
    private function findVarToAssign($rightNode, array $toAssign = array())
    {
        if ($rightNode->expr instanceof Expr\Assign) {
            $toAssign = $this->findVarToAssign($rightNode->expr);
        }
        $toAssign[] = $this->dispatcher->p($rightNode->var);

        return $toAssign;
    }

    /**
     * @param Assign $node
     * @param Expr   $leftNode
     * @param Expr   $rightNode
     * @param string $operatorString
     */
    private function arrayDimFetchCase($node, $leftNode, $rightNode, $operatorString, $precedence, $associativity, $extract = true)
    {
        $this->logger->trace(
            self::getType().' '.__LINE__,
            $node,
            $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
        );
        $head = '';

        if ($leftNode instanceof ArrayDimFetch) {
            if (false === $splitedArray = $this->arrayManipulator->arrayNeedToBeSplit($leftNode)) {
                $leftString = $this->dispatcher->pPrec($leftNode, $precedence, $associativity, 1);
            } else {
                $result = $this->dispatcher->pExpr_ArrayDimFetch($leftNode, true);
                if ($extract === true) {
                    $head .= $result['head'];
                }
                $leftString = $result['lastExpr'];
            }
        } else {
            $leftString = $this->dispatcher->pPrec($leftNode, $precedence, $associativity, -1);
        }

        if ($rightNode instanceof ArrayDimFetch) {
            if (false === $splitedArray = $this->arrayManipulator->arrayNeedToBeSplit($rightNode)) {
                $rightString = $this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1);
            } else {
                $result = $this->dispatcher->pExpr_ArrayDimFetch($rightNode, true, true);
                if ($extract === true) {
                    $head .= $result['head'];
                }
                $rightString = $result['lastExpr'];
            }
        } elseif ($this->isSomething($rightNode)) {
            $this->logger->trace(
                self::getType().' '.__LINE__,
                $node,
                $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
            );
            // @TODO add test case for each
            $rightString = $this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1);
        } else {
            if ($extract === true) {
                $head .= $this->dispatcher->pPrec($rightNode, $precedence, $associativity, 1).";\n";
            }

            $rightString = $this->dispatcher->p($rightNode->var);
        }

        return $head.'let '.$leftString.$operatorString.$rightString;
    }
}


<?php

namespace PhpToZephir\Converter\Printer\Expr;

use PhpParser\Node\Expr;
use PhpToZephir\Converter\SimplePrinter;

class AssignRefPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pExpr_AssignRef';
    }

    /**
     * @param Expr\AssignRef $node
     *
     * @return string
     */
    public function convert(Expr\AssignRef $node)
    {
        $this->logger->logIncompatibility(
            '(=&) AssignRef',
            '(=&) AssignRef does not exist in zephir, assign',
            $node,
            $this->dispatcher->getMetadata()->getFullQualifiedNameClass()
        );

        return 'let '.$this->dispatcher->pInfixOp('Expr_AssignRef', $node->var, ' = ', $node->expr);
    }
}


<?php

namespace PhpToZephir\Converter;

use PhpToZephir\Logger;
use PhpToZephir\NodeFetcher;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpToZephir\Converter\Printer\Expr\ClosurePrinter;
use PhpToZephir\ClassCollector;

class Converter
{
    /**
     * @var Dispatcher
     */
    private $dispatcher = null;
    /**
     * @var NodeFetcher
     */
    private $nodeFetcher = null;

    /**
     * @param Dispatcher  $dispatcher
     * @param NodeFetcher $nodeFetcher
     */
    public function __construct(Dispatcher $dispatcher, NodeFetcher $nodeFetcher)
    {
        $this->dispatcher = $dispatcher;
        $this->nodeFetcher = $nodeFetcher;
    }

    /**
     * @param array          $stmts
     * @param ClassCollector $classCollector
     * @param Logger         $logger
     * @param string         $fileName
     * @param array          $classCollected
     *
     * @return array
     */
    public function nodeToZephir(array $stmts, ClassCollector $classCollector, Logger $logger, $fileName = null, array $classCollected = array())
    {
        $classInformation = ClassInformationFactory::getInstance();
        $metadata = $classInformation->getClassesMetdata($stmts);

        $this->implementsExist($metadata, $classCollector);

        return array(
            'code' => $this->dispatcher->convert($stmts, $metadata, $classCollector, $logger),
            'namespace' => $metadata->getNamespace(),
            'additionalClass' => $this->findAdditionalClasses($stmts, $logger),
        );
    }

    /**
     * @param ClassMetadata $metadata
     * @param ClassCollector $classCollector
     */
    private function implementsExist(ClassMetadata $metadata, ClassCollector $classCollector)
    {
        foreach ($metadata->getImplements() as $implements) {
            $this->implementExist($metadata, $classCollector, $implements);
        }
    }

    /**
     * @param ClassMetadata $metadata
     * @param ClassCollector $classCollector
     * @param string $implements
     * @throws \Exception
     * @return boolean
     */
    private function implementExist(ClassMetadata $metadata, ClassCollector $classCollector, $implements)
    {
        // Class is in actual namespace
        if (array_key_exists($metadata->getNamespace() . '\\' . $implements, $classCollector->getCollected())) {
            return true;
        }

        foreach ($metadata->getClasses() as $use) {
            if (substr(strrchr($use, "\\"), 1) === $implements) {
                return true;
            }
        }

        if (interface_exists($implements)) {
            return true;
        }

        throw new \Exception(sprintf('interface %s does not exist', $implements));
    }

    /**
     * @param array $stmts
     *
     * @return array
     */
    private function findAdditionalClasses(array $stmts, Logger $logger)
    {
        $closurePrinter = new ClosurePrinter($this->dispatcher, $logger);
        $lastMethod = null;
        $aditionalClass = array();
        $number = 0;

        foreach ($this->nodeFetcher->foreachNodes($stmts) as $nodeData) {
            $node = $nodeData['node'];
            if ($node instanceof Stmt\ClassMethod) {
                $lastMethod = $node->name;
            } elseif ($node instanceof Expr\Closure) {
                $aditionalClass[] = $closurePrinter->createClosureClass($node, $lastMethod, $number);
                ++$number;
            }
        }

        return $aditionalClass;
    }
}


<?php

namespace PhpToZephir\Converter\Printer;

use PhpParser\Node\Stmt;
use PhpToZephir\Converter\SimplePrinter;

class ModifiersPrinter extends SimplePrinter
{
    /**
     * @return string
     */
    public static function getType()
    {
        return 'pModifiers';
    }

    /**
     * @param int $modifiers
     *
     * @return string
     */
    public function convert($modifiers)
    {
        return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC    ? 'public '    : '')
              .($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '')
              .($modifiers & Stmt\Class_::MODIFIER_PRIVATE   ? 'protected ' : '') // due to #issues/251
              .($modifiers & Stmt\Class_::MODIFIER_STATIC    ? 'static '    : '')
              .($modifiers & Stmt\Class_::MODIFIER_ABSTRACT  ? 'abstract '  : '')
              .($modifiers & Stmt\Class_::MODIFIER_FINAL     ? 'final '     : '');
    }
}


<?php

namespace PhpToZephir\CodeCollector;

class DirectoryCodeCollector implements CodeCollectorInterface
{
    /**
     * @var array
     */
    private $directories;

    /**
     * @param array $code
     */
    public function __construct(array $directories)
    {
        $this->directories = $directories;
    }

    /**
     * @param string $dir
     *
     * @return \RegexIterator
     */
    private function findFiles($dir)
    {
        $directory = new \RecursiveDirectoryIterator($dir);
        $iterator = new \RecursiveIteratorIterator($directory);
        $regex = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);

        return $regex;
    }

    /**
     * @return array
     */
    public function getCode()
    {
        $files = array();

        foreach ($this->directories as $directory) {
            foreach ($this->findFiles($directory) as $file) {
                if (is_array($file)) {
                    $file = reset($file);
                }

                $files[$file] = file_get_contents($file);
            }
        }

        return $files;
    }
}


<?php

namespace PhpToZephir\Converter\Manipulator;

use PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpToZephir\ReservedWordReplacer;
use PhpToZephir\Converter\ClassMetadata;

class ClassManipulator
{
    /**
     * @var ReservedWordReplacer
     */
    private $reservedWordReplacer = null;
    private $classes = array();

    /**
     * @param ReservedWordReplacer $reservedWordReplacer
     */
    public function __construct(ReservedWordReplacer $reservedWordReplacer)
    {
        $this->reservedWordReplacer = $reservedWordReplacer;
    }

    /**
     * @param Node\Name     $node
     * @param ClassMetadata $metadata
     * @param array         $classCollected
     *
     * @return string
     */
    public function findRightClass(Node\Name $node, ClassMetadata $metadata, array $classCollected = array())
    {
        $class = implode('\\', $node->parts);
        $lastPartsClass = array_map(function ($value) { return substr(strrchr($value, '\\'), 1); }, $classCollected);

        $class = $this->reservedWordReplacer->replace($class);

        if (in_array($class, $classCollected)) {
            return '\\'.$class;
        } elseif (array_key_exists($class, $metadata->getClassesAlias())) {
            $alias = $metadata->getClassesAlias();

            return '\\'.$alias[$class];
        } elseif (false !== $key = array_search($class, $lastPartsClass)) {
            return '\\'.$classCollected[$key];
        } elseif (false !== $key = array_search($metadata->getNamespace().'\\'.$class, $classCollected)) {
            return '\\'.$classCollected[$key];
        } else {
            return $class;
        }
    }

    public function registerClassImplements(Stmt\Class_ $node)
    {
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles\ioc\binding;
use stubbles\ioc\InjectionProvider;
/**
 * Ensures that an object instance is only created once.
 *
 * @internal
 */
class SingletonBindingScope implements BindingScope
{
    /**
     * instances in this scope
     *
     * @type  object[]
     */
    protected $instances = [];

    /**
     * returns the requested instance from the scope
     *
     * @param   \ReflectionClass                 $impl      concrete implementation
     * @param   \stubbles\ioc\InjectionProvider  $provider
     * @return  object
     */
    public function getInstance(\ReflectionClass $impl, InjectionProvider $provider)
    {
        $key = $impl->getName();
        if (!isset($this->instances[$key])) {
            $this->instances[$key] = $provider->get();
        }

        return $this->instances[$key];
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles\ioc;
use stubbles\ioc\binding\BindingException;
use stubbles\ioc\binding\BindingScopes;
use stubbles\ioc\binding\ClassBinding;
use stubbles\ioc\binding\ConstantBinding;
use stubbles\ioc\binding\ListBinding;
use stubbles\ioc\binding\MapBinding;
use stubbles\ioc\binding\PropertyBinding;
use stubbles\ioc\binding\Session;
use stubbles\reflect\annotation\Annotations;

use function stubbles\reflect\annotationsOf;
/**
 * Injector for the IoC functionality.
 *
 * Used to create the instances.
 */
class Injector
{
    /**
     * @type  string
     */
    private $environment;
    /**
     * index for faster access to bindings
     *
     * Do not access this array directly, use getIndex() instead. The binding
     * index is a requirement because the key for a binding is not necessarily
     * complete when the binding is added to the injector.
     *
     * @type  \stubbles\ioc\binding\Binding[]
     */
    private $index    = [];
    /**
     * list of available binding scopes
     *
     * @type  \stubbles\ioc\binding\BindingScopes
     */
    private $scopes;
    /**
     * denotes how deep in the object graph the current injection takes place
     *
     * @type  string[]
     */
    private $injectionStack = [];

    /**
     * constructor
     *
     * @param  string                               $environment  optional  name of current environment
     * @param  \stubbles\ioc\binding\Binding[]      $bindings     optional
     * @param  \stubbles\ioc\binding\BindingScopes  $scopes       optional
     * @since  1.5.0
     */
    public function __construct($environment = null, array $bindings = [], BindingScopes $scopes = null)
    {
        $this->environment = $environment;
        $this->scopes      = $scopes !== null ? $scopes : new BindingScopes();
        foreach ($bindings as $binding) {
            $this->index[$binding->getKey()] = $binding;
        }
    }

    /**
     * sets the session for the session scope in case it is the built-in implementation
     *
     * Additionally, it binds the given session interface name to the session
     * instance. If no interface is given it uses the session instances class
     * name.
     *
     * @param   \stubbles\ioc\binding\Session  $session
     * @param   string                         $sessionInterface  optional
     * @return  \stubbles\ioc\Injector
     * @since   5.4.0
     */
    public function setSession(Session $session, $sessionInterface = null)
    {
        $this->scopes->setSession($session);
        $binding = $this->bind(null !== $sessionInterface ? $sessionInterface : get_class($session))
                ->toInstance($session);
        $this->index[$binding->getKey()] = $binding;
        return $this;
    }

    /**
     * check whether a binding for a type is available (explicit and implicit)
     *
     * @api
     * @param   string   $type
     * @param   string   $name
     * @return  boolean
     */
    public function hasBinding($type, $name = null)
    {
        if (PropertyBinding::TYPE === $type) {
            return $this->hasProperty($name);
        }

        return ($this->findBinding($type, $name) != null);
    }

    /**
     * checks whether property with given name is available
     *
     * @param   string  $name
     * @return  bool
     * @since   3.4.0
     */
    private function hasProperty($name)
    {
        if (!isset($this->index[PropertyBinding::TYPE])) {
            return false;
        }

        return $this->index[PropertyBinding::TYPE]->hasProperty($name);
    }

    /**
     * check whether an excplicit binding for a type is available
     *
     * Be aware that implicit bindings turn into explicit bindings when
     * hasBinding() or getInstance() are called.
     *
     * @api
     * @param   string   $type
     * @param   string   $name
     * @return  boolean
     */
    public function hasExplicitBinding($type, $name = null)
    {
        if (PropertyBinding::TYPE === $type) {
            return $this->hasProperty($name);
        }

        $bindingName = $this->bindingName($name);
        if (null !== $bindingName && isset($this->index[$type . '#' . $bindingName])) {
            return true;
        }

        if (isset($this->index[$type])) {
            return true;
        }

        return false;
    }

    /**
     * get an instance
     *
     * @api
     * @param   string  $type
     * @param   string  $name
     * @return  object
     */
    public function getInstance($type, $name = null)
    {
        if (__CLASS__ === $type) {
            return $this;
        }

        array_push($this->injectionStack, $type . '#' . $name);
        $instance = $this->getBinding($type, $name)->getInstance($this, $name);
        array_pop($this->injectionStack);
        return $instance;
    }

    /**
     * returns how deep in the object graph the current injection takes place
     *
     * @return  string[]
     */
    public function stack()
    {
        return $this->injectionStack;
    }

    /**
     * check whether a constant is available
     *
     * @api
     * @param   string  $name  name of constant to check for
     * @return  bool
     * @since   1.1.0
     */
    public function hasConstant($name)
    {
        return $this->hasBinding(ConstantBinding::TYPE, $name);
    }

    /**
     * returns constanct value
     *
     * @api
     * @param   string  $name  name of constant value to retrieve
     * @return  scalar
     * @since   1.1.0
     */
    public function getConstant($name)
    {
        return $this->getBinding(ConstantBinding::TYPE, $name)
                    ->getInstance($this, $name);
    }

    /**
     * gets a binding
     *
     * @param   string  $type
     * @param   string  $name
     * @return  \stubbles\ioc\binding\Binding
     * @throws  \stubbles\ioc\binding\BindingException
     */
    private function getBinding($type, $name = null)
    {
        $binding = $this->findBinding($type, $name);
        if (null === $binding) {
            throw new BindingException('No binding for ' . $type . ' defined');
        }

        return $binding;
    }

    /**
     * tries to find a binding
     *
     * @param   string  $type
     * @param   string  $name
     * @return  \stubbles\ioc\binding\Binding
     */
    private function findBinding($type, $name)
    {
        $bindingName = $this->bindingName($name);
        if (null !== $bindingName && isset($this->index[$type . '#' . $bindingName])) {
            return $this->index[$type . '#' . $bindingName];
        }

        if (isset($this->index[$type])) {
            return $this->index[$type];
        }

        if (!in_array($type, [PropertyBinding::TYPE, ConstantBinding::TYPE, ListBinding::TYPE, MapBinding::TYPE])) {
            $this->index[$type] = $this->getAnnotatedBinding(new \ReflectionClass($type));
            return $this->index[$type];
        }

        return null;
    }

    /**
     * parses binding name from given name
     *
     * @param   string|\ReflectionClass  $name
     * @return  string
     */
    private function bindingName($name)
    {
        if ($name instanceof \ReflectionClass) {
            return $name->getName();
        }

        return $name;
    }

    /**
     * returns binding denoted by annotations on type to create
     *
     * An annotated binding is when the type to create is annotated with
     * @ImplementedBy oder @ProvidedBy.
     *
     * If this is not the case it will fall back to the implicit binding.
     *
     * @param   \ReflectionClass  $class
     * @return  \stubbles\ioc\binding\Binding
     */
    private function getAnnotatedBinding(\ReflectionClass $class)
    {
        $annotations = annotationsOf($class);
        if ($class->isInterface() && $annotations->contain('ImplementedBy')) {
            return $this->bind($class->getName())
                    ->to($this->findImplementation($annotations, $class->getName()));
        } elseif ($annotations->contain('ProvidedBy')) {
            return $this->bind($class->getName())
                    ->toProviderClass(
                        $annotations->firstNamed('ProvidedBy')
                                ->getProviderClass()
                    );
        }

        return $this->getImplicitBinding($class);
    }

    /**
     * finds implementation to be used from list of @ImplementedBy annotations
     *
     * @param   \stubbles\reflect\annotation\Annotations  $annotations
     * @param   string                                         $type
     * @return  \ReflectionClass
     * @throws  \stubbles\ioc\binding\BindingException
     */
    private function findImplementation(Annotations $annotations, $type)
    {
        $implementation = null;
        foreach ($annotations->named('ImplementedBy') as $annotation) {
            /* @var $annotation \stubbles\reflect\annotation\Annotation */
            if (null !== $this->environment && $annotation->hasValueByName('mode') && strtoupper($annotation->getMode()) === $this->environment) {
                $implementation = $annotation->getClass();
            } elseif (!$annotation->hasValueByName('mode') && null == $implementation) {
                $implementation = $annotation->getClass();
            }
        }

        if (null === $implementation) {
            throw new BindingException('Interface ' . $type . ' annotated with @ImplementedBy, but no default found');
        }

        return $implementation;
    }

    /**
     * returns implicit binding
     *
     * An implicit binding means that a type is requested which itself is a class
     * and not an interface. Obviously, it makes sense to say that a class is
     * always bound to itself if no other bindings were defined.
     *
     * @param   \ReflectionClass  $class
     * @return  \stubbles\ioc\binding\Binding
     */
    private function getImplicitBinding(\ReflectionClass $class)
    {
        if (!$class->isInterface()) {
            return $this->bind($class->getName())->to($class);
        }

        return null;
    }

    /**
     * creates a class binding
     *
     * @param   string  $classname
     * @return  \stubbles\ioc\binding\ClassBinding
     */
    private function bind($classname)
    {
        return new ClassBinding($classname, $this->scopes);
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles;
use stubbles\ioc\Binder;
use stubbles\ioc\module\BindingModule;
/**
 * Application base class.
 */
abstract class App
{
    /**
     * runs the application
     *
     * @api
     * @since  2.0.0
     */
    public abstract function run();

    /**
     * creates an object via injection
     *
     * If the class to create an instance of contains a static __bindings() method
     * this method will be used to configure the ioc bindings before using the ioc
     * container to create the instance.
     *
     * @api
     * @param   string  $projectPath  path to project
     * @return  \stubbles\App
     */
    public static function create($projectPath)
    {
        return self::createInstance(get_called_class(), $projectPath);
    }

    /**
     * project path, set when instance is created
     *
     * @type  string
     */
    private static $projectPath;

    /**
     * returns current project path
     *
     * Should only be used during app initialization, but never at runtime.
     *
     * @since   5.0.0
     * @return  string
     */
    protected static function projectPath()
    {
        return self::$projectPath;
    }

    /**
     * creates an object via injection
     *
     * If the class to create an instance of contains a static __bindings() method
     * this method will be used to configure the ioc bindings before using the ioc
     * container to create the instance.
     *
     * @api
     * @param   string  $className    full qualified class name of class to create an instance of
     * @param   string  $projectPath  path to project
     * @return  \stubbles\App
     */
    public static function createInstance($className, $projectPath)
    {
        Runtime::reset();
        self::$projectPath = $projectPath;
        $binder = new Binder();
        foreach (static::getBindingsForApp($className) as $bindingModule) {
            if (is_string($bindingModule)) {
                $bindingModule = new $bindingModule();
            }

            if ($bindingModule instanceof BindingModule) {
                $bindingModule->configure($binder, $projectPath);
            } elseif ($bindingModule instanceof \Closure) {
                $bindingModule($binder, $projectPath);
            } else {
                throw new \InvalidArgumentException(
                        'Given module class ' . get_class($bindingModule)
                        . ' is not an instance of stubbles\ioc\module\BindingModule'
                );
            }
        }

        return $binder->getInjector()->getInstance($className);
    }

    /**
     * creates list of bindings from given class
     *
     * @internal  must not be used by applications
     * @param   string  $className    full qualified class name of class to create an instance of
     * @param   string  $projectPath  path to project
     * @return  \stubbles\ioc\module\BindingModule[]
     * @since   1.3.0
     */
    protected static function getBindingsForApp($className)
    {
        $bindings = method_exists($className, '__bindings') ? $className::__bindings() : [];
        if (!Runtime::initialized()) {
            $bindings[] = static::runtime();
        }

        return $bindings;
    }

    /**
     * creates mode binding module
     *
     * @api
     * @param   \stubbles\Environment|callable  $environment  optional  current environment
     * @return  \stubbles\Runtime
     * @since   2.0.0
     */
    protected static function runtime($environment = null)
    {
        return new Runtime($environment);
    }

    /**
     * create a binding module which binds current working directory
     *
     * @api
     * @return  \Closure
     */
    protected static function currentWorkingDirectory()
    {
        return function(Binder $binder)
        {
            $binder->bindConstant('stubbles.cwd')
                   ->to(getcwd());
        };
    }

    /**
     * create a binding module which binds current hostnames
     *
     * @api
     * @return  \Closure
     */
    protected static function hostname()
    {
        return function(Binder $binder)
        {
            if (DIRECTORY_SEPARATOR === '\\') {
                $fq = php_uname('n');
                if (isset($_SERVER['USERDNSDOMAIN'])) {
                    $fq .= '.' . $_SERVER['USERDNSDOMAIN'];
                }
            } else {
                $fq = exec('hostname -f');
            }

            $binder->bindConstant('stubbles.hostname.nq')
                   ->to(php_uname('n'));
            $binder->bindConstant('stubbles.hostname.fq')
                   ->to($fq);
        };
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles\predicate;
/**
 * Evaluates if a given value fulfills a criteria.
 *
 * @api
 * @since  4.0.0
 * @method  \stubbles\predicate\Predicate  and(\stubbles\predicate\Predicate|callable $predicate)
 * @method  \stubbles\predicate\Predicate  or(\stubbles\predicate\Predicate|callable $predicate)
 * @deprecated  since 7.0.0, will be removed with 8.0.0
 */
abstract class Predicate
{
    /**
     * casts given predicate to a predicate instance
     *
     * @param   \stubbles\predicate\Predicate|callable  $predicate
     * @return  \stubbles\predicate\Predicate
     * @throws  \InvalidArgumentException
     */
    public static function castFrom($predicate)
    {
        if ($predicate instanceof self) {
            return $predicate;
        } elseif (is_callable($predicate)) {
            return new CallablePredicate($predicate);
        }

        throw new \InvalidArgumentException(
                'Given predicate is neither a callable nor an instance of ' . __CLASS__
        );
    }

    /**
     * evaluates predicate against given value
     *
     * @param   mixed  $value
     * @return  bool
     */
    public abstract function test($value);

    /**
     * evaluates predicate against given value
     *
     * @param   mixed  $value
     * @return  bool
     */
    public function __invoke($value)
    {
        return $this->test($value);
    }

    /**
     * combines this with another predicate to a predicate which requires both to be true
     *
     * @param   \stubbles\predicate\Predicate|callable  $other
     * @return  \stubbles\predicate\Predicate
     * @deprecated  since 7.0.0, use or($other) instead, will be removed with 8.0.0
     */
    public function asWellAs($other)
    {
        return new AndPredicate($this, self::castFrom($other));
    }

    /**
     * combines this with another predicate to a predicate which requires on of them to be true
     *
     * @param   \stubbles\predicate\Predicate|callable  $other
     * @return  \stubbles\predicate\Predicate
     * @deprecated  since 7.0.0, use or($other) instead, will be removed with 8.0.0
     */
    public function orElse($other)
    {
        return new OrPredicate($this, self::castFrom($other));
    }

    /**
     * provide utility methods "and" and "or" to combine predicates
     *
     * @param   string   $method
     * @param   mixed[]  $arguments
     * @return  \stubbles\predicate\Predicate
     * @throws  \BadMethodCallException
     * @since   1.4.0
     */
    public function __call($method, $arguments)
    {
        switch ($method) {
            case 'and':
                return $this->asWellAs(...$arguments);

            case 'or':
                return $this->orElse(...$arguments);

            default:
                throw new \BadMethodCallException(
                        'Call to undefined method '
                        . get_class($this) . '->' . $method . '()'
                );
        }
    }

    /**
     * returns a negated version of this predicate
     *
     * @return  \stubbles\predicate\Predicate
     */
    public function negate()
    {
        return new NegatePredicate($this);
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles\ioc\binding;
use stubbles\ioc\ClosureInjectionProvider;
use stubbles\ioc\InjectionProvider;
use stubbles\ioc\Injector;
/**
 * Binding to bind a property to a constant value.
 */
class ConstantBinding implements Binding
{
    /**
     * This string is used when generating the key for a constant binding.
     */
    const TYPE             = '__CONSTANT__';
    /**
     * annotated with a name
     *
     * @type  string
     */
    private $name          = null;
    /**
     * value to provide
     *
     * @type  mixed
     */
    private $value;
    /**
     * provider to use for this binding
     *
     * @type  \stubbles\ioc\InjectionProvider
     */
    private $provider      = null;
    /**
     * provider class to use for this binding (will be created via injector)
     *
     * @type  string
     */
    private $providerClass = null;

    /**
     * constructor
     *
     * @param  string  $name  name of the list or map
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * set the constant value
     *
     * @api
     * @param   mixed  $value
     * @return  ConstantBinding
     */
    public function to($value)
    {
        $this->value = $value;
        return $this;
    }

    /**
     * set the provider that should be used to create instances for this binding
     *
     * This cannot be used in conjuction with the 'to()' or
     * 'toProviderClass()' method.
     *
     * @api
     * @param   \stubbles\ioc\InjectionProvider  $provider
     * @return  \stubbles\ioc\binding\ConstantBinding
     * @since   1.6.0
     */
    public function toProvider(InjectionProvider $provider)
    {
        $this->provider = $provider;
        return $this;
    }

    /**
     * set the provider class that should be used to create instances for this binding
     *
     * This cannot be used in conjuction with the 'to()' or
     * 'toProvider()' method.
     *
     * @api
     * @param   string|\ReflectionClass  $providerClass
     * @return  \stubbles\ioc\binding\ConstantBinding
     * @since   1.6.0
     */
    public function toProviderClass($providerClass)
    {
        $this->providerClass = (($providerClass instanceof \ReflectionClass) ?
                                    ($providerClass->getName()) : ($providerClass));
        return $this;
    }

    /**
     * sets a closure which can create the instance
     *
     * @api
     * @param   \Closure  $closure
     * @return  \stubbles\ioc\binding\ConstantBinding
     * @since   2.1.0
     */
    public function toClosure(\Closure $closure)
    {
        $this->provider = new ClosureInjectionProvider($closure);
        return $this;
    }

    /**
     * creates a unique key for this binding
     *
     * @return  string
     */
    public function getKey()
    {
        return self::TYPE . '#' . $this->name;
    }

    /**
     * returns the created instance
     *
     * @param   \stubbles\ioc\Injector  $injector
     * @param   string                  $name
     * @return  mixed
     */
    public function getInstance(Injector $injector, $name)
    {
        if (null !== $this->provider) {
            return $this->provider->get($name);
        }

        if (null != $this->providerClass) {
            $provider = $injector->getInstance($this->providerClass);
            if (!($provider instanceof InjectionProvider)) {
                 throw new BindingException('Configured provider class ' . $this->providerClass . ' for constant ' . $this->name . ' is not an instance of stubbles\ioc\InjectionProvider.');
            }

            $this->provider = $provider;
            return $this->provider->get($name);
        }

        return $this->value;
    }
}


<?php
/**
 * This file is part of stubbles.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package  stubbles
 */
namespace stubbles\ioc\binding;
/**
 * All built-in scopes.
 *
 * @internal
 */
class BindingScopes
{
    /**
     * scope for singleton objects
     *
     * @type  \stubbles\ioc\binding\BindingScope
     */
    private $singletonScope;
    /**
     * scope for session resources
     *
     * @type  \stubbles\ioc\binding\BindingScope
     */
    private $sessionScope;

    /**
     * constructor
     *
     * @param  \stubbles\ioc\binding\BindingScope  $singletonScope
     * @param  \stubbles\ioc\binding\BindingScope  $sessionScope
     * @since  1.5.0
     */
    public function  __construct(BindingScope $singletonScope = null, BindingScope $sessionScope = null)
    {
        $this->singletonScope = ((null === $singletonScope) ? (new SingletonBindingScope()) : ($singletonScope));
        $this->sessionScope   = ((null === $sessionScope) ? (new SessionBindingScope()) : ($sessionScope));
    }

    /**
     * returns scope for singleton objects
     *
     * @return  \stubbles\ioc\binding\BindingScope
     * @since   1.5.0
     */
    public function singleton()
    {
        return $this->singletonScope;
    }

    /**
     * sets the session for the session scope in case it is the built-in implementation
     *
     * @param   \stubbles\ioc\binding\Session  $session
     * @return  \stubbles\ioc\binding\BindingScopes
     * @throws  \RuntimeException  in case the session scope has been replaced with another implementation
     * @since   5.4.0
     */
    public function setSession(Session $session)
    {
        if ($this->sessionScope instanceof SessionBindingScope) {
            $this->sessionScope->setSession($session);
            return $this;
        }

        throw new \RuntimeException('Can not set session for session scope implementation ' . get_class($this->sessionScope));
    }

    /**
     * returns scope for session resources
     *
     * @return  \stubbles\ioc\binding\BindingScope
     * @since   1.5.0
     */
    public function session()
    {
        return $this->sessionScope;
    }
}