<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\Generator\ParameterListMerge;

use Eloquent\Typhax\Comparator\TypeEquivalenceComparator;
use Eloquent\Typhax\Type\AndType;
use Eloquent\Typhax\Type\ArrayType;
use Eloquent\Typhax\Type\CallableType;
use Eloquent\Typhax\Type\MixedType;
use Eloquent\Typhax\Type\NullType;
use Eloquent\Typhax\Type\ObjectType;
use Eloquent\Typhax\Type\OrType;
use Eloquent\Typhax\Type\TraversableType;
use Eloquent\Typhax\Type\TupleType;
use Eloquent\Typhax\Type\Type;
use Eloquent\Typhoon\ClassMapper\ClassDefinition;
use Eloquent\Typhoon\ClassMapper\MethodDefinition;
use Eloquent\Typhoon\CodeAnalysis\Issue\IssueInterface;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\DefinedParameterVariableLength;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\DocumentedParameterByReferenceMismatch;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\DocumentedParameterNameMismatch;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\DocumentedParameterTypeMismatch;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\DocumentedParameterUndefined;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue\UndocumentedParameter;
use Eloquent\Typhoon\Configuration\RuntimeConfiguration;
use Eloquent\Typhoon\Generator\NullifiedType;
use Eloquent\Typhoon\Parameter\Parameter;
use Eloquent\Typhoon\Parameter\ParameterList;
use Eloquent\Typhoon\TypeCheck\TypeCheck;
use ReflectionClass;

class MergeTool
{
    /**
     * @param boolean $throwOnError
     */
    public function __construct($throwOnError = true)
    {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());

        $this->throwOnError = $throwOnError;
        $this->clearIssues();

        $reflectionParameterClass = new ReflectionClass('ReflectionParameter');
        $this->nativeCallableAvailable =
            $reflectionParameterClass->hasMethod('isCallable')
        ;
    }

    /**
     * @return boolean
     */
    public function throwOnError()
    {
        $this->typeCheck->throwOnError(func_get_args());

        return $this->throwOnError;
    }

    public function clearIssues()
    {
        $this->typeCheck->clearIssues(func_get_args());

        $this->issues = array();
    }

    /**
     * @return array<IssueInterface>
     */
    public function issues()
    {
        $this->typeCheck->issues(func_get_args());

        return $this->issues;
    }

    /**
     * @return boolean
     */
    public function nativeCallableAvailable()
    {
        $this->typeCheck->nativeCallableAvailable(func_get_args());

        return $this->nativeCallableAvailable;
    }

    /**
     * @param RuntimeConfiguration $configuration
     *
     * @return boolean
     */
    public function useNativeCallable(RuntimeConfiguration $configuration)
    {
        $this->typeCheck->useNativeCallable(func_get_args());

        return
            $configuration->useNativeCallable() &&
            $this->nativeCallableAvailable()
        ;
    }

    /**
     * @param RuntimeConfiguration $configuration
     * @param ClassDefinition|null $classDefinition
     * @param MethodDefinition     $methodDefinition
     * @param ParameterList        $documentedParameterList
     * @param ParameterList        $nativeParameterList
     *
     * @return ParameterList
     */
    public function merge(
        RuntimeConfiguration $configuration,
        ClassDefinition $classDefinition = null,
        MethodDefinition $methodDefinition,
        ParameterList $documentedParameterList,
        ParameterList $nativeParameterList
    ) {
        $this->typeCheck->merge(func_get_args());

        $documentedParameters = $documentedParameterList->parameters();
        $nativeParameters = $nativeParameterList->parameters();

        $parameters = array();
        foreach ($nativeParameters as $index => $nativeParameter) {
            if (array_key_exists($index, $documentedParameters)) {
                $parameters[] = $this->mergeParameter(
                    $configuration,
                    $classDefinition,
                    $methodDefinition,
                    $documentedParameters[$index],
                    $nativeParameter
                );
            } else {
                $this->handleError(new UndocumentedParameter(
                    $classDefinition,
                    $methodDefinition,
                    $nativeParameter->name()
                ));

                $parameters[] = $nativeParameter;
            }
        }

        $documentedParameterCount = count($documentedParameters);
        $nativeParameterCount = count($nativeParameters);

        $isVariableLength = false;
        if ($documentedParameterList->isVariableLength()) {
            if ($documentedParameterCount > $nativeParameterCount + 1) {
                $this->handleError(new DocumentedParameterUndefined(
                    $classDefinition,
                    $methodDefinition,
                    $documentedParameters[$nativeParameterCount + 1]->name()
                ));
            } elseif ($documentedParameterCount === $nativeParameterCount) {
                $this->handleError(new DefinedParameterVariableLength(
                    $classDefinition,
                    $methodDefinition,
                    $nativeParameters[$nativeParameterCount - 1]->name()
                ));
            } elseif ($documentedParameterCount === $nativeParameterCount + 1) {
                $isVariableLength = true;
                $parameters[] = $documentedParameters[$nativeParameterCount];
            }
        } elseif ($documentedParameterCount > $nativeParameterCount) {
            for ($i = $nativeParameterCount; $i < $documentedParameterCount; $i ++) {
                $this->handleError(new DocumentedParameterUndefined(
                    $classDefinition,
                    $methodDefinition,
                    $documentedParameters[$i]->name()
                ));
            }
        }

        return new ParameterList($parameters, $isVariableLength);
    }

    /**
     * @param RuntimeConfiguration $configuration
     * @param ClassDefinition|null $classDefinition
     * @param MethodDefinition     $methodDefinition
     * @param Parameter            $documentedParameter
     * @param Parameter            $nativeParameter
     *
     * @return Parameter
     */
    protected function mergeParameter(
        RuntimeConfiguration $configuration,
        ClassDefinition $classDefinition = null,
        MethodDefinition $methodDefinition,
        Parameter $documentedParameter,
        Parameter $nativeParameter
    ) {
        $this->typeCheck->mergeParameter(func_get_args());

        if ($documentedParameter->name() !== $nativeParameter->name()) {
            $this->handleError(new DocumentedParameterNameMismatch(
                $classDefinition,
                $methodDefinition,
                $nativeParameter->name(),
                $documentedParameter->name()
            ));

            return $nativeParameter;
        }

        if ($documentedParameter->isByReference() !== $nativeParameter->isByReference()) {
            $this->handleError(new DocumentedParameterByReferenceMismatch(
                $classDefinition,
                $methodDefinition,
                $nativeParameter->name(),
                $nativeParameter->isByReference()
            ));
        }

        return new Parameter(
            $documentedParameter->name(),
            $this->mergeType(
                $configuration,
                $classDefinition,
                $methodDefinition,
                $documentedParameter->name(),
                $documentedParameter->type(),
                $nativeParameter->type()
            ),
            $documentedParameter->description(),
            $nativeParameter->isOptional(),
            $documentedParameter->isByReference()
        );
    }

    /**
     * @param RuntimeConfiguration $configuration
     * @param ClassDefinition|null $classDefinition
     * @param MethodDefinition     $methodDefinition
     * @param string               $parameterName
     * @param Type                 $documentedType
     * @param Type                 $nativeType
     *
     * @return Type
     */
    protected function mergeType(
        RuntimeConfiguration $configuration,
        ClassDefinition $classDefinition = null,
        MethodDefinition $methodDefinition,
        $parameterName,
        Type $documentedType,
        Type $nativeType
    ) {
        $this->typeCheck->mergeType(func_get_args());

        if (!$this->typeIsCompatible(
            $configuration,
            $documentedType,
            $nativeType
        )) {
            $this->handleError(new DocumentedParameterTypeMismatch(
                $classDefinition,
                $methodDefinition,
                $parameterName,
                $nativeType,
                $documentedType
            ));

            return $nativeType;
        }

        if (TypeEquivalenceComparator::equivalent($documentedType, $nativeType)) {
            return new NullifiedType($documentedType);
        }

        return $documentedType;
    }

    /**
     * @param RuntimeConfiguration $configuration
     * @param Type                 $documentedType
     * @param Type                 $nativeType
     * @param integer              $depth
     *
     * @return boolean
     */
    protected function typeIsCompatible(
        RuntimeConfiguration $configuration,
        Type $documentedType,
        Type $nativeType,
        $depth = 0
    ) {
        $this->typeCheck->typeIsCompatible(func_get_args());

        // callable
        if (
            $this->useNativeCallable($configuration) &&
            $documentedType instanceof CallableType
        ) {
            return $nativeType instanceof CallableType;
        }

        // null
        if ($documentedType instanceof NullType) {
            if ($depth < 1) {
                return $nativeType instanceof MixedType;
            }

            return $nativeType instanceof NullType;
        }

        // traversable
        if ($documentedType instanceof TraversableType) {
            return
                !$documentedType->primaryType() instanceof ArrayType ||
                $nativeType instanceof TraversableType
            ;
        }

        // tuple
        if ($documentedType instanceof TupleType) {
            return $nativeType instanceof TraversableType;
        }

        // object of type
        if (
            $documentedType instanceof ObjectType &&
            null !== $documentedType->ofType()
        ) {
            if (!$nativeType instanceof ObjectType) {
                return false;
            }
            if ($nativeType->ofType()->isRuntimeEquivalentTo($documentedType->ofType())) {
                return true;
            }

            $documentedClassReflector = new ReflectionClass(
                $documentedType->ofType()->string()
            );

            return $documentedClassReflector->isSubclassOf(
                $nativeType->ofType()->string()
            );
        }

        // or type
        if ($documentedType instanceof OrType) {
            if ($nativeType instanceof OrType) {
                foreach ($documentedType->types() as $documentedSubType) {
                    $compatible = false;
                    foreach ($nativeType->types() as $nativeSubType) {
                        $compatible = $this->typeIsCompatible(
                            $configuration,
                            $documentedSubType,
                            $nativeSubType,
                            $depth + 1
                        );
                        if ($compatible) {
                            break;
                        }
                    }
                    if (!$compatible) {
                        return false;
                    }
                }

                return $compatible;
            }

            if (!$nativeType instanceof MixedType) {
                return false;
            }

            $hasArray = false;
            $hasCallable = false;
            $hasNull = false;
            $hasObjectOfType = false;
            $impossibleNatively = false;
            foreach ($documentedType->types() as $documentedSubType) {
                if (
                    $documentedSubType instanceof TraversableType &&
                    $documentedSubType->primaryType() instanceof ArrayType
                ) {
                    $hasArray = true;
                } elseif ($documentedSubType instanceof CallableType) {
                    $hasCallable = true;
                } elseif ($documentedSubType instanceof NullType) {
                    $hasNull = true;
                } elseif (
                    $documentedSubType instanceof ObjectType &&
                    null !== $documentedSubType->ofType()
                ) {
                    $hasObjectOfType = true;
                } else {
                    return true;
                }
            }

            return
                (!$this->useNativeCallable($configuration) && $hasCallable) ||
                ($hasArray && $hasCallable) ||
                ($hasArray && $hasObjectOfType) ||
                ($hasCallable && $hasObjectOfType)
            ;
        }

        // and type
        if ($documentedType instanceof AndType) {
            foreach ($documentedType->types() as $documentedSubType) {
                $compatible = $this->typeIsCompatible(
                    $configuration,
                    $documentedSubType,
                    $nativeType,
                    $depth + 1
                );
                if ($compatible) {
                    return true;
                }
            }

            return false;
        }

        return $nativeType instanceof MixedType;
    }

    /**
     * @param IssueInterface $error
     */
    protected function handleError(IssueInterface $error)
    {
        $this->typeCheck->handleError(func_get_args());

        if ($this->throwOnError()) {
            throw new Exception\ParameterListMergeException($error);
        }

        $this->issues[] = $error;
    }

    private $throwOnError;
    private $issues;
    private $nativeCallableAvailable;
    private $typeCheck;
}


<?php declare(strict_types=1);

/**
 * @copyright (c) 2015-present brian ridley
 * @author      brian ridley <ptlis@ptlis.net>
 * @license     http://opensource.org/licenses/MIT MIT
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace ptlis\ShellCommand\Logger;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use ptlis\ShellCommand\Interfaces\ProcessObserverInterface;
use ptlis\ShellCommand\Interfaces\ProcessOutputInterface;

/**
 * Logs all information about the process.
 */
final class AllLogger implements ProcessObserverInterface
{
    /** @var LoggerInterface */
    private $logger;

    /** @var string */
    private $logLevel;

    public function __construct(
        LoggerInterface $logger,
        string $logLevel = LogLevel::DEBUG
    ) {
        $this->logger = $logger;
        $this->logLevel = $logLevel;
    }

    public function processCreated(int $pid, string $command): void
    {
        $this->log('Process created', ['pid' => $pid, 'command' => $command]);
    }

    public function processPolled(int $pid, int $runningTime): void
    {
        $this->log('Process polled', ['pid' => $pid, 'running time' => $runningTime]);
    }

    public function stdOutRead(int $pid, string $stdOut): void
    {
        $this->log('Read from stdout', ['pid' => $pid, 'stdout' => $stdOut]);
    }

    public function stdErrRead(int $pid, string $stdErr): void
    {
        $this->log('Read from stderr', ['pid' => $pid, 'stderr' => $stdErr]);
    }

    public function sentSignal(int $pid, string $signal): void
    {
        $this->log('Signal sent', ['pid' => $pid, 'signal' => $signal]);
    }

    public function processExited(int $pid, ProcessOutputInterface $processOutput): void
    {
        $this->log('Process exited', ['pid' => $pid, 'exit_code' => $processOutput->getExitCode()]);
    }

    /**
     * Create a log entry, utility method for derived classes.
     *
     * @param string $message
     * @param array $context
     */
    private function log(string $message, array $context = []): void
    {
        $this->logger->log($this->logLevel, $message, $context);
    }
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\Parser;

use Eloquent\Cosmos\ClassName;
use Eloquent\Blox\AST\DocumentationBlock;
use Eloquent\Blox\AST\DocumentationTag;
use Eloquent\Blox\AST\Visitor;
use Eloquent\Blox\BloxParser;
use Eloquent\Blox\DocumentationBlockParser;
use Eloquent\Typhax\Parser\Parser as TyphaxParser;
use Eloquent\Typhax\Parser\Exception\UnexpectedTokenException;
use Eloquent\Typhax\Type\ArrayType;
use Eloquent\Typhax\Type\CallableType;
use Eloquent\Typhax\Type\MixedType;
use Eloquent\Typhax\Type\NullType;
use Eloquent\Typhax\Type\ObjectType;
use Eloquent\Typhax\Type\OrType;
use Eloquent\Typhax\Type\TraversableType;
use Eloquent\Typhax\Type\Type;
use Eloquent\Typhoon\Parameter\Parameter;
use Eloquent\Typhoon\Parameter\ParameterList;
use Eloquent\Typhoon\TypeCheck\TypeCheck;
use ReflectionException;
use ReflectionFunctionAbstract;
use ReflectionObject;
use ReflectionParameter;

class ParameterListParser implements Visitor
{
    /**
     * @param TyphaxParser|null $typhaxParser
     */
    public function __construct(TyphaxParser $typhaxParser = null)
    {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        if (null === $typhaxParser) {
            $typhaxParser = new TyphaxParser;
        }

        $this->typhaxParser = $typhaxParser;
    }

    /**
     * @return TyphaxParser
     */
    public function typhaxParser()
    {
        $this->typeCheck->typhaxParser(func_get_args());

        return $this->typhaxParser;
    }

    /**
     * @param ClassName|null                $className
     * @param string                        $functionName
     * @param string                        $blockComment
     * @param DocumentationBlockParser|null $documentationParser
     *
     * @return ParameterList
     */
    public function parseBlockComment(
        ClassName $className = null,
        $functionName,
        $blockComment,
        DocumentationBlockParser $documentationParser = null
    ) {
        $this->typeCheck->parseBlockComment(func_get_args());

        if (null === $documentationParser) {
            $documentationParser = new BloxParser;
        }

        try {
            $parameterList =
                $documentationParser
                ->parseBlockComment($blockComment)
                ->accept($this)
            ;
        } catch (Exception\ParseException $e) {
            throw new Exception\InvalidFunctionDocumentationException(
                $className,
                $functionName
            );
        }

        return $parameterList;
    }

    /**
     * @param ReflectionFunctionAbstract $reflector
     *
     * @return ParameterList
     */
    public function parseReflector(ReflectionFunctionAbstract $reflector)
    {
        $this->typeCheck->parseReflector(func_get_args());

        $parameters = array();
        foreach ($reflector->getParameters() as $parameterReflector) {
            $parameters[] = $this->parseParameterReflector($parameterReflector);
        }

        return new ParameterList($parameters);
    }

    /**
     * @param ReflectionParameter $reflector
     *
     * @return Parameter
     */
    public function parseParameterReflector(ReflectionParameter $reflector)
    {
        $this->typeCheck->parseParameterReflector(func_get_args());

        $class = null;
        try {
            $class = $reflector->getClass();
        } catch (ReflectionException $e) {
            $className = null;
            if ($declaringClass = $reflector->getDeclaringClass()) {
                $className = ClassName::fromString(
                    $declaringClass->getName()
                )->toAbsolute();
            }

            throw new Exception\TypeHintUndefinedClassException(
                $className,
                $reflector->getDeclaringFunction()->getName(),
                $reflector,
                $e
            );
        }

        if ($class) {
            $type = new ObjectType(ClassName::fromString($class->getName())->toAbsolute());
        } elseif ($reflector->isArray()) {
            $type = new TraversableType(
                new ArrayType,
                new MixedType,
                new MixedType
            );
        } else {
            $reflectorReflector = new ReflectionObject($reflector);
            if (
                $reflectorReflector->hasMethod('isCallable') &&
                $reflector->isCallable()
            ) {
                $type = new CallableType;
            } else {
                $type = new MixedType;
            }
        }

        if (
            !$type instanceof MixedType &&
            $reflector->allowsNull()
        ) {
            $type = new OrType(array(
                $type,
                new NullType,
            ));
        }

        return new Parameter(
            $reflector->getName(),
            $type,
            null,
            $reflector->isOptional(),
            $reflector->isPassedByReference()
        );
    }

    /**
     * @param DocumentationBlock $documentationBlock
     *
     * @return ParameterList
     */
    public function visitDocumentationBlock(DocumentationBlock $documentationBlock)
    {
        $this->typeCheck->visitDocumentationBlock(func_get_args());

        $parameters = array();
        $paramTags = $documentationBlock->tagsByName('param');
        $lastIndex = count($paramTags) - 1;
        $variableLength = false;
        foreach ($paramTags as $index => $paramTag) {
            $parameters[] = $paramTag->accept($this);
            if ($index === $lastIndex) {
                $variableLength = preg_match(
                    static::PATTERN_VARIABLE_LENGTH,
                    $paramTag->content()
                ) && true;
            }
        }

        return new ParameterList($parameters, $variableLength);
    }

    /**
     * @param DocumentationTag $documentationTag
     *
     * @return Parameter
     */
    public function visitDocumentationTag(DocumentationTag $documentationTag)
    {
        $this->typeCheck->visitDocumentationTag(func_get_args());
        $content = $documentationTag->content() ?: '';

        $position = 0;
        $type = $this->parseType(
            $content,
            $position
        );
        $byReference = $this->parseByReference(
            $content,
            $position
        );
        $name = $this->parseName(
            $content,
            $position
        );
        $description = $this->parseDescription(
            $content,
            $position
        );
        $optional = $this->parseOptional(
            $content
        );

        return new Parameter(
            $name,
            $type,
            $description,
            $optional,
            $byReference
        );
    }

    const PATTERN_VARIABLE_LENGTH = '/^.*\s&?\$\w+,\.{3}(?:$|\s)/';

    /**
     * @param string $content
     * @param integer &$position
     *
     * @return Type
     */
    protected function parseType($content, &$position)
    {
        $this->typeCheck->parseType(func_get_args());

        try {
            return $this->typhaxParser()->parse(
                $content,
                $position
            );
        } catch (UnexpectedTokenException $e) {
            throw new Exception\UnexpectedContentException(
                'type',
                $e->position(),
                $e
            );
        }
    }

    /**
     * @param string $content
     * @param integer &$position
     *
     * @return boolean
     */
    protected function parseByReference($content, &$position)
    {
        $this->typeCheck->parseByReference(func_get_args());

        return null !== $this->parseContent(
            $content,
            $position,
            '/^\s*(&)/',
            true,
            'byReference'
        );
    }

    /**
     * @param string $content
     * @param integer &$position
     *
     * @return string
     */
    protected function parseName($content, &$position)
    {
        $this->typeCheck->parseName(func_get_args());

        return $this->parseContent(
            $content,
            $position,
            '/^\s*\$(\w+)(?:,\.{3})?/',
            false,
            'name'
        );
    }

    /**
     * @param string $content
     * @param integer &$position
     *
     * @return string|null
     */
    protected function parseDescription($content, &$position)
    {
        $this->typeCheck->parseDescription(func_get_args());

        return $this->parseContent(
            $content,
            $position,
            '/^\s*(.*)$/',
            true,
            'description'
        );
    }

    /**
     * @param string $content
     *
     * @return boolean
     */
    protected function parseOptional($content)
    {
        $this->typeCheck->parseOptional(func_get_args());

        if (preg_match(static::PATTERN_VARIABLE_LENGTH, $content)) {
            return true;
        }

        return false;
    }

    /**
     * @param string $content
     * @param integer &$position
     * @param string  $pattern
     * @param boolean $optional
     * @param string  $type
     *
     * @return string|null
     */
    protected function parseContent($content, &$position, $pattern, $optional, $type)
    {
        $this->typeCheck->parseContent(func_get_args());

        $subject = substr($content, $position - 1);
        if (
            !preg_match($pattern, $subject, $matches) ||
            '' === $matches[1]
        ) {
            if ($optional) {
                return null;
            }

            throw new Exception\UnexpectedContentException(
                $type,
                $position
            );
        }

        $position += strlen($matches[0]);

        return $matches[1];
    }

    private $typhaxParser;
    private $typeCheck;
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssue;

use Eloquent\Typhoon\ClassMapper\ClassDefinition;
use Eloquent\Typhoon\ClassMapper\MethodDefinition;
use Eloquent\Typhoon\CodeAnalysis\Issue\AbstractParameterRelatedIssue;
use Eloquent\Typhoon\CodeAnalysis\Issue\IssueSeverity;
use Eloquent\Typhoon\CodeAnalysis\Issue\IssueVisitorInterface;
use Eloquent\Typhoon\CodeAnalysis\Issue\ParameterIssueInterface;
use Eloquent\Typhoon\TypeCheck\TypeCheck;

class DocumentedParameterByReferenceMismatch extends AbstractParameterRelatedIssue implements ParameterIssueInterface
{
    /**
     * @param ClassDefinition    $classDefinition
     * @param MethodDefinition   $methodDefinition
     * @param string             $parameterName
     * @param boolean            $isByReference
     * @param IssueSeverity|null $severity
     */
    public function __construct(
        ClassDefinition $classDefinition,
        MethodDefinition $methodDefinition,
        $parameterName,
        $isByReference,
        IssueSeverity $severity = null
    ) {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        parent::__construct(
            $classDefinition,
            $methodDefinition,
            $parameterName,
            $severity
        );

        $this->isByReference = $isByReference;
    }

    /**
     * @return boolean
     */
    public function isByReference()
    {
        $this->typeCheck->isByReference(func_get_args());

        return $this->isByReference;
    }

    /**
     * @param IssueVisitorInterface $visitor
     *
     * @return mixed
     */
    public function accept(IssueVisitorInterface $visitor)
    {
        $this->typeCheck->accept(func_get_args());

        return $visitor->visitDocumentedParameterByReferenceMismatch($this);
    }

    private $isByReference;
    private $typeCheck;
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\Extension;

use Eloquent\Typhoon\TypeCheck\TypeCheck;
use ReflectionClass;
use ReflectionException;

class ExtensionLoader implements ExtensionLoaderInterface
{
    /**
     * @param array $constructorArguments Arguments to forward to each extension's constructor.
     */
    public function __construct(array $constructorArguments = array())
    {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());

        $this->constructorArguments = $constructorArguments;
        $this->extensions = array();
    }

    /**
     * @param string  $className   The name of the extension class.
     * @param boolean $forceReload
     *
     * @return ExtensionInterface
     */
    public function load($className, $forceReload = false)
    {
        $this->typeCheck->load(func_get_args());

        // Extension is already loaded ...
        if (!$forceReload && $this->isLoaded($className)) {
            return $this->extensions[$className];
        }

        // Obtain a reflector ...
        try {
            $reflector = new ReflectionClass($className);
        } catch (ReflectionException $e) {
            throw new Exception\InvalidExtensionException($className, $e);
        }

        // Verify interface ...
        if (!$reflector->implementsInterface('Eloquent\Typhoon\Extension\ExtensionInterface')) {
            throw new Exception\InvalidExtensionException($className);
        }

        return $this->extensions[$className] = $reflector->newInstanceArgs($this->constructorArguments);
    }

    /**
     * @param string $className The name of the extension class.
     */
    public function unload($className)
    {
        $this->typeCheck->unload(func_get_args());

        unset($this->extensions[$className]);
    }

    /**
     * @param string $className The name of the extension class.
     *
     * @return boolean
     */
    public function isLoaded($className)
    {
        $this->typeCheck->isLoaded(func_get_args());

        return array_key_exists($className, $this->extensions);
    }

    private $constructorArguments;
    private $extensions;
    private $typeCheck;
}


<?php

namespace Harmony\Bundle\ThemeBundle;

use Harmony\Bundle\ThemeBundle\DependencyInjection\Compiler\ThemeCompilerPass;
use Harmony\Bundle\ThemeBundle\DependencyInjection\Compiler\ThemeProviderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
 * Class HarmonyThemeBundle
 *
 * @package Harmony\Bundle\ThemeBundle
 */
class HarmonyThemeBundle extends Bundle
{

    /** Constants */
    const THEMES_DIR = 'themes';

    /**
     * Builds the bundle.
     * It is only ever called once when the cache is empty.
     *
     * @param ContainerBuilder $container
     */
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new ThemeCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, - 10);
        $container->addCompilerPass(new ThemeProviderPass());
    }

}


<?php

/**
 * Blog Holder
 *
 * @package silverstripe
 * @subpackage blog
 *
 * @author Michael Strong <github@michaelstrong.co.uk>
**/
class Blog extends Page {

	private static $db = array(
		"PostsPerPage" => "Int",
	);

	private static $has_many = array(
		"Tags" => "BlogTag",
		"Categories" => "BlogCategory",
	);

	private static $allowed_children = array(
		"BlogPost",
	);

	private static $extensions = array(
		"BlogFilter",
	);

	private static $defaults = array(
		"ProvideComments" => false,
	);

	private static $description = 'Adds a blog to your website.';

	public function getCMSFields() {
		$self =& $this;
		$this->beforeUpdateCMSFields(function($fields) use ($self) {

			// Create categories and tag config
			$config = GridFieldConfig_RecordEditor::create();
			$config->removeComponentsByType("GridFieldAddNewButton");
			$config->addComponent(new GridFieldAddByDBField("buttons-before-left"));

			$categories = GridField::create(
				"Categories",
				_t("Blog.Categories", "Categories"),
				$self->Categories(),
				$config
			);

			$tags = GridField::create(
				"Tags",
				_t("Blog.Tags", "Tags"),
				$self->Tags(),
				$config
			);

			$fields->addFieldsToTab("Root.BlogOptions", array(
				$categories,
				$tags
			));

		});

		$fields = parent::getCMSFields();

		// Ensure we're using the BlogPost GridField config and not Lumberjack's
		$gridField = $fields->dataFieldByName('ChildPages');
		$gridField->setConfig(GridFieldConfig_BlogPost::create());

		return $fields;
	}

	public function getSettingsFields() {
		$fields = parent::getSettingsFields();
		$fields->addFieldToTab("Root.Settings",
			NumericField::create("PostsPerPage", _t("Blog.PostsPerPage", "Posts Per Page"))
		);
		return $fields;
	}

	/**
	 * Return blog posts
	 *
	 * @return DataList of BlogPost objects
	**/
	public function getBlogPosts() {
		$blogPosts = BlogPost::get()->filter("ParentID", $this->ID);
		//Allow decorators to manipulate list
		$this->extend('updateGetBlogPosts', $blogPosts);
		return $blogPosts;
	}

	/**
	 * Returns blogs posts for a given date period.
	 *
	 * @param $year int
	 * @param $month int
	 * @param $dat int
	 *
	 * @return DataList
	**/
	public function getArchivedBlogPosts($year, $month = null, $day = null) {
		$query = $this->getBlogPosts()->dataQuery();

		$stage = $query->getQueryParam("Versioned.stage");
		if($stage) $stage = '_' . Convert::raw2sql($stage);

		$query->innerJoin("BlogPost", "`SiteTree" . $stage . "`.`ID` = `BlogPost" . $stage . "`.`ID`");
		$query->where("YEAR(PublishDate) = '" . Convert::raw2sql($year) . "'");
		if($month) {
			$query->where("MONTH(PublishDate) = '" . Convert::raw2sql($month) . "'");
			if($day) {
				$query->where("DAY(PublishDate) = '" . Convert::raw2sql($day) . "'");
			}
		}

		return $this->getBlogPosts()->setDataQuery($query);
	}

	/**
	 * This sets the title for our gridfield
	 *
	 * @return string
	 */
	public function getLumberjackTitle() {
		return _t('Blog.LumberjackTitle', 'Blog Posts');
	}

}

/**
 * Blog Controller
 *
 * @package silverstripe
 * @subpackage blog
 *
 * @author Michael Strong <github@michaelstrong.co.uk>
**/
class Blog_Controller extends Page_Controller {

	private static $allowed_actions = array(
		'archive',
		'tag',
		'category',
		'rss',
	);

	private static $url_handlers = array(
		'tag/$Tag!' => 'tag',
		'category/$Category!' => 'category',
		'archive/$Year!/$Month/$Day' => 'archive',
	);

	/**
	 * The current Blog Post DataList query.
	 *
	 * @var DataList
	**/
	protected $blogPosts;

	public function index() {
		$this->blogPosts = $this->getBlogPosts();
		return $this->render();
	}

	/**
	 * Renders an archive for a specificed date. This can be by year or year/month
	 *
	 * @return SS_HTTPResponse
	**/
	public function archive() {
		$year = $this->getArchiveYear();
		$month = $this->getArchiveMonth();
		$day = $this->getArchiveDay();

		// If an invalid month has been passed, we can return a 404.
		if($this->request->param("Month") && !$month) {
			return $this->httpError(404, "Not Found");
		}

		// Check for valid day
		if($month && $this->request->param("Day") && !$day) {
			return $this->httpError(404, "Not Found");
		}

		if($year) {
			$this->blogPosts = $this->getArchivedBlogPosts($year, $month, $day);
			return $this->render();
		}
		return $this->httpError(404, "Not Found");
	}

	/**
	 * Renders the blog posts for a given tag.
	 *
	 * @return SS_HTTPResponse
	**/
	public function tag() {
		$tag = $this->getCurrentTag();
		if($tag) {
			$this->blogPosts = $tag->BlogPosts();
			return $this->render();
		}
		return $this->httpError(404, "Not Found");
	}

	/**
	 * Renders the blog posts for a given category
	 *
	 * @return SS_HTTPResponse
	**/
	public function category() {
		$category = $this->getCurrentCategory();
		if($category) {
			$this->blogPosts = $category->BlogPosts();
			return $this->render();
		}
		return $this->httpError(404, "Not Found");
	}

	/**
	 * Displays an RSS feed of blog posts
	 *
	 * @return string HTML
	**/
	public function rss() {
		$rss = new RSSFeed($this->getBlogPosts(), $this->Link(), $this->MetaTitle, $this->MetaDescription);
		$this->extend('updateRss', $rss);
		return $rss->outputToBrowser();
	}

	/**
	 * Returns a list of paginated blog posts based on the blogPost dataList
	 *
	 * @return PaginatedList
	**/
	public function PaginatedList() {
		$posts = new PaginatedList($this->blogPosts);

		// If pagination is set to '0' then no pagination will be shown.
		if($this->PostsPerPage > 0) $posts->setPageLength($this->PostsPerPage);
		else $posts->setPageLength($this->getBlogPosts()->count());

		$start = $this->request->getVar($posts->getPaginationGetVar());
		$posts->setPageStart($start);

		return $posts;
	}

	/**
	 * Tag Getter for use in templates.
	 *
	 * @return BlogTag|null
	**/
	public function getCurrentTag() {
		$tag = $this->request->param("Tag");
		if($tag) {
			return $this->dataRecord->Tags()
				->filter("URLSegment", $tag)
				->first();
		}
		return null;
	}

	/**
	 * Category Getter for use in templates.
	 *
	 * @return BlogCategory|null
	**/
	public function getCurrentCategory() {
		$category = $this->request->param("Category");
		if($category) {
			return $this->dataRecord->Categories()
				->filter("URLSegment", $category)
				->first();
		}
		return null;
	}

	/**
	 * Fetches the archive year from the url
	 *
	 * @return int|null
	**/
	public function getArchiveYear() {
		$year = $this->request->param("Year");
		if(preg_match("/^[0-9]{4}$/", $year)) {
			return (int) $year;
		}
		return null;
	}

	/**
	 * Fetches the archive money from the url.
	 *
	 * @return int|null
	**/
	public function getArchiveMonth() {
		$month = $this->request->param("Month");
		if(preg_match("/^[0-9]{1,2}$/", $month)) {
			if($month > 0 && $month < 13) {
				// Check that we have a valid date.
				if(checkdate($month, 01, $this->getArchiveYear())) {
					return (int) $month;
				}
			}
		}
		return null;
	}

	/**
	 * Fetches the archive day from the url
	 *
	 * @return int|null
	**/
	public function getArchiveDay() {
		$day = $this->request->param("Day");
		if(preg_match("/^[0-9]{1,2}$/", $day)) {

			// Check that we have a valid date
			if(checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) {
				return (int) $day;
			}
		}
		return null;
	}

	/**
	 * Returns the current archive date.
	 *
	 * @return Date
	**/
	public function getArchiveDate() {
		$year = $this->getArchiveYear();
		$month = $this->getArchiveMonth();
		$day = $this->getArchiveDay();

		if($year) {
			if($month) {
				$date = $year . '-' . $month . '-01';
				if($day) {
					$date = $year . '-' . $month . '-' . $day;
				}
			} else {
				$date = $year . '-01-01';
			}
			return DBField::create_field("Date", $date);
		}
	}

	/**
	 * Returns a link to the RSS feed.
	 *
	 * @return string URL
	**/
	public function getRSSLink() {
		return $this->Link("rss");
	}

}


<?php

/*
 * This file is part of the andreas-weber/php-runner library.
 *
 * (c) Andreas Weber <code@andreas-weber.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace AndreasWeber\Runner\Example;

use AndreasWeber\Runner\Payload\ArrayPayload;
use AndreasWeber\Runner\Payload\PayloadInterface;
use AndreasWeber\Runner\Task\AbstractTask;

class TaskExitCode extends AbstractTask
{
    /**
     * Executes the task.
     *
     * @param PayloadInterface $payload
     *
     * @return null|int The exit status code
     */
    public function run(PayloadInterface $payload)
    {
        /** @var ArrayPayload $payload */
        $payload->setData(
            array_merge(
                $payload->getData(),
                array(
                    __CLASS__ => true
                )
            )
        );

        return 1;
    }
}


<?php

namespace Harmony\Bundle\ThemeBundle\Twig;

use Harmony\Bundle\CoreBundle\Component\HttpKernel\AbstractKernel;
use Harmony\Bundle\ThemeBundle\HarmonyThemeBundle;
use Harmony\Bundle\SettingsManagerBundle\Settings\SettingsRouter;
use Liip\ThemeBundle\ActiveTheme;
use Symfony\Bridge\Twig\Extension\AssetExtension as BridgeAssetExtension;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpKernel\KernelInterface;
use Twig\Extension\GlobalsInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;

/**
 * Class AssetsExtension
 *
 * @package Harmony\Bundle\ThemeBundle\Twig
 */
class AssetsExtension extends BridgeAssetExtension implements GlobalsInterface
{

    /** @var ActiveTheme $activeTheme */
    protected $activeTheme;

    /** @var string $projectDir */
    protected $projectDir;

    /** @var KernelInterface|AbstractKernel $kernel */
    protected $kernel;

    /** @var SettingsRouter $settingsRouter */
    protected $settingsRouter;

    /**
     * AssetsExtension constructor.
     *
     * @param KernelInterface $kernel
     * @param Packages        $packages
     * @param ActiveTheme     $activeTheme
     * @param SettingsRouter  $settingsRouter
     * @param string          $projectDir
     */
    public function __construct(KernelInterface $kernel, Packages $packages, ActiveTheme $activeTheme,
                                SettingsRouter $settingsRouter, string $projectDir)
    {
        parent::__construct($packages);
        $this->kernel         = $kernel;
        $this->activeTheme    = $activeTheme;
        $this->projectDir     = $projectDir;
        $this->settingsRouter = $settingsRouter;
    }

    /**
     * Returns a list of functions to add to the existing list.
     *
     * @return TwigFunction[]
     */
    public function getFunctions(): array
    {
        return array_merge(parent::getFunctions(), [
            new TwigFunction('asset_theme', [$this, 'getAssetUrl'])
        ]);
    }

    /**
     * Returns a list of filters to add to the existing list.
     *
     * @return TwigFilter[]
     */
    public function getFilters(): array
    {
        return array_merge(parent::getFilters(), [
            new TwigFilter('theme', [$this, 'getThemeUrl'])
        ]);
    }

    /**
     * Returns a list of global variables to add to the existing list.
     *
     * @return array An array of global variables
     */
    public function getGlobals(): array
    {
        return [
            'harmony' => [
                'site_name' => $this->settingsRouter->get('site_name')
            ]
        ];
    }

    /**
     * Returns the public url/path of an asset.
     * If the package used to generate the path is an instance of
     * UrlPackage, you will always get a URL and not a path.
     *
     * @param string $path        A public path
     * @param string $packageName The name of the asset package to use
     *
     * @return string The public path of the asset
     */
    public function getAssetUrl($path, $packageName = null): string
    {
        if (null !== $theme = $this->kernel->getThemes()[$this->activeTheme->getName()] ?? null) {
            $parentTheme = $theme->getParent();

            $assetPath = sprintf('%s/%s/%s', HarmonyThemeBundle::THEMES_DIR, $theme->getShortName(), $path);

            // Asset exists in current active theme
            if (\file_exists(sprintf('%s/public/%s', $this->kernel->getProjectDir(), $assetPath))) {
                return parent::getAssetUrl($assetPath, $packageName);
            } // Has a parent theme
            elseif (null !== $parentTheme && \file_exists(sprintf('%s/public/%s', $this->kernel->getProjectDir(),
                    $parentAssetPath = sprintf('%s/%s/%s', HarmonyThemeBundle::THEMES_DIR, $parentTheme->getShortName(),
                        $path)))) {
                return parent::getAssetUrl($parentAssetPath, $packageName);
            }
        }

        return parent::getAssetUrl($path, $packageName);
    }
}


<?php

/*
 * This file is part of the andreas-weber/php-runner library.
 *
 * (c) Andreas Weber <code@andreas-weber.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace AndreasWeber\Runner\Task\Retries;

use AndreasWeber\Runner\Task\Retries\Exception\MaxRetriesExceededException;

class Retries
{
    /**
     * @var int Max allowed retries
     */
    private $maxRetries;

    /**
     * @var int Current retries
     */
    private $retries;

    /**
     * __construct()
     *
     * @param $maxRetries
     */
    public function __construct($maxRetries)
    {
        $this->maxRetries = (int)$maxRetries;
        $this->retries = 0;

        if ($this->maxRetries <= 0) {
            throw new \InvalidArgumentException('Max allowed retries count can\'t be negative or zero.');
        }
    }

    /**
     * Increases the retry counter by one.
     *
     * @return $this
     */
    public function increase()
    {
        $this->retries++;

        if ($this->retries > $this->maxRetries) {
            throw new MaxRetriesExceededException(
                sprintf(
                    'Max allowed retries exceeded. Allowed: %s. Tried: %s.',
                    $this->maxRetries,
                    $this->retries
                )
            );
        }

        return $this;
    }

    /**
     * Resets the retry counter.
     *
     * @return $this
     */
    public function reset()
    {
        $this->retries = 0;

        return $this;
    }

    /**
     * Returns the retry counter count.
     *
     * @return int
     */
    public function getRetries()
    {
        return $this->retries;
    }

    /**
     * Returns the max allowed retries.
     *
     * @return int
     */
    public function getMaxRetries()
    {
        return $this->maxRetries;
    }
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\CodeAnalysis\Issue\MethodIssue;

use Eloquent\Typhoon\CodeAnalysis\Issue\AbstractMethodRelatedIssue;
use Eloquent\Typhoon\CodeAnalysis\Issue\IssueVisitorInterface;
use Eloquent\Typhoon\CodeAnalysis\Issue\MethodIssueInterface;
use Eloquent\Typhoon\TypeCheck\TypeCheck;

class MissingMethodCall extends AbstractMethodRelatedIssue implements MethodIssueInterface
{
    /**
     * @param IssueVisitorInterface $visitor
     *
     * @return mixed
     */
    public function accept(IssueVisitorInterface $visitor)
    {
        TypeCheck::get(__CLASS__)->accept(func_get_args());

        return $visitor->visitMissingMethodCall($this);
    }
}


<?php

namespace Harmony\Bundle\ThemeBundle\Command;

use Harmony\Bundle\CoreBundle\Component\HttpKernel\AbstractKernel;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\KernelInterface;

/**
 * Class ThemeAssetsInstallCommand
 *
 * @package Harmony\Bundle\ThemeBundle\Command
 */
class ThemeAssetsInstallCommand extends Command
{

    /** Constants */
    const THEMES_DIR = 'themes';
    const ASSETS_DIR = 'assets';

    /** @var Filesystem $filesystem */
    protected $filesystem;

    /** @var KernelInterface|AbstractKernel $kernel */
    protected $kernel;

    /**
     * ThemeAssetsInstallCommand constructor.
     *
     * @param KernelInterface|AbstractKernel $kernel
     * @param Filesystem                     $filesystem
     */
    public function __construct(KernelInterface $kernel, Filesystem $filesystem)
    {
        parent::__construct(null);
        $this->kernel     = $kernel;
        $this->filesystem = $filesystem;
    }

    /**
     * Configures the current command.
     */
    protected function configure()
    {
        $this->setName('theme:assets:install')
            ->setDefinition([
                new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', 'public'),
            ])
            ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it')
            ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
            ->setDescription('Installs themes web assets under a public directory')
            ->setHelp(<<<'EOT'
The <info>%command.name%</info> command installs theme assets into a given
directory (e.g. the <comment>public</comment> directory).

  <info>php %command.full_name% public</info>

A "themes" directory will be created inside the target directory and the
"assets" directory of each theme will be copied into it.

To create a symlink to each theme instead of copying its assets, use the
<info>--symlink</info> option (will fall back to hard copies when symbolic links aren't possible:

  <info>php %command.full_name% public --symlink</info>

To make symlink relative, add the <info>--relative</info> option:

  <info>php %command.full_name% public --symlink --relative</info>

EOT
            );
    }

    /**
     * Executes the current command.
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     *
     * @return null|int null or 0 if everything went fine, or an error code
     * @throws LogicException When this abstract method is not implemented
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $targetArg = rtrim($input->getArgument('target'), '/');
        if (!is_dir($targetArg)) {
            throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.',
                $input->getArgument('target')));
        }

        // Create the themes directory otherwise symlink will fail.
        $themesDir = $targetArg . DIRECTORY_SEPARATOR . self::THEMES_DIR . DIRECTORY_SEPARATOR;
        $this->filesystem->mkdir($themesDir, 0777);

        $io = new SymfonyStyle($input, $output);
        $io->newLine();
        if ($input->getOption('relative')) {
            $expectedMethod = AssetsInstallCommand::METHOD_RELATIVE_SYMLINK;
            $io->text('Trying to install theme assets as <info>relative symbolic links</info>.');
        } elseif ($input->getOption('symlink')) {
            $expectedMethod = AssetsInstallCommand::METHOD_ABSOLUTE_SYMLINK;
            $io->text('Trying to install theme assets as <info>absolute symbolic links</info>.');
        } else {
            $expectedMethod = AssetsInstallCommand::METHOD_COPY;
            $io->text('Installing theme assets as <info>hard copies</info>.');
        }
        $io->newLine();

        $rows     = [];
        $copyUsed = false;
        $exitCode = 0;

        foreach ($this->kernel->getThemes() as $theme) {
            $originDir = $theme->getPath() . DIRECTORY_SEPARATOR . self::ASSETS_DIR;
            if (!is_dir($originDir)) {
                continue;
            }
            $targetDir = $themesDir . $theme->getShortName();

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $message = sprintf("%s\n-> %s", $theme->getIdentifier(), $targetDir);
            } else {
                $message = $theme->getIdentifier();
            }

            try {
                $this->filesystem->remove($targetDir);

                if (AssetsInstallCommand::METHOD_RELATIVE_SYMLINK === $expectedMethod) {
                    $method = $this->_relativeSymlinkWithFallback($originDir, $targetDir);
                } elseif (AssetsInstallCommand::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) {
                    $method = $this->_absoluteSymlinkWithFallback($originDir, $targetDir);
                } else {
                    $method = $this->_hardCopy($originDir, $targetDir);
                }

                if (AssetsInstallCommand::METHOD_COPY === $method) {
                    $copyUsed = true;
                }

                if ($method === $expectedMethod) {
                    $rows[] = [
                        sprintf('<fg=green;options=bold>%s</>',
                            '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */),
                        $message,
                        $method
                    ];
                } else {
                    $rows[] = [
                        sprintf('<fg=yellow;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'),
                        $message,
                        $method
                    ];
                }
            }
            catch (\Exception $e) {
                $exitCode = 1;
                $rows[]   = [
                    sprintf('<fg=red;options=bold>%s</>',
                        '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */),
                    $message,
                    $e->getMessage()
                ];
            }
        }

        $io->table(['', 'Theme', 'Method / Error'], $rows);

        if (0 !== $exitCode) {
            $io->error('Some errors occurred while installing assets.');
        } else {
            if ($copyUsed) {
                $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.');
            }
            $io->success('All assets were successfully installed.');
        }

        return $exitCode;
    }

    /**
     * Try to create relative symlink.
     * Falling back to absolute symlink and finally hard copy.
     *
     * @param string $originDir
     * @param string $targetDir
     *
     * @return string
     */
    private function _relativeSymlinkWithFallback(string $originDir, string $targetDir): string
    {
        try {
            $this->_symlink($originDir, $targetDir, true);
            $method = AssetsInstallCommand::METHOD_RELATIVE_SYMLINK;
        }
        catch (IOException $e) {
            $method = $this->_absoluteSymlinkWithFallback($originDir, $targetDir);
        }

        return $method;
    }

    /**
     * Try to create absolute symlink.
     * Falling back to hard copy.
     *
     * @param string $originDir
     * @param string $targetDir
     *
     * @return string
     */
    private function _absoluteSymlinkWithFallback(string $originDir, string $targetDir): string
    {
        try {
            $this->_symlink($originDir, $targetDir);
            $method = AssetsInstallCommand::METHOD_ABSOLUTE_SYMLINK;
        }
        catch (IOException $e) {
            // fall back to copy
            $method = $this->_hardCopy($originDir, $targetDir);
        }

        return $method;
    }

    /**
     * Creates symbolic link.
     *
     * @param string $originDir
     * @param string $targetDir
     * @param bool   $relative
     *
     * @throws IOException If link can not be created.
     */
    private function _symlink(string $originDir, string $targetDir, bool $relative = false)
    {
        if ($relative) {
            $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir)));
        }
        $this->filesystem->symlink($originDir, $targetDir);
        if (!file_exists($targetDir)) {
            throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0,
                null, $targetDir);
        }
    }

    /**
     * Copies origin to target.
     *
     * @param string $originDir
     * @param string $targetDir
     *
     * @return string
     */
    private function _hardCopy(string $originDir, string $targetDir): string
    {
        $this->filesystem->mkdir($targetDir, 0777);
        // We use a custom iterator to ignore VCS files
        $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir));

        return AssetsInstallCommand::METHOD_COPY;
    }
}


<?php

/*
 * This file is part of the andreas-weber/php-runner library.
 *
 * (c) Andreas Weber <code@andreas-weber.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace AndreasWeber\Runner\Example;

use AndreasWeber\Runner\Payload\ArrayPayload;
use AndreasWeber\Runner\Payload\PayloadInterface;
use AndreasWeber\Runner\Task\AbstractTask;

class TaskFail extends AbstractTask
{
    /**
     * @var int Run counter
     */
    private $runCounter;

    /**
     * @var int Fail count
     */
    private $failCount;

    /**
     * __construct()
     *
     * @param int $failCount
     */
    public function __construct($failCount)
    {
        parent::__construct();

        $this->failCount = (int)$failCount;
        $this->runCounter = 0;
    }

    /**
     * Executes the task.
     *
     * @param PayloadInterface $payload
     *
     * @return null
     */
    public function run(PayloadInterface $payload)
    {
        $this->runCounter++;

        if ($this->runCounter <= $this->failCount) {
            // 2 retries triggered by method
            $this->retry();
        }

        /** @var ArrayPayload $payload */
        $payload->setData(
            array_merge(
                $payload->getData(),
                array(
                    __CLASS__ => true
                )
            )
        );
    }
}


<?php

/**
 * An indivisual blog post.
 *
 * @package silverstripe
 * @subpackage blog
 *
 * @author Michael Strong <github@michaelstrong.co.uk>
**/
class BlogPost extends Page {

	private static $db = array(
		"PublishDate" => "SS_Datetime",
	);

	private static $has_one = array(
		"FeaturedImage" => "Image",
	);

	private static $many_many = array(
		"Categories" => "BlogCategory",
		"Tags" => "BlogTag",
	);

	private static $defaults = array(
		"ShowInMenus" => false,
		"InheritSideBar" => true, // Support for widgets
		"ProvideComments" => true, // Support for comments
	);

	private static $extensions = array(
		"BlogPostFilter",
	);

	private static $searchable_fields = array(
		"Title"
	);

	private static $summary_fields = array(
		"Title",
	);

	private static $allowed_children = array();

	private static $default_sort = "PublishDate DESC";

	private static $can_be_root = false;

	/**
	 * This will display or hide the current class from the SiteTree. This
	 * variable can be configured using YAML.
	 *
	 * @var boolean
	**/
	private static $show_in_sitetree = false;

	public function getCMSFields() {
		Requirements::css(BLOGGER_DIR . '/css/cms.css');

		$self =& $this;
		$this->beforeUpdateCMSFields(function($fields) use ($self) {
			$fields->addFieldsToTab('Root.Main', array(
				HeaderField::create('Post Options', 3),
				$publishDate = DatetimeField::create("PublishDate", _t("BlogPost.PublishDate", "Publish Date")),
				ListboxField::create(
					"Categories",
					_t("BlogPost.Categories", "Categories"),
					$self->Parent()->Categories()->map()->toArray()
				)->setMultiple(true),
				ListboxField::create(
					"Tags",
					_t("BlogPost.Tags", "Tags"),
					$self->Parent()->Tags()->map()->toArray()
				)->setMultiple(true)
			));
			$publishDate->getDateField()->setConfig("showcalendar", true);

			// Add featured image
			$fields->insertBefore(
				$uploadField = UploadField::create("FeaturedImage", _t("BlogPost.FeaturedImage", "Featured Image")),
				"Content"
			);
			$uploadField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
		});

		$fields = parent::getCMSFields();

		// We're going to make an SEO tab and move all the usual crap there
		$menuTitle = $fields->dataFieldByName('MenuTitle');
		$urlSegment = $fields->dataFieldByName('URLSegment');
		$fields->addFieldsToTab('Root.SEO', array(
			$menuTitle,
			$urlSegment,
		));

		$metaField = $fields->fieldByName('Root.Main.Metadata');
		if($metaField) {
			$metaFields = $metaField->getChildren();
			if($metaFields->count() > 0) {
				$tab = $fields->findOrMakeTab('Root.SEO');
				$tab->push(HeaderField::create('Meta', 3));
				foreach($metaFields as $field) {
					$tab->push($field);
				}
			}
			$fields->removeByName('Metadata');
		}

		return $fields;
	}

	/**
	 * If no publish date is set, set the date to now.
	**/
	public function onBeforeWrite() {
		parent::onBeforeWrite();
		if(!$this->PublishDate) $this->setCastedField("PublishDate", time());
	}

	/**
	 * Update the PublishDate to now, if being published for the first time, and the date hasn't been set to the future.
	**/
	public function onBeforePublish() {
		if ($this->dbObject('PublishDate')->InPast() && !$this->isPublished()) {
			$this->setCastedField("PublishDate", time());
			$this->write();
		}
	}

	/**
	 * Checks the publish date to see if the blog post has actually been published.
	 *
	 * @param $member Member|null
	 *
	 * @return boolean
	**/
	public function canView($member = null) {
		if(!parent::canView($member)) return false;

		if($this->PublishDate) {
			$publishDate = $this->dbObject("PublishDate");
			if($publishDate->InFuture() && !Permission::checkMember($member, "VIEW_DRAFT_CONTENT")) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns the post excerpt.
	 *
	 * @param $wordCount int - number of words to display
	 *
	 * @return string
	**/
	public function Excerpt($wordCount = 30) {
		return $this->dbObject("Content")->LimitWordCount($wordCount);
	}

	/**
	 * Returns a monthly archive link for the current blog post.
	 *
	 * @param $type string day|month|year
	 *
	 * @return string URL
	**/
	public function getMonthlyArchiveLink($type = "day") {
		$date = $this->dbObject("PublishDate");
		$year = $date->format("Y");
		if($type != "year") {
			if($type == "day") {
				return Controller::join_links(
					$this->Parent()->Link("archive"),
					$date->format("Y"),
					$date->format("m"),
					$date->format("d")
				);
			}
			return Controller::join_links($this->Parent()->Link("archive"), $date->format("Y"), $date->format("m"));
		}
		return Controller::join_links($this->Parent()->Link("archive"), $date->format("Y"));
	}

	/**
	 * Returns a yearly archive link for the current blog post.
	 *
	 * @return string URL
	**/
	public function getYearlyArchiveLink() {
		$date = $this->dbObject("PublishDate");
		return Controller::join_links($this->Parent()->Link("archive"), $date->format("Y"));
	}

	/**
	 * Sets the label for BlogPost.Title to 'Post Title' (Rather than 'Page name')
	 *
	 * @return array
	**/
	public function fieldLabels($includerelations = true) {
		$labels = parent::fieldLabels($includerelations);
		$labels['Title'] = _t('BlogPost.PageTitleLabel', "Post Title");
		return $labels;
	}

}

/**
 * Blog Post controller
 *
 * @package silverstripe
 * @subpackage blog
 *
 * @author Michael Strong <github@michaelstrong.co.uk>
**/
class BlogPost_Controller extends Page_Controller {

}


<?php declare(strict_types=1);

/**
 * @copyright (c) 2015-present brian ridley
 * @author brian ridley <ptlis@ptlis.net>
 * @license http://opensource.org/licenses/MIT MIT
 */

namespace ptlis\ShellCommand\Mock;

use ptlis\ShellCommand\Interfaces\CommandBuilderInterface;
use ptlis\ShellCommand\Interfaces\CommandInterface;
use ptlis\ShellCommand\Interfaces\ProcessOutputInterface;
use ptlis\ShellCommand\Interfaces\ProcessObserverInterface;
use ptlis\ShellCommand\ProcessOutput;
use ptlis\ShellCommand\UnixEnvironment;

/**
 * Mock implementation of the command builder interface.
 */
final class MockCommandBuilder implements CommandBuilderInterface
{
    /** @var string */
    private $command;

    /** @var string[] */
    private $argumentList = [];

    /** @var string[] */
    private $rawArgumentList = [];

    /** @var int*/
    private $timeout;

    /** @var int */
    private $pollTimeout;

    /** @var string */
    private $cwd;

    /** @var ProcessObserverInterface[] */
    private $observerList = [];

    /** @var string[] */
    private $envVariableList = [];

    /** @var ProcessOutput[] */
    private $mockResultList = [];

    /** @var MockCommand[] */
    private $builtCommandList = [];

    public function __construct(
        array $mockResultList = [],
        string $command = '',
        array $argumentsList = [],
        int $pollTimeout = 1000,
        int $timeout = -1,
        string $cwd = '',
        array $envVariableList = [],
        array &$builtCommandList = [],
        array $rawArgumentList = [],
        array $observerList = []
    ) {
        $this->mockResultList = $mockResultList;
        $this->command = $command;
        $this->argumentList = $argumentsList;
        $this->timeout = $timeout;
        $this->pollTimeout = $pollTimeout;
        $this->cwd = $cwd;
        $this->envVariableList = $envVariableList;
        $this->builtCommandList = &$builtCommandList;
        $this->rawArgumentList = $rawArgumentList;
        $this->observerList = $observerList;
    }

    public function setCommand(string $command): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;
        $newBuilder->command = $command;

        return $newBuilder;
    }

    public function addArgument(string $argument, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;

        if ($conditionalResult) {
            $argumentList = $this->argumentList;
            $argumentList[] = $argument;
            $newBuilder->argumentList = $argumentList;
        }

        return $newBuilder;
    }

    public function addArguments(array $argumentList, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            /** @var string[] $argumentList */
            $argumentList = array_merge($this->argumentList, $argumentList);
            $newBuilder->builtCommandList = &$this->builtCommandList;
            $newBuilder->mockResultList = &$this->mockResultList;
            $newBuilder->argumentList = $argumentList;
        }

        return $newBuilder;
    }

    public function addRawArgument(string $rawArgument, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $rawArgumentList = $this->rawArgumentList;
            $rawArgumentList[] = $rawArgument;
            $newBuilder->builtCommandList = &$this->builtCommandList;
            $newBuilder->mockResultList = &$this->mockResultList;
            $newBuilder->rawArgumentList = $rawArgumentList;
        }

        return $newBuilder;
    }

    public function addRawArguments(array $rawArgumentList, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            /** @var string[] $argumentList */
            $rawArgumentList = array_merge($this->rawArgumentList, $rawArgumentList);
            $newBuilder->builtCommandList = &$this->builtCommandList;
            $newBuilder->mockResultList = &$this->mockResultList;
            $newBuilder->rawArgumentList = $rawArgumentList;
        }

        return $newBuilder;
    }

    public function setTimeout(int $timeout): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;
        $newBuilder->timeout = $timeout;

        return $newBuilder;
    }

    public function setPollTimeout(int $pollTimeout): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;
        $newBuilder->pollTimeout = $pollTimeout;

        return $newBuilder;
    }

    public function setCwd(string $cwd): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;
        $newBuilder->cwd = $cwd;

        return $newBuilder;
    }

    public function getBuiltCommands(): array
    {
        return $this->builtCommandList;
    }

    public function addProcessObserver(ProcessObserverInterface $observer): CommandBuilderInterface
    {
        $observerList = $this->observerList;
        $observerList[] = $observer;

        $newBuilder = clone $this;
        $newBuilder->builtCommandList = &$this->builtCommandList;
        $newBuilder->mockResultList = &$this->mockResultList;
        $newBuilder->observerList = $observerList;

        return $newBuilder;
    }

    public function addEnvironmentVariable(
        string $key,
        string $value,
        bool $conditionalResult = true
    ): CommandBuilderInterface {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $envVariableList = $this->envVariableList;
            $envVariableList[$key] = $value;
            $newBuilder->builtCommandList = &$this->builtCommandList;
            $newBuilder->mockResultList = &$this->mockResultList;
            $newBuilder->envVariableList = $envVariableList;
        }

        return $newBuilder;
    }

    public function addEnvironmentVariables(array $envVars, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $envVariableList = array_merge($this->envVariableList, $envVars);
            $newBuilder->builtCommandList = &$this->builtCommandList;
            $newBuilder->mockResultList = &$this->mockResultList;
            $newBuilder->envVariableList = $envVariableList;
        }

        return $newBuilder;
    }

    public function buildCommand(): CommandInterface
    {
        if (!$this->command) {
            throw new \RuntimeException('No command was provided to "' . __CLASS__ . '", unable to build command.');
        }

        if (!count($this->mockResultList)) {
            throw new \RuntimeException('No result was provided for use when mocking execution of the command.');
        }

        $result = array_shift($this->mockResultList);

        $command = new MockCommand(
            new UnixEnvironment(),
            $this->command,
            $this->argumentList,
            $this->rawArgumentList,
            $result,
            $this->envVariableList
        );
        $this->builtCommandList[] = $command;

        return $command;
    }

    /**
     * Add a mock result (to be returned in order of execution).
     *
     * @param int $exitCode
     * @param string $stdOut
     * @param string $stdErr
     *
     * @return $this
     */
    public function addMockResult(int $exitCode, string $stdOut, string $stdErr): CommandBuilderInterface
    {
        $mockResultList = $this->mockResultList;
        $mockResultList[] = new ProcessOutput($exitCode, $stdOut, $stdErr);

        return new MockCommandBuilder(
            $mockResultList,
            $this->command,
            $this->argumentList,
            $this->timeout,
            $this->pollTimeout,
            $this->cwd,
            $this->envVariableList,
            $this->builtCommandList
        );
    }
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\ClassMapper;

use Eloquent\Cosmos\ClassName;
use Eloquent\Typhoon\TypeCheck\TypeCheck;
use FilesystemIterator;
use Icecave\Isolator\Isolator;
use Icecave\Pasta\AST\Type\AccessModifier;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class ClassMapper
{
    /**
     * @param Isolator|null $isolator
     */
    public function __construct(Isolator $isolator = null)
    {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        $this->isolator = Isolator::get($isolator);
    }

    /**
     * @param string $path
     *
     * @return array<ClassDefinition>
     */
    public function classesByPath($path)
    {
        $this->typeCheck->classesByPath(func_get_args());

        if ($this->isolator->is_dir($path)) {
            return $this->classesByDirectory($path);
        }

        return $this->classesByFile($path);
    }

    /**
     * @param array<string> $paths
     *
     * @return array<ClassDefinition>
     */
    public function classesByPaths(array $paths)
    {
        $this->typeCheck->classesByPaths(func_get_args());

        $classMap = array();
        foreach ($paths as $path) {
            $classMap = array_merge(
                $classMap,
                $this->classesByPath($path)
            );
        }

        return $classMap;
    }

    /**
     * @param string $directoryPath
     *
     * @return array<ClassDefinition>
     */
    public function classesByDirectory($directoryPath)
    {
        $this->typeCheck->classesByDirectory(func_get_args());

        $classDefinitions = array();
        foreach ($this->fileIterator($directoryPath) as $filePathInfo) {
            $classDefinitions = array_merge(
                $classDefinitions,
                $this->classesByFile($filePathInfo->getPathname())
            );
        }

        return $classDefinitions;
    }

    /**
     * @param string $filePath
     *
     * @return array<ClassDefinition>
     */
    public function classesByFile($filePath)
    {
        $this->typeCheck->classesByFile(func_get_args());

        return $this->classesBySource(
            $this->isolator->file_get_contents($filePath)
        );
    }

    /**
     * @param string $source
     *
     * @return array<ClassDefinition>
     */
    public function classesBySource($source)
    {
        $this->typeCheck->classesBySource(func_get_args());

        $classDefinitions = array();
        $namespaceName = null;
        $usedClasses = array();
        $tokens = $this->isolator->token_get_all($source);

        while ($token = next($tokens)) {
            if (is_array($token)) {
                switch ($token[0]) {
                    case T_NAMESPACE:
                        $namespaceName = $this->parseNamespaceName($tokens);
                        $usedClasses = array();
                        break;
                    case T_USE:
                        $usedClasses[] = $this->parseUsedClass($tokens);
                        break;
                    case T_CLASS:
                        $classDefinitions[] = $this->parseClassDefinition(
                            $tokens,
                            $namespaceName,
                            $usedClasses
                        );
                        break;
                }
            }
        }

        return $classDefinitions;
    }

    /**
     * @param ClassName $className
     * @param string    $source
     *
     * @return ClassDefinition
     * @throws Exception\UndefinedClassException
     */
    public function classBySource(ClassName $className, $source)
    {
        $this->typeCheck->classBySource(func_get_args());

        foreach ($this->classesBySource($source) as $classDefinition) {
            if ($classDefinition->className()->string() === $className->toAbsolute()->string()) {
                return $classDefinition;
            }
        }

        throw new Exception\UndefinedClassException($className);
    }

    /**
     * @param array<string|array> &$tokens
     *
     * @return ClassName
     */
    protected function parseNamespaceName(array &$tokens)
    {
        $this->typeCheck->parseNamespaceName(func_get_args());

        $namespaceAtoms = array();
        do {
            $token = next($tokens);

            switch ($token[0]) {
                case T_STRING:
                    $namespaceAtoms[] = $token[1];
                    break;
            }
        } while (
            T_STRING === $token[0]
            || T_NS_SEPARATOR === $token[0]
            || T_WHITESPACE === $token[0]
        );

        return ClassName::fromAtoms($namespaceAtoms, true);
    }

    /**
     * @param array<string|array> &$tokens
     *
     * @return array<ClassName>
     */
    protected function parseUsedClass(array &$tokens)
    {
        $this->typeCheck->parseUsedClass(func_get_args());

        $usedClassTuple = array();
        $usedClassAtoms = array();

        $token = next($tokens);
        while (
            is_array($token) && (
                T_STRING === $token[0] ||
                T_NS_SEPARATOR === $token[0] ||
                T_WHITESPACE === $token[0]
            )
        ) {
            if (T_STRING === $token[0]) {
                $usedClassAtoms[] .= $token[1];
            }

            $token = next($tokens);
        }
        $usedClassTuple[] = ClassName::fromAtoms($usedClassAtoms, true);

        if (
            !is_array($token) ||
            T_AS !== $token[0]
        ) {
            return $usedClassTuple;
        }

        $token = next($tokens);
        while (
            is_array($token) &&
            T_WHITESPACE === $token[0]
        ) {
            $token = next($tokens);
        }

        if (
            is_array($token) &&
            T_STRING === $token[0]
        ) {
            $usedClassTuple[] = ClassName::fromAtoms(array($token[1]), false);
        }

        return $usedClassTuple;
    }

    /**
     * @param array<string|array>     &$tokens
     * @param ClassName|null          $namespaceName
     * @param array<array<ClassName>> $usedClasses
     *
     * @return ClassDefinition
     */
    protected function parseClassDefinition(array &$tokens, ClassName $namespaceName = null, array $usedClasses)
    {
        $this->typeCheck->parseClassDefinition(func_get_args());

        $className = $this->parseClassName($tokens);
        if (null !== $namespaceName) {
            $className = $namespaceName->join($className);
        }

        $methods = array();
        $properties = array();
        $inClassBody = false;
        $accessModifier = null;
        $isStatic = null;
        $isAbstract = null;
        $source = null;
        while ($token = next($tokens)) {
            $token = $this->normalizeToken($token);

            if ($inClassBody) {
                if ('}' === $token[0]) {
                    break;
                }

                if (
                    T_PUBLIC === $token[0] ||
                    T_PROTECTED === $token[0] ||
                    T_PRIVATE === $token[0] ||
                    T_STATIC === $token[0] ||
                    T_ABSTRACT === $token[0] ||
                    T_FINAL === $token[0]
                ) {
                    $lineNumber = $token[2];
                    $token = $this->parseClassMemberModifiers(
                        $token,
                        $tokens,
                        $accessModifier,
                        $isStatic,
                        $isAbstract,
                        $source
                    );

                    if (T_FUNCTION === $token[0]) {
                        $methods[] = $this->parseMethod(
                            $className,
                            $token,
                            $tokens,
                            $accessModifier,
                            $isStatic,
                            $isAbstract,
                            $source,
                            $lineNumber
                        );
                    } elseif (T_VARIABLE === $token[0]) {
                        $properties[] = $this->parseProperty(
                            $className,
                            $token,
                            $tokens,
                            $accessModifier,
                            $isStatic,
                            $source,
                            $lineNumber
                        );
                    }

                    $accessModifier = null;
                    $isStatic = null;
                    $isAbstract = null;
                    $source = null;
                }
            } elseif ('{' === $token[0]) {
                $inClassBody = true;
            }
        }

        return new ClassDefinition(
            $className,
            $usedClasses,
            $methods,
            $properties
        );
    }

    /**
     * @param tuple<integer,string,integer> $token
     * @param array<string|array>           &$tokens
     * @param null                          &$accessModifier
     * @param null                          &$isStatic
     * @param null                          &$isAbstract
     * @param null                          &$source
     *
     * @return tuple<integer|string,string,integer|null>
     */
    protected function parseClassMemberModifiers(
        array $token,
        array &$tokens,
        &$accessModifier,
        &$isStatic,
        &$isAbstract,
        &$source
    ) {
        $this->typeCheck->parseClassMemberModifiers(func_get_args());

        $isStatic = false;
        $isAbstract = false;
        $source = '';

        while ($token) {
            $token = $this->normalizeToken($token);
            $source .= $token[1];
            if (
                T_FUNCTION === $token[0] ||
                T_VARIABLE === $token[0]
            ) {
                break;
            } elseif (
                T_PUBLIC === $token[0] ||
                T_PROTECTED === $token[0] ||
                T_PRIVATE === $token[0]
            ) {
                $accessModifier = AccessModifier::instanceByValue(
                    strtolower($token[1])
                );
            } elseif (T_STATIC === $token[0]) {
                $isStatic = true;
            } elseif (T_ABSTRACT === $token[0]) {
                $isAbstract = true;
            }

            $token = next($tokens);
        }

        return $token;
    }

    /**
     * @param ClassName                     $className
     * @param tuple<integer,string,integer> $token
     * @param array<string|array>           &$tokens
     * @param AccessModifier                $accessModifier
     * @param boolean                       $isStatic
     * @param string                        $source
     * @param integer                       $lineNumber
     *
     * @return PropertyDefinition
     */
    protected function parseProperty(
        ClassName $className,
        array $token,
        array &$tokens,
        AccessModifier $accessModifier,
        $isStatic,
        $source,
        $lineNumber
    ) {
        $this->typeCheck->parseProperty(func_get_args());

        $name = substr($token[1], 1);

        while ($token = next($tokens)) {
            $token = $this->normalizeToken($token);
            $source .= $token[1];
            if (';' === $token[0]) {
                break;
            }
        }

        return new PropertyDefinition(
            $className,
            $name,
            $isStatic,
            $accessModifier,
            $lineNumber,
            $source
        );
    }

    /**
     * @param ClassName                     $className
     * @param tuple<integer,string,integer> $token
     * @param array<string|array>           &$tokens
     * @param AccessModifier                $accessModifier
     * @param boolean                       $isStatic
     * @param boolean                       $isAbstract
     * @param string                        $source
     * @param integer                       $lineNumber
     *
     * @return MethodDefinition
     */
    protected function parseMethod(
        ClassName $className,
        array $token,
        array &$tokens,
        AccessModifier $accessModifier,
        $isStatic,
        $isAbstract,
        $source,
        $lineNumber
    ) {
        $this->typeCheck->parseMethod(func_get_args());

        do {
            $token = $this->normalizeToken(next($tokens));
            $source .= $token[1];
        } while (T_WHITESPACE === $token[0]);

        $name = $token[1];

        $bracketDepth = 0;
        while ($token = next($tokens)) {
            $token = $this->normalizeToken($token);
            $source .= $token[1];

            if ('{' === $token[0]) {
                $bracketDepth ++;
            } elseif ('}' === $token[0]) {
                $bracketDepth --;

                if ($bracketDepth < 1) {
                    break;
                }
            } elseif (';' === $token[0]) {
                if ($bracketDepth < 1) {
                    break;
                }
            }
        }

        return new MethodDefinition(
            $className,
            $name,
            $isStatic,
            $isAbstract,
            $accessModifier,
            $lineNumber,
            $source
        );
    }

    /**
     * @param array<string|array> &$tokens
     *
     * @return ClassName
     */
    protected function parseClassName(array &$tokens)
    {
        $this->typeCheck->parseClassName(func_get_args());

        do {
            $token = $this->normalizeToken(next($tokens));
        } while (T_WHITESPACE === $token[0]);

        return ClassName::fromAtoms(array($token[1]), false);
    }

    /**
     * @param string $directoryPath
     *
     * @return RecursiveIteratorIterator
     */
    protected function fileIterator($directoryPath)
    {
        $this->typeCheck->fileIterator(func_get_args());

        return new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator(
                $directoryPath,
                FilesystemIterator::FOLLOW_SYMLINKS |
                FilesystemIterator::SKIP_DOTS
            )
        );
    }

    /**
     * @param string|tuple<integer,string,integer> $token
     *
     * @return tuple<integer|string,string,integer|null>
     */
    protected function normalizeToken($token)
    {
        $this->typeCheck->normalizeToken(func_get_args());

        if (is_array($token)) {
            return $token;
        }

        return array($token, $token, null);
    }

    private $isolator;
    private $typeCheck;
}


<?php

/**
 * Adds a component which allows a user to add a new DataObject by database field.
 *
 * @package silverstripe
 * @subpackage blog
 *
 * @author Michael Strong <github@michaelstrong.co.uk>
**/
class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLProvider {

	/**
	 * HTML Fragment to render the field.
	 *
	 * @var string
	**/
	protected $targetFragment;

	/**
	 * Default field to create the dataobject by should be Title.
	 *
	 * @var string
	**/
	protected $dataObjectField = "Title";

	/**
	 * Creates a text field and add button which allows the user to directly create a new
	 * DataObject by just entering the title.
	 *
	 * @param $targetFragment string
	**/
	public function __construct($targetFragment = 'before', $dataObjectField = "Title") {
		$this->targetFragment = $targetFragment;
		$this->dataObjectField = (string) $dataObjectField;
	}

	/**
	 * Provide actions to this component.
	 *
	 * @param $gridField GridField
	 *
	 * @return array
	**/
	public function getActions($gridField) {
		return array("add");
	}

	/**
	 * Handles the add action for the given DataObject
	 *
	 * @param $gridFIeld GridFIeld
	 * @param $actionName string
	 * @param $arguments mixed
	 * @param $data array
	**/
	public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
		if($actionName == "add") {
			$dbField = $this->getDataObjectField();

			$objClass = $gridField->getModelClass();
			$obj = new $objClass();
			if($obj->hasField($dbField)) {
				$obj->setCastedField($dbField, $data['gridfieldaddbydbfield'][$obj->ClassName][$dbField]);

				if($obj->canCreate()) {
					$id = $gridField->getList()->add($obj);
					if(!$id) {
						$gridField->setError(_t(
							"GridFieldAddByDBField.AddFail",
							"Unable to save {class} to the database.",
							"Unable to add the DataObject.",
							array(
								"class" => get_class($obj)
							)),
							"error"
						);
					}
				} else {
					return Security::permissionFailure(
						Controller::curr(),
						_t(
							"GridFieldAddByDBField.PermissionFail",
							"You don't have permission to create a {class}.",
							"Unable to add the DataObject.",
							array(
								"class" => get_class($obj)
							)
						)
					);
				}
			} else {
				throw new UnexpectedValueException("Invalid field (" . $dbField . ") on  " . $obj->ClassName . ".");
			}
		}
	}

	/**
	 * Renders the TextField and add button to the GridField.
	 *
	 * @param $girdField GridField
	 *
	 * @return string HTML
	**/
	public function getHTMLFragments($gridField) {
		$dataClass = $gridField->getList()->dataClass();
		$obj = singleton($dataClass);
		if(!$obj->canCreate()) return "";

		$dbField = $this->getDataObjectField();

		$textField = TextField::create(
			"gridfieldaddbydbfield[" . $obj->ClassName . "][" . Convert::raw2htmlatt($dbField) . "]"
			)->setAttribute("placeholder", $obj->fieldLabel($dbField))
			->addExtraClass("no-change-track");

		$addAction = new GridField_FormAction($gridField,
			'add',
			_t('GridFieldAddByDBField.Add',
				"Add {name}", "Add button text",
				array("name" => $obj->i18n_singular_name())
			),
			'add',
			'add'
		);
		$addAction->setAttribute('data-icon', 'add');

		// Start thinking about rending this back to the GF
		$forTemplate = new ArrayData(array());
		$forTemplate->Fields = new ArrayList();

		$forTemplate->Fields->push($textField);
		$forTemplate->Fields->push($addAction);

		return array(
			$this->targetFragment => $forTemplate->renderWith("GridFieldAddByDBField")
		);
	}

	/**
	 * Returns the database field for which we'll add the new data object.
	 *
	 * @return string
	**/
	public function getDataObjectField() {
		return $this->dataObjectField;
	}

	/**
	 * Set the database field.
	 *
	 * @param $field string
	**/
	public function setDataObjectField($field) {
		$this->dataObjectField = (string) $field;
	}

}