<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use ClanCats\Container\{
    Container,

    Exceptions\ContainerBuilderException
};

class ContainerBuilder
{
    /**
     * The full container name with namespace
     *
     * @var string
     */
    protected $containerName;

    /**
     * The class name without namespace
     *
     * @var string
     */
    protected $containerClassName;

    /**
     * Just the namespace
     *
     * @var string
     */
    protected $containerNamespace;

    /**
     * An array of paramters to be builded directly
     * as propterty.
     *
     * @var array
     */
    protected $parameters = [];

    /**
     * An array of binded services
     *
     * @param array[string => Service]
     */
    protected $services = [];

    /**
     * An array of service names that should be shared in the builded container
     *
     * @param array[string]
     */
    protected $shared = [];

    /**
     * An array of converted service names
     * The normalized service names is camel cased and should be usable as method name.
     *
     * @param array[string]
     */
    private $normalizedServiceNames = [];

    /**
     * Constrcut a container builder instance
     *
     * @param string            $containerName
     * @return void
     */
    public function __construct(string $containerName)
    {
        $this->setContainerName($containerName);
    }

    /**
     * Sets the container name
     * This will also update the "containerClassName" and "containerNamespace"
     *
     * @param string            $containerName
     * @return void
     */
    public function setContainerName(string $containerName)
    {
        if (empty($containerName) || !(preg_match('/^[a-zA-Z0-9\\\\_]*$/', $containerName)) || is_numeric($containerName[0]))
        {
            throw new ContainerBuilderException('The container name cannot be empty, start with a number or contain sepcial characters except "\\".');
        }

        if ($containerName[0] === "\\")
        {
            $containerName = substr($containerName, 1);
        }

        $this->containerClassName = $this->containerName = $containerName;

        // check if we need to generate a namespace
        if (($pos = strrpos($containerName, "\\")) !== false)
        {
            $this->containerNamespace = substr($containerName, 0, $pos);
            $this->containerClassName = substr($containerName, $pos + 1);
        }
    }

    /**
     * Get the current container full name
     *
     * @return string
     */
    public function getContainerName() : string
    {
        return $this->containerName;
    }

    /**
     * Get the current container class name without namespace
     *
     * @return string
     */
    public function getContainerClassName() : string
    {
        return $this->containerClassName;
    }

    /**
     * Get the php container namespace not to confuse with the "ContainerNamespace" class.
     *
     * @return string|null
     */
    public function getContainerNamespace()
    {
        return $this->containerNamespace;
    }

    /**
     * Get all currently added services
     *
     * @return array[string => ServiceDefinition]
     */
    public function getServices() : array
    {
        return $this->services;
    }

    /**
     * Returns all shared service names
     *
     * @return array[string]
     */
    public function getSharedNames() : array
    {
        return $this->shared;
    }

    /**
     * Add a service by string and arguments array.
     *
     * @param string            $serviceName
     * @param string            $serviceClass
     * @param array             $serviceArguments
     * @param bool              $shared
     * @return ServiceDefinition
     */
    public function add(string $serviceName, string $serviceClass, array $serviceArguments = [], bool $isShared = true) : ServiceDefinition
    {
        $service = new ServiceDefinition($serviceClass, $serviceArguments);
        $this->addService($serviceName, $service, $isShared);

        return $service;
    }

    /**
     * Add services by an array
     *
     * @param array             $serviceArray
     * @return void
     */
    public function addArray(array $servicesArray)
    {
        foreach($servicesArray as $serviceName => $serviceConfiguration)
        {
            $this->addService($serviceName, ServiceDefinition::fromArray($serviceConfiguration), $serviceConfiguration['shared'] ?? true);
        }
    }

    /**
     * Add a service definition instance to the container builder.
     *
     * @param string                        $serviceName
     * @param ServiceDefinitionInterface    $serviceDefinition
     * @return void
     */
    public function addService(string $serviceName, ServiceDefinitionInterface $serviceDefinition, bool $isShared = true)
    {
        if ($this->invalidServiceBuilderString($serviceName))
        {
            throw new ContainerBuilderException('The "'.$serviceName.'" servicename must be a string, cannot be numeric, empty or contain any special characters except "." and "_".');
        }

        // add the service definition
        $this->services[$serviceName] = $serviceDefinition;

        // generate the normalized name
        $this->generateNormalizedServiceName($serviceName);

        // set the shared unshared flag
        if ($isShared && (!in_array($serviceName, $this->shared)))
        {
            $this->shared[] = $serviceName;
        }
        elseif ((!$isShared) && in_array($serviceName, $this->shared))
        {
            unset($this->shared[array_search($serviceName, $this->shared)]);
        }
    }

    /**
     * Import data from a container namespace
     *
     * @param ContainerNamespace            $namespace
     * @return void
     */
    public function importNamespace(ContainerNamespace $namespace)
    {
        // import the parameters
        $this->parameters = array_merge($this->parameters, $namespace->getParameters());

        // import the service definitions
        foreach($namespace->getServices() as $name => $service)
        {
            $this->addService($name, $service);
        }
    }

    /**
     * Checks if the given string is valid and not numeric &
     *
     * @param string            $value
     * @return bool
     */
    private function invalidServiceBuilderString(string $value) : bool
    {
        if (empty($value) || is_numeric($value)) {
            return true;
        }

        // check for trailing / prepending whitespace ect.
        if (trim($value) !== $value) {
            return true;
        }

        // check for other special characters
        if (preg_match('/[^a-zA-Z0-9._]+/', $value))  {
            return true;
        }

        // also check the first character the string contains with a number
        if (is_numeric($value[0]) || $value[0] === '.' || $value[0] === '_') {
            return true;
        }

        $lastCharacter = $value[strlen($value) - 1];
        if ($lastCharacter === '.' || $lastCharacter === '_') {
            return true;
        }

        return false;
    }

    /**
     * Generate a camelized service name
     *
     * @param string            $serviceName
     * @return string
     */
    private function camelizeServiceName(string $serviceName) : string
    {
        return str_replace(['.', '_'], '', ucwords(str_replace(['.', '_'], '.', $serviceName), '.'));
    }

    /**
     * Generates the "normalizedServiceNames" array.
     *
     * @param string            $serviceName
     * @return void
     */
    private function generateNormalizedServiceName(string $serviceName)
    {
        $normalizedServiceName = $this->camelizeServiceName($serviceName);

        $duplicateCounter = 0;
        $countedNormalizedServiceName = $normalizedServiceName;
        while(in_array($countedNormalizedServiceName, $this->normalizedServiceNames))
        {
            $duplicateCounter++;
            $countedNormalizedServiceName = $normalizedServiceName . $duplicateCounter;
        }

        $this->normalizedServiceNames[$serviceName] = $countedNormalizedServiceName;
    }

    /**
     * Generate the container class code string
     *
     * @return string
     */
    public function generate() : string
    {
        $buffer = "<?php\n\n";

        // add namespace if needed
        if (!is_null($this->containerNamespace))
        {
            $buffer .= "namespace " . $this->containerNamespace . ";\n\n";
        }

        // add use statement for the super container
        $aliasContainerName = 'ClanCatsContainer' . md5($this->containerName);
        $buffer .= "use " . Container::class . " as " . $aliasContainerName . ";\n\n";

        // generate the the class
        $buffer .= "class $this->containerClassName extends $aliasContainerName {\n\n";

        $buffer .= $this->generateParameters() . "\n";
        $buffer .= $this->generateMetaData() . "\n";
        $buffer .= $this->generateResolverTypes() . "\n";
        $buffer .= $this->generateResolverMappings() . "\n";
        $buffer .= $this->generateResolverMethods() . "\n";

        return $buffer . "\n}";
    }

    /**
     * Generate the service resolver method name for the given service
     *
     * @param string            $serviceName
     * @return string
     */
    private function getResolverMethodName(string $serviceName) : string
    {
        if (!isset($this->normalizedServiceNames[$serviceName]))
        {
            throw new ContainerBuilderException("The '" . $serviceName . "' service has never been definied.");
        }

        return 'resolve' . $this->normalizedServiceNames[$serviceName];
    }

    /**
     * Generate arguments code
     *
     * @param ServiceArguments          $arguments
     * @return string
     */
    private function generateArgumentsCode(ServiceArguments $arguments) : string
    {
        $buffer = [];

        foreach($arguments->getAll() as list($argumentValue, $argumentType))
        {
            if ($argumentType === ServiceArguments::DEPENDENCY)
            {
                if ($argumentValue === 'container')
                {
                    $buffer[] = "\$this";
                }
                // if the dependency is defined in the current container builder
                // we can be sure that it exists and directly call the resolver method
                elseif (isset($this->services[$argumentValue]))
                {
                    $resolverMethodCall = "\$this->" . $this->getResolverMethodName($argumentValue) . '()';

                    // if is not shared we can just forward the factory method
                    if (!in_array($argumentValue, $this->shared))
                    {
                        $buffer[] = $resolverMethodCall;
                    }
                    // otherwise we have to check if the singleton has
                    // already been resolved.
                    else
                    {
                        $buffer[] = "\$this->resolvedSharedServices['$argumentValue'] ?? \$this->resolvedSharedServices['$argumentValue'] = " . $resolverMethodCall;
                    }
                }
                // if the dependency is not defined inside the container builder
                // it might be added dynamically later. So we just access the containers `get` method.
                else
                {
                    $buffer[] = "\$this->get('$argumentValue')";
                }
            }
            elseif ($argumentType === ServiceArguments::PARAMETER)
            {
                $buffer[] = "\$this->getParameter('$argumentValue')";
            }
            elseif ($argumentType === ServiceArguments::RAW)
            {
                $buffer[] = var_export($argumentValue, true);
            }
        }

        return implode(', ', $buffer);
    }

    /**
     * Generate the containers parameter property
     *
     * @return string
     */
    private function generateParameters() : string
    {
        return "protected \$parameters = " . var_export($this->parameters, true) . ";\n";
    }

    /**
     * Generate the containers parameter property
     *
     * @return string
     */
    private function generateMetaData() : string
    {
        $metaData = [];
        $metaDataService = [];

        foreach($this->services as $serviceName => $serviceDefinition)
        {
            foreach($serviceDefinition->getMetaData() as $key => $serviceMetaData)
            {
                if (!isset($metaData[$key])) {
                    $metaData[$key] = [];
                }

                $metaData[$key][$serviceName] = $serviceMetaData;

                // mapping for the service centered
                if (!isset($metaDataService[$serviceName])) {
                    $metaDataService[$serviceName] = [];
                }

                if (!in_array($key, $metaDataService[$serviceName])) {
                    $metaDataService[$serviceName][] = $key;
                }
            }
        }

        return "protected \$metadata = " . var_export($metaData, true) . ";\nprotected \$metadataService = " . var_export($metaDataService, true) . ";\n";
    }

    /**
     * Generate the resolver types array
     *
     * @return string
     */
    private function generateResolverTypes() : string
    {
        $types = [];

        foreach($this->services as $serviceName => $serviceDefinition)
        {
            $types[] = var_export($serviceName, true) . ' => ' . Container::RESOLVE_METHOD;
        }

        return "protected \$serviceResolverType = [" . implode(', ', $types) . "];\n";
    }

    /**
     * Generate the resolver mappings array
     *
     * @return string
     */
    private function generateResolverMappings() : string
    {
        $mappings = [];

        foreach($this->services as $serviceName => $serviceDefinition)
        {
            $mappings[] = var_export($serviceName, true) . ' => ' . var_export($this->getResolverMethodName($serviceName), true);
        }

        return "protected \$resolverMethods = [" . implode(', ', $mappings) . "];\n";
    }

    /**
     * Generate the resolver methods
     *
     * @return string
     */
    private function generateResolverMethods() : string
    {
        $buffer = "";

        foreach($this->services as $serviceName => $serviceDefinition)
        {
            $buffer .= "protected function " . $this->getResolverMethodName($serviceName) . "() {\n";

            $serviceClassName = $serviceDefinition->getClassName();

            if ($serviceClassName[0] !== "\\")
            {
                $serviceClassName = "\\" . $serviceClassName;
            }

            $buffer .= "\t\$instance = new " . $serviceClassName . "(". $this->generateArgumentsCode($serviceDefinition->getArguments()) .");\n";

            foreach($serviceDefinition->getMethodCalls() as list($callName, $callArguments))
            {
                $buffer .= "\t\$instance->" . $callName . '('. $this->generateArgumentsCode($callArguments) .");\n";
            }

            if (in_array($serviceName, $this->shared))
            {
                $buffer .= "\t\$this->resolvedSharedServices[" . var_export($serviceName, true) . "] = \$instance;\n";
            }

            $buffer .= "\treturn \$instance;\n";

            $buffer .= "}\n";
        }

        return $buffer;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ParameterReferenceNode,
    Nodes\ServiceReferenceNode
};

class ReferenceParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        $token = $this->currentToken();

        // get the current value and remove the prefix
        $name = $token->getValue();
        $name = substr($name, 1);

        // is parameter reference
        if ($token->isType(T::TOKEN_PARAMETER))
        {
            return new ParameterReferenceNode($name);
        }

        // is dependency reference
        elseif ($token->isType(T::TOKEN_DEPENDENCY))
        {
            return new ServiceReferenceNode($name);
        }

        // anything else?
        else
        {
            throw $this->errorUnexpectedToken($token);
        }
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Nodes;

use ClanCats\Container\Exceptions\LogicalNodeException;

use ClanCats\Container\ContainerParser\{
    Token
};

class ValueNode extends BaseNode implements AssignableNode
{
    /**
     * Create value node from the given token
     *
     * @param Token         $token
     * @return ValueNode
     */
    public static function fromToken(Token $token) : ValueNode
    {
        if (!$token->isValue())
        {
            throw new LogicalNodeException('Can only create ValueNode from a value type Token.');
        }

        return new ValueNode($token->getValue(), $token->getType());
    }

    /**
	 * The value type
	 *
	 * @var string
	 */
	protected $type = null;

	/**
	 * The value value >.>
	 *
	 * @var mixed
	 */
	protected $value = null;

	/**
     * The value types
     *
     * This MUST equal the token type raw values!
     */
    const TYPE_STRING = 0;
    const TYPE_NUMBER = 1;
    const TYPE_BOOL_TRUE = 2;
    const TYPE_BOOL_FALSE = 3;
    const TYPE_NULL = 4;

	/**
     * Construct the value node
     *
     * @param mixed 			$value
     * @param strign 			$type
     * @return void
     */
    public function __construct($value, int $type)
    {
    	$this->setRawValue($value);
        $this->setType($type);
    }

    /**
     * Get the current rawValue
     *
     * @return mixed
     */
    public function getRawValue()
    {
        return $this->value;
    }

    /**
     * Return the current type
     *
     * @return int
     */
    public function getType() : int
    {
        return $this->type;
    }

    /**
     * Set the values value
     *
     * @param mixed 			$value
     * @return void
     */
    public function setRawValue($value)
    {
    	$this->value = $value;
    }

    /**
     * Validate if the given type and assing it
     *
     * @param int 				$type
     * @return void
     */
    public function setType(int $type)
    {
    	if (!in_array($type, [
            self::TYPE_STRING,
            self::TYPE_NUMBER,
            self::TYPE_BOOL_TRUE,
            self::TYPE_BOOL_FALSE,
            self::TYPE_NULL,
        ]))
        {
            throw new LogicalNodeException('Invalid value type assigned.');
        }

    	$this->type = $type;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use Composer\Script\Event;
use Composer\Package\CompletePackage;
use Composer\IO\ConsoleIO;

class ComposerContainerFileLoader
{
    /**
     * This method will generate a mapping file
     * of container files found in required composer packages.
     * This mapping file can be imported into a container namespace.
     *
     * @param Event             $event
     */
    public static function generateMap(Event $event)
    {
        // prepare the container file mapping array
        $mapping = [];

        // get all available packages
        $packages = $event->getComposer()->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();

        foreach ($packages as $package)
        {
            if ($package instanceof CompletePackage)
            {
                $extra = $package->getExtra();

                if (isset($extra['container']) && !empty($extra['container']))
                {
                    foreach($extra['container'] as $containerImportName => $containerFileName)
                    {
                        // special case for the main file
                        if ($containerImportName === '@main') {
                            $containerImportName = $package->getName();
                        } else {
                            $containerImportName = $package->getName() . '/' . $containerImportName;
                        }

                        $event->getIO()->write('found <fg=blue>'. $containerImportName .'</>', true, ConsoleIO::VERBOSE);

                        // also prefix the container file with package name
                        $containerFileName = $package->getName() . '/' . $containerFileName;

                        // assign the mapping
                        $mapping[$containerImportName] = '__DIR__' . $containerFileName;
                    }
                }
            }
        }

        $mappingFilePath = $event->getComposer()->getConfig()->get('vendor-dir') . '/container_map.php';

        // define the mapping file
        $mappingFileHeader = "<?php\n\$vendorDir = __DIR__ . '/';\n\nreturn ";
        $mappingFileContent = str_replace('\'__DIR__', '$vendorDir . \'', var_export($mapping, true));

        // store it
        file_put_contents($mappingFilePath, $mappingFileHeader . $mappingFileContent . ';');

        $event->getIO()->write("<fg=green>Generated container map file</>");
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser;

class Token
{
    /**
     * The type of this token.
     *
     * @var int
     */
    protected $type;

    /**
     * The value of this token.
     *
     * @var mixed
     */
    protected $value;

    /**
     * The line this token has been found in the code.
     *
     * @var int
     */
    protected $line = 0;

    /**
     * The token types
     */
    const TOKEN_STRING = 0;
    const TOKEN_NUMBER = 1;
    const TOKEN_BOOL_TRUE = 2;
    const TOKEN_BOOL_FALSE = 3;
    const TOKEN_NULL = 4;
    const TOKEN_DEPENDENCY = 5;
    const TOKEN_PARAMETER = 6;
    const TOKEN_COMMENT = 7;
    const TOKEN_LINE = 8;
    const TOKEN_SPACE = 9;
    const TOKEN_ASSIGN = 10;
    const TOKEN_IMPORT = 11;
    const TOKEN_USE = 12;
    const TOKEN_OVERRIDE = 13;
    const TOKEN_BRACE_OPEN = 14;
    const TOKEN_BRACE_CLOSE = 15;
    const TOKEN_SCOPE_OPEN = 16;
    const TOKEN_SCOPE_CLOSE = 17;
    const TOKEN_MINUS = 18;
    const TOKEN_SEPERATOR = 19;
    const TOKEN_PROTOTYPE = 20;
    const TOKEN_IDENTIFIER = 21;
    const TOKEN_EQUAL = 22;

    /**
     * The constructor
     *
     * @param int       $line The line the token is on.
     * @param int       $type The type of the token represented by an int.
     * @param mixed     $value The Value of the token.
     * @return void
     */
    public function __construct(int $line, int $type, $value)
    {
        $this->line = $line;
        $this->type = $type;
        $this->value = $value;
    }

    /**
     * Get the line of the token.
     *
     * @return int
     */
    public function getLine() : int
    {
        return $this->line;
    }

    /**
     * Get the type of the token represented as int.
     *
     * @return int
     */
    public function getType() : int
    {
        return $this->type;
    }

    /**
     * Is the token the given type
     *
     * @return bool
     */
    public function isType(int $type) : bool
    {
        return $this->type === $type;
    }

    /**
     * Get the tokens value as php native type.
     *
     * @return mixed
     */
    public function getValue()
    {
        $value = $this->value;

        switch ($this->type)
        {
            case self::TOKEN_BOOL_TRUE:
                $value = true;
                break;

            case self::TOKEN_BOOL_FALSE:
                $value = false;
                break;

            case self::TOKEN_STRING:
                $value = str_replace("\\", "", substr($value, 1, -1));
                break;

            case self::TOKEN_NUMBER:
                $value = $value + 0;
                break;

            case self::TOKEN_NULL:
                $value = null;
                break;
        }

        return $value;
    }

    /**
     * Is this a value token?
     *
     * @return bool
     */
    public function isValue() : bool
    {
        return
        $this->type === self::TOKEN_STRING ||
        $this->type === self::TOKEN_NUMBER ||
        $this->type === self::TOKEN_NULL ||
        $this->type === self::TOKEN_BOOL_TRUE ||
        $this->type === self::TOKEN_BOOL_FALSE;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Nodes;

use ClanCats\Container\Exceptions\LogicalNodeException;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node
};

class ArrayNode extends BaseNode implements AssignableNode
{
    /**
     * An array of ArrayElementNode
     *
     * @var [AssignableNode]
     */
    protected $elements = [];

    /**
     * The current arrays index
     *
     * @var int
     */
    private $index = 0;

    /**
     * Get array elements
     *
     * @return [ArrayElementNode]
     */
    public function getElements() : array
    {
        return $this->elements;
    }

    /**
     * Add an ArrayElementNode to the current array
     * this will not check for dublicates.
     *
     * @param $element              ArrayElementNode
     * @return void
     */
    public function addElement(ArrayElementNode $element)
    {
        $this->elements[] = $element;
    }

    /**
     * Checks if an element with the given key already exists
     *
     * @param string|int            $key
     * @return bool
     */
    public function has($key)
    {
        foreach ($this->elements as $element) {
            if ($element->getKey() == $key) {
                return true;
            }
        }

        return false;
    }

    /**
     * Pushes an assignable node to the array elements
     *
     * @param AssignableNode            $value
     * @return void
     */
    public function push(AssignableNode $value)
    {
        // count the index up to find a free spot
        while ($this->has($this->index)) {
            $this->index++;
        }

        $this->addElement(new ArrayElementNode($this->index, $value));
    }

    /**
     * Converts the current array to a PHP array
     * This will throw an exception when the array contains a reference
     * to a service or parameter
     *
     * @return array
     */
    public function convertToNativeArray() : array
    {
        $array = [];

        foreach($this->getElements() as $element)
        {
            $value = $element->getValue();

            if ($value instanceof ValueNode) {
                $array[$element->getKey()] = $value->getRawValue();
            }
            elseif ($value instanceof ArrayNode) {
                $array[$element->getKey()] = $value->convertToNativeArray();
            }
            else {
                throw new LogicalNodeException("You cannot convert a ctn array to PHP that contains a reference to a service or parameter.");
            }
        }

        return $array;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser;

use ClanCats\Container\{
    Exceptions\ContainerLexerException,

    // Alias the token as T
    ContainerParser\Token as T
};

class ContainerLexer
{
    /**
     * The current code we want to iterate trough
     *
     * @var string
     */
    protected $code = null;

    /**
     * The code lenght to iterate
     *
     * @var int
     */
    protected $length = 0;

    /**
     * The current string offset in the code
     *
     * @var int
     */
    protected $offset = 0;

    /**
     * The current line
     *
     * @var int
     */
    protected $line = 0;

    /**
     * Token map
     *
     * @var array
     */
    protected $tokenMap =
    [
        // strings
        '/^"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"/' => T::TOKEN_STRING,
        "/^'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/" => T::TOKEN_STRING,

        // numbers
        "/^[+\-]?([0-9]*[.])?[0-9]+/" => T::TOKEN_NUMBER,

        // bool
        "/^(true)/" => T::TOKEN_BOOL_TRUE,
        "/^(false)/" => T::TOKEN_BOOL_FALSE,

        // null
        "/^(null)/" => T::TOKEN_NULL,

        // container objects
        "/^(@[\w\-\/\.]+)/" => T::TOKEN_DEPENDENCY,
        "/^(:[\w\-\/\.]+)/" => T::TOKEN_PARAMETER,

        // comments
        "/^\/\/.*/" => T::TOKEN_COMMENT,
        "/^#.*/" => T::TOKEN_COMMENT,
        "/^\/\*(?:.|[\r\n])*?\*\//" => T::TOKEN_COMMENT,

        // markup
        "/^(\r\n|\n|\r)/" => T::TOKEN_LINE,
        "/^(\s)/" => T::TOKEN_SPACE,

        // keywords
        "/^(use )/" => T::TOKEN_USE,
        "/^(import )/" => T::TOKEN_IMPORT,
        "/^(override )/" => T::TOKEN_OVERRIDE,

        // scope
        "/^(\()/" => T::TOKEN_BRACE_OPEN,
        "/^(\))/" => T::TOKEN_BRACE_CLOSE,
        "/^(\{)/" => T::TOKEN_SCOPE_OPEN,
        "/^(\})/" => T::TOKEN_SCOPE_CLOSE,

        // syntax
        "/^(\:)/" => T::TOKEN_ASSIGN,
        "/^(\-)/" => T::TOKEN_MINUS,
        "/^(\=)/" => T::TOKEN_EQUAL,
        "/^(\,)/" => T::TOKEN_SEPERATOR,
        "/^(\?)/" => T::TOKEN_PROTOTYPE,

        // ids
        "/^([\w\-\/\\\\.]+)/" => T::TOKEN_IDENTIFIER,
    ];

    /**
     * The constructor
     *
     * @var string         $code
     * @return void
     */
    public function __construct(string $code)
    {
        // there is never a need for tabs or multiple whitespaces
        // so we remove them before assigning the code
        $this->code = trim(preg_replace("/[ \t]+/", ' ', $code));

        // we need to know the codes length
        $this->length = strlen($this->code);
    }

    /**
     * Get the current code
     *
     * @return string
     */
    public function code() : string
    {
        return $this->code;
    }

    /**
     * Get the next token from our code
     *
     * @throws ContainerLexerException
     *
     * @return string|false Returns `false` if everything has been parsed and we are done.
     */
    protected function next()
    {
        if ($this->offset >= $this->length)
        {
            return false;
        }

        foreach ($this->tokenMap as $regex => $token)
        {
            if (preg_match($regex, substr($this->code, $this->offset), $matches))
            {
                if ($token === T::TOKEN_LINE) {
                    $this->line++;
                }

                $this->offset += strlen($matches[0]);

                return new T($this->line + 1, $token, $matches[0]);
            }
        }

        throw new ContainerLexerException(sprintf('Unexpected character "%s" on line %s', $this->code[$this->offset], $this->line));
    }

    /**
     * Start the lexer and retrieve all resulting tokens.
     *
     * @return array[Token]
     */
    public function tokens() : array
    {
        $tokens = [];

        while ($token = $this->next())
        {
            // skip doublicated linebreaks
            if (
                $token->getType() === T::TOKEN_LINE &&
                isset($tokens[count($tokens) - 1]) &&
                $tokens[count($tokens) - 1]->getType() === T::TOKEN_LINE
            ) {
                continue;
            }

            $tokens[] = $token;
        }

        return $tokens;
    }
}


<?php

namespace Morphatic\AutoDeploy\Controllers;

use Log;
use Mail;
use Monolog\Logger;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use AdamBrett\ShellWrapper\Command\Builder as Command;
use AdamBrett\ShellWrapper\Command\CommandInterface;
use AdamBrett\ShellWrapper\Runners\Exec;
use Monolog\Formatter\HtmlFormatter;
use Monolog\Handler\SwiftMailerHandler;
use Morphatic\AutoDeploy\Origins\OriginInterface;

class DeployController extends Controller
{
    /**
     * The origin of the webhook request.
     *
     * @var Morphatic\AutoDeploy\Origins\OriginInterface
     */
    private $origin;

    /**
     * The URL of the repo to be cloned.
     *
     * @var string
     */
    private $repoUrl;

    /**
     * The absolute path of the directory on the server that contains the project.
     *
     * @var string
     */
    private $webroot;

    /**
     * The absolute path of the directory where the new deployment will be set up.
     *
     * @var string
     */
    private $installDir;

    /**
     * A log of the results of the entire deploy process.
     *
     * @var Monolog\Logger
     */
    private $log;

    /**
     * The commit ID for this commit.
     *
     * @var string
     */
    private $commitId;

    /**
     * The commit ID for this commit.
     *
     * @var AdamBrett\ShellWrapper\Runners\Exec
     */
    private $shell;

    /**
     * The result of this commit.
     *
     * @var array
     */
    private $result;

    /**
     * Create a new DeployController instance.
     *
     * @param Morphatic\AutoDeploy\Origins\OriginInterface $origin The origin of the webhook
     * @param AdamBrett\ShellWrapper\Runners\Exec          $exec   The shell command execution class
     */
    public function __construct(OriginInterface $origin, Exec $exec)
    {
        // set class variables related to the webhook origin
        $this->origin = $origin;
        $this->repoUrl = $this->origin->getRepoUrl();
        $this->commitId = $this->origin->getCommitId();

        // create an instance of the shell exec
        $this->shell = $exec;
    }

    /**
     * Handles incoming webhook requests.
     */
    public function index()
    {
        // set up logging to email
        $this->log = Log::getMonolog();
        if ($to = config('auto-deploy.notify')) {
            $domain = parse_url(config('app.url'), PHP_URL_HOST);
            $msg = \Swift_Message::newInstance('Project Deployed')
                    ->setFrom(["do_not_reply@$domain" => "Laravel Auto-Deploy[$domain]"])
                    ->setTo($to)
                    ->setBody('', 'text/html');
            $handler = new SwiftMailerHandler(Mail::getSwiftMailer(), $msg, Logger::NOTICE);
            $handler->setFormatter(new HtmlFormatter());
            $this->log->pushHandler($handler);
        }

        // check to see if we should execute this event
        if (in_array($this->origin->event(), array_keys(config("auto-deploy.{$this->origin->name}")))) {
            // get the parameters for the event we're handling
            $configKey = "auto-deploy.{$this->origin->name}.{$this->origin->event()}";
            $this->webroot = config("$configKey.webroot");
            $this->installDir = dirname($this->webroot).'/'.date('Y-m-d').'_'.$this->commitId;
            $steps = config("$configKey.steps");

            // execute the configured steps
            $this->result = [
                'Commit_ID' => $this->commitId,
                'Timestamp' => date('r'),
                'output' => '',
            ];
            $whitelist = ['backupDatabase','pull','copyEnv','composer','npm','migrate','seed','deploy'];
            foreach ($steps as $step) {
                if (in_array($step, $whitelist) && !$this->{$step}()) {
                    $this->log->error('Deploy failed.', $this->result);

                    return;
                }
            }
            $this->log->notice('Deploy succeeded!', $this->result);
        } else {
            $this->log->error('Deploy failed.', ['Reason' => 'This event was not configured.']);
        }
    }

    /**
     * Runs a shell command, logs, and handles the result.
     *
     * @param AdamBrett\ShellWrapper\CommandInterface $cmd The text of the command to be run
     *
     * @return bool True if the command was successful, false on error
     */
    private function ex(CommandInterface $cmd)
    {
        // try to run the command
        $this->shell->run($cmd);
        $output = $this->shell->getOutput();
        $returnValue = $this->shell->getReturnValue();

        // record the result
        $output = count($output) ? implode("\n", $output)."\n" : '';
        $this->result['output'] .= "$cmd\n$output";

        // return a boolean
        return 0 === $returnValue;
    }

    /**
     * Backup the database.
     *
     * @return bool True if the database was successfully backed up. False on error.
     */
    private function backupDatabase()
    {
        // get the name of the DB to backed up and the connection to use
        $dbdir = database_path();
        $dbconn = config('database.default');
        $dbname = config("database.connections.$dbconn.database");

        // make a directory for the backup file and switch into that directory
        $cmd = new Command('cd');
        $cmd->addParam($dbdir)
            ->addSubCommand('&&')
            ->addSubCommand('mkdir')
            ->addParam('backups');
        if ($this->ex($cmd)) {
            $cmd = new Command('cd');
            $cmd->addParam($dbdir.'/backups')
                ->addSubCommand('&&');
            switch ($dbconn) {
                case 'sqlite':
                    $cmd->addSubCommand('cp');
                    $cmd->addParam($dbname)
                        ->addParam('.');

                    return $this->ex($cmd);
                case 'mysql':
                    $cmd->addSubCommand('mysqldump');
                    $cmd->addParam($dbname)
                        ->addParam('>')
                        ->addParam("$dbname.sql");

                    return $this->ex($cmd);
                case 'pgsql':
                    $cmd->addSubCommand('pg_dump');
                    $cmd->addParam($dbname)
                        ->addParam('>')
                        ->addParam("$dbname.sql");

                    return $this->ex($cmd);
            }
        }

        return false;
    }

    /**
     * Create a new directory parallel to the webroot and clone the project into that directory.
     *
     * @return bool True if the clone is successful. False otherwise.
     */
    private function pull()
    {
        if (is_writable(dirname($this->installDir))) {
            $cmd = new Command('mkdir');
            $cmd->addFlag('p')
                ->addParam($this->installDir);
            if ($this->ex($cmd)) {
                $cmd = new Command('cd');
                $cmd->addParam($this->installDir)
                    ->addSubCommand('&&')
                    ->addSubCommand('git')
                    ->addSubCommand('clone')
                    ->addParam($this->repoUrl)
                    ->addParam('.');

                return $this->ex($cmd);
            }
        }

        return false;
    }

    /**
     * Copy the .env file from the new deploy directory.
     *
     * @return bool True if the update is successful. False otherwise.
     */
    private function copyEnv()
    {
        $cmd = new Command('cp');
        $cmd->addParam($this->webroot.'./env')
            ->addParam($this->installDir.'/.env');

        return $this->ex($cmd);
    }

    /**
     * Update composer and run composer update.
     *
     * @return bool True if the update is successful. False otherwise.
     */
    private function composer()
    {
        $cmd = new Command('cd');
        $cmd->addParam($this->installDir)
            ->addSubCommand('&&')
            ->addSubCommand('composer')
            ->addParam('self-update')
            ->addSubCommand('&&')
            ->addSubCommand('composer')
            ->addParam('update')
            ->addArgument('no-interaction');

        return $this->ex($cmd);
    }

    /**
     * Run npm update.
     *
     * @return bool True if npm is successful. False otherwise.
     */
    private function npm()
    {
        $cmd = new Command('cd');
        $cmd->addParam($this->installDir)
            ->addSubCommand('&&')
            ->addSubCommand('npm')
            ->addParam('update');

        return $this->ex($cmd);
    }

    /**
     * Run any necessary database migrations.
     *
     * @return bool True if the migration is successful. False otherwise.
     */
    private function migrate()
    {
        $cmd = new Command('cd');
        $cmd->addParam($this->installDir)
            ->addSubCommand('&&')
            ->addSubCommand('php')
            ->addSubCommand('artisan')
            ->addParam('migrate')
            ->addArgument('force')
            ->addArgument('no-interaction');

        return $this->ex($cmd);
    }

    /**
     * Run any necessary database migrations.
     *
     * @return bool True if the migration is successful. False otherwise.
     */
    private function seed()
    {
        $cmd = new Command('cd');
        $cmd->addParam($this->installDir)
            ->addSubCommand('&&')
            ->addSubCommand('php')
            ->addSubCommand('artisan')
            ->addParam('db:seed');

        return $this->ex($cmd);
    }

    /**
     * Symlinks the new deploy directory to the webroot.
     *
     * @return bool True if the symlink is successful. False otherwise.
     */
    private function deploy()
    {
        $cmd = new Command('cd');
        $cmd->addParam(dirname($this->webroot))
            ->addSubCommand('&&')
            ->addSubCommand('ln')
            ->addFlag('fs')
            ->addParam($this->installDir)
            ->addParam($this->webroot);

        return $this->ex($cmd);
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use ClanCats\Container\{
    Exceptions\UnknownServiceException,
    Exceptions\InvalidServiceException
};

class ServiceProviderArray implements ServiceProviderInterface
{
    /**
     * Array of to be provided services
     * You can subclass the ServiceProviderArray and simply overwrite
     * this attribute. You can also create an instance of the array provider and
     * set your services using the `setServices() method.
     *
     * @var array
     */
    protected $services = [];

    /**
     * Set the providers services
     *
     * @param array             $services
     */
    public function setServices(array $services)
    {
        $this->services = $services;
    }

    /**
     * What services are provided by the service provider
     *
     * @return array[string]
     */
    public function provides() : array
    {
        return array_keys($this->services);
    }

    /**
     * Resolve a service with the given name
     *
     * @param string                    $serviceName
     * @param Container                 $container
     * @return array[mixed, bool]
     */
    public function resolve(string $serviceName, Container $container) : array
    {
        if (!isset($this->services[$serviceName]))
        {
            throw new UnknownServiceException('The service provider "' . get_class($this) . '" does not support service resolving of the service "' . $serviceName . '"');
        }

        $serviceConfiguration = $this->services[$serviceName];

        // create the factory
        $factory = ServiceFactory::fromArray($serviceConfiguration);

        // resolve the service
        $service = $factory->create($container);

        // if the service is not shared skip the store
        if ((!isset($serviceConfiguration['shared'])) || $serviceConfiguration['shared'] !== false)
        {
            return [$service, true];
        }

        return [$service, false];
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use ClanCats\Container\{
    Exceptions\ContainerException
};

class ContainerFactory
{
    /**
     * Service loader file cache
     *
     * @var string
     */
    protected $cacheDirectory = null;

    /**
     * Is the factory in debug mode
     *
     * @var bool
     */
    protected $debugMode = false;

    /**
     * Construct a new service loader with a given cache directory
     *
     * @param string                $cacheDirectory
     * @return void
     */
    public function __construct(string $cacheDirectory, bool $debugMode = false)
    {
        $this->setCacheDirecotry($cacheDirectory);
        $this->debugMode = $debugMode;
    }

    /**
     * Check if the container factory is in debug mode.
     *
     * @return bool
     */
    public function isDebugMode() : bool
    {
        return $this->debugMode;
    }

    /**
     * Set the cache directory
     *
     * @param string        $cacheDirectory
     * @return void
     */
    public function setCacheDirecotry(string $cacheDirectory)
    {
        if (substr($cacheDirectory, -1) !== DIRECTORY_SEPARATOR)
        {
            $cacheDirectory .= DIRECTORY_SEPARATOR;
        }

        $this->cacheDirectory = $cacheDirectory;
    }

    /**
     * Get the current cache directory
     *
     * @return string The cache directory used by the factory.
     */
    public function getCacheDirectory() : string
    {
        return $this->cacheDirectory;
    }

    /**
     * Create a container with the given name. You can pass an
     * array with services
     *
     * @param string                $containerName
     * @param callable              $builderCallback
     * @param array                 $initalParameters
     *
     * @return Container The generated container instnace.
     */
    public function create(string $containerName, callable $builderCallback, array $initalParameters = []) : Container
    {
        if (class_exists($containerName))
        {
            return new $containerName;
        }

        $fileName = basename(str_replace("\\", '/', $containerName));
        $cacheFile = $this->cacheDirectory . $fileName . '.php';

        if ((!(file_exists($cacheFile) && is_readable($cacheFile))) || $this->isDebugMode())
        {
            $builder = new ContainerBuilder($containerName);

            // run the builder callback
            $builderCallback($builder);

            // store the cache file
            $cacheDir = dirname($cacheFile);

            if (!is_dir($cacheDir) || !is_writable($cacheDir)) {
                throw new ContainerException("The directory \"{$cacheDir}\" is not writable. Cannot generate container class.");
            } elseif (is_file($cacheFile) && !is_writable($cacheFile)) {
                throw new ContainerException("The file \"{$cacheFile}\" is not writable. Cannot generate container class.");
            }

            file_put_contents($cacheFile, $builder->generate());
        }

        // require the generated cache file
        require_once $cacheFile;

        // create an instance of the generated container
        return new $containerName($initalParameters);
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ScopeImportNode
};

class ScopeImportParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        if (!$this->currentToken()->isType(T::TOKEN_IMPORT))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $this->skipToken(); // skip the import keyword

        // the next token must be an identifier
        if (!$this->currentToken()->isType(T::TOKEN_IDENTIFIER))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $node = new ScopeImportNode;
        $node->setPath($this->currentToken()->getValue());

        // skip the path
        $this->skipToken();

        return $node;
    }
}


<?php

namespace Morphatic\AutoDeploy\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerifyDeployRequest
{
    protected $origins = [];

    public function __construct(Request $request)
    {
        foreach (config('auto-deploy.origins') as $origin) {
            $type = "\Morphatic\AutoDeploy\Origins\\$origin";
            $this->origins[$origin] = new $type($request);
        }
    }

    /**
     * Handles the HTTP request.
     *
     * @param Illuminate\Http\Request $request The request
     * @param Closure                 $next    Mechanism for passing the result down the pipeline to the next piece of middleware
     *
     * @return Illuminate\Http\Response A Response object that is passed to the next piece of middleware
     */
    public function handle($request, Closure $next)
    {
        if ($request->path() === config('auto-deploy.route')) {
            if (!config('auto-deploy.require-ssl') || $request->secure()) {
                $origin = $this->determineOrigin();
                if (null !== $origin) {
                    if ($origin->isAuthentic()) {
                        // set the origin type in the controller
                        $request->offsetSet('origin', $origin);

                        return $next($request);
                    } else {
                        abort(403, 'Forbidden. Could not verify the origin of the request.');
                    }
                } else {
                    abort(403, 'Forbidden. Could not determine the origin of the request.');
                }
            } else {
                abort(403, 'Forbidden. Webhook requests must be sent using SSL.');
            }
        }
        // Passthrough if it's not our specific route
        return $next($request);
    }

    /**
     * Determine the origin of a deploy request.
     *
     * @return \Morphatic\AutoDeploy\Origins\OriginInterface An object corresponding to the origin type or null.
     */
    private function determineOrigin()
    {
        foreach ($this->origins as $origin) {
            if ($origin->isOrigin()) {
                return $origin;
            }
        }
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use ClanCats\Container\{
    Exceptions\ContainerNamespaceException,
    ServiceDefinition
};

use ClanCats\Container\ContainerParser\{
    ContainerLexer,
    ContainerInterpreter,
    Parser\ScopeParser
};

/**
 * The container namespace acts as a collection of multiple
 * container files that get parsed into one pot.
 */
class ContainerNamespace
{
    /**
     * The container namespaces parameters
     *
     * @var array
     */
    protected $parameters = [];

    /**
     * The container namespaces service defintions
     *
     * @param array[string => Service]
     */
    protected $services = [];

    /**
     * An array of service names that should be shared through the container
     *
     * @param array[string]
     */
    protected $shared = [];

    /**
     * An array of paths
     *
     *     name => container file path
     *
     * @var array
     */
    protected $paths = [];

    /**
     * Constructor
     *
     * @param $paths array[string:string]
     */
    public function __construct(array $paths = [])
    {
        $this->paths = $paths;
    }

    /**
     * Import paths from vendor container map
     *
     * @param string                $vendorDir
     * @return void
     */
    public function importFromVendor(string $vendorDir)
    {
        $mappingFile = $vendorDir . '/container_map.php';

        if (!(file_exists($mappingFile) && is_readable($mappingFile)))
        {
            throw new ContainerNamespaceException("Could not find the the container map file at: " . $mappingFile);
        }

        $vendorPaths = require $mappingFile;
        $this->paths = array_merge($vendorPaths, $this->paths);
    }

    /**
     * Does the container namespace have a parameter with the given name?
     *
     * @param string            $name The parameter name.
     * @return bool
     */
    public function hasParameter(string $name) : bool
    {
        return array_key_exists($name, $this->parameters);
    }

    /**
     * Set the given parameter and value
     *
     * @param string            $name The parameter name.
     * @param mixed             $value The parameter value.
     * @return void
     */
    public function setParameter(string $name, $value)
    {
        $this->parameters[$name] = $value;
    }

    /**
     * Get all parameters from the container namespace
     *
     * @return array
     */
    public function getParameters() : array
    {
        return $this->parameters;
    }

    /**
     * Does the container namespace have a service with the given name?
     *
     * @param string            $name
     * @return bool
     */
    public function hasService(string $name) : bool
    {
        return array_key_exists($name, $this->services);
    }

    /**
     * Set a service in the namespace
     *
     * @param string            $name The service name.
     * @param mixed             $value The service definition.
     * @return void
     */
    public function setService(string $name, ServiceDefinition $service)
    {
        $this->services[$name] = $service;
    }

    /**
     * Get all services from the container namespace
     *
     * @return array[ServiceDefinition]
     */
    public function getServices() : array
    {
        return $this->services;
    }

    /**
     * Is the given path name binded?
     *
     * @param string            $name The container files path key.
     * @return bool
     */
    public function has(string $name) : bool
    {
        return isset($this->paths[$name]) && is_string($this->paths[$name]);
    }

    /**
     * Simply returns the contents of the given file
     *
     * @param return string         $containerFilePath The path to a container file.
     * @return string
     */
    protected function getCodeFromFile(string $containerFilePath) : string
    {
        if (!file_exists($containerFilePath) || !is_readable($containerFilePath))
        {
            throw new ContainerNamespaceException("The file '" . $containerFilePath . "' is not readable or does not exist.");
        }

        return file_get_contents($containerFilePath);
    }

    /**
     * Returns the code of in the current namespace binded file.
     *
     * @return string           $name The container files path key.
     */
    public function getCode(string $name) : string
    {
        if (!$this->has($name))
        {
            throw new ContainerNamespaceException("There is no path named '" . $name . "' binded to the namespace.");
        }

        return $this->getCodeFromFile($this->paths[$name]);
    }

    /**
     * Parse the given container file with the current namespace
     *
     * @param string        $containerFilePath The path to a container file.
     */
    public function parse(string $containerFilePath)
    {
        // create a lexer from the given file
        $lexer = new ContainerLexer($this->getCodeFromFile($containerFilePath));

        // parse the file
        $parser = new ScopeParser($lexer->tokens());

        // interpret the parsed node
        $interpreter = new ContainerInterpreter($this);
        $interpreter->handleScope($parser->parse());
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser;

use ClanCats\Container\Exceptions\ContainerInterpreterException;

use ClanCats\Container\ContainerNamespace;
use ClanCats\Container\ServiceDefinition;
use ClanCats\Container\ServiceArguments;

use ClanCats\Container\ContainerParser\{
    ContainerLexer,
    Parser\ScopeParser
};

use ClanCats\Container\ContainerParser\Nodes\{
    BaseNode as Node,

    // the nodes
    ValueNode,
    ArrayNode,
    ScopeNode,
    ScopeImportNode,
    ParameterDefinitionNode,
    ServiceDefinitionNode,
    ParameterReferenceNode,
    ServiceReferenceNode,
    ServiceMethodCallNode,
    MetaDataAssignmentNode,
    ArgumentArrayNode
};

class ContainerInterpreter
{
    /**
     * The container definitions namesapce
     *
     * @var ContainerNamespace
     */
    protected $namespace;

    /**
     * Construct a new container file interpreter
     *
     * @param ContainerNamespace                $namespace
     * @return void
     */
    public function __construct(ContainerNamespace $namespace)
    {
        $this->namespace = $namespace;
    }

	/**
	 * Handle a container file scope
	 *
	 * @param ScopeNode 			$scope
	 * @return void
	 */
    public function handleScope(ScopeNode $scope)
    {
    	foreach($scope->getNodes() as $node)
    	{
    		if ($node instanceof ScopeImportNode)
    		{
    			$this->handleScopeImport($node);
    		}
            elseif ($node instanceof ParameterDefinitionNode)
            {
                $this->handleParameterDefinition($node);
            }
            elseif ($node instanceof ServiceDefinitionNode)
            {
                $this->handleServiceDefinition($node);
            }
    		else
    		{
    			throw new ContainerInterpreterException("Unexpected node in scope found.");
    		}
    	}
    }

    /**
     * Handle a scope import statement
     *
     * @param ScopeImportNode           $import
     * @return void
     */
    public function handleScopeImport(ScopeImportNode $import)
    {
        $path = $import->getPath();

        if (is_null($path) || empty($path))
        {
            throw new ContainerInterpreterException("An import statement cannot be empty.");
        }

        $code = $this->namespace->getCode($path);

        // after retrieving new code we have
        // to start a new lexer & and parser
        $lexer = new ContainerLexer($code);
        $parser = new ScopeParser($lexer->tokens());
        $scopeNode = $parser->parse();

        unset($lexer, $parser);

        // and continue handling the importet scope
        $this->handleScope($scopeNode);
    }

    /**
     * Handle a parameter definition
     *
     * @param ParameterDefinitionNode 			$definition
     * @return void
     */
    public function handleParameterDefinition(ParameterDefinitionNode $definition)
    {
        if ($this->namespace->hasParameter($definition->getName()) && $definition->isOverride() === false)
        {
            throw new ContainerInterpreterException("A parameter named \"{$definition->getName()}\" is already defined, you can prefix the definition with \"override\" to get around this error.");
        }

        if ($definition->getValue() instanceof ValueNode) {
            $this->namespace->setParameter($definition->getName(), $definition->getValue()->getRawValue());
        }
        elseif ($definition->getValue() instanceof ArrayNode) {
            $this->namespace->setParameter($definition->getName(), $definition->getValue()->convertToNativeArray());
        }
        else {
            throw new ContainerInterpreterException("Invalid parameter value given for key \"{$definition->getName()}\".");
        }
    }

    /**
     * Create final service arguments from an arguments array node
     *
     * @param ArgumentArrayNode         $argumentsNode
     * @return ServiceArguments
     */
    protected function createServiceArgumentsFromNode(ArgumentArrayNode $argumentsNode) : ServiceArguments
    {
        $arguments = $argumentsNode
            ->getArguments();

        $definition = new ServiceArguments();

        foreach($arguments as $argument)
        {
            if ($argument instanceof ServiceReferenceNode)
            {
                $definition->addDependency($argument->getName());
            }
            elseif ($argument instanceof ParameterReferenceNode)
            {
                $definition->addParameter($argument->getName());
            }
            elseif ($argument instanceof ArrayNode)
            {
                $definition->addRaw($argument->convertToNativeArray());
            }
            elseif ($argument instanceof ValueNode)
            {
                $definition->addRaw($argument->getRawValue());
            }
            else
            {
                throw new ContainerInterpreterException("Unable to handle argument node of type \"" . get_class($argument) . "\".");
            }
        }

        return $definition;
    }

    /**
     * Handle a service definition
     *
     * @param ParameterDefinitionNode           $definition
     * @return void
     */
    public function handleServiceDefinition(ServiceDefinitionNode $definition)
    {
        if ($this->namespace->hasService($definition->getName()) && $definition->isOverride() === false)
        {
            throw new ContainerInterpreterException("A service named \"{$definition->getName()}\" is already defined, you can prefix the definition with \"override\" to get around this error.");
        }

        // create a service definition from the node
        $service = new ServiceDefinition($definition->getClassName());

        if ($definition->hasArguments())
        {
            $arguments = $definition
                ->getArguments() // get the definitions arguments
                ->getArguments(); // and the argument array from the object

            foreach($arguments as $argument)
            {
                if ($argument instanceof ServiceReferenceNode)
                {
                    $service->addDependencyArgument($argument->getName());
                }
                elseif ($argument instanceof ParameterReferenceNode)
                {
                    $service->addParameterArgument($argument->getName());
                }
                elseif ($argument instanceof ArrayNode)
                {
                    $service->addRawArgument($argument->convertToNativeArray());
                }
                elseif ($argument instanceof ValueNode)
                {
                    $service->addRawArgument($argument->getRawValue());
                }
                else
                {
                    throw new ContainerInterpreterException("Unable to handle argument node of type \"" . get_class($argument) . "\".");
                }
            }
        }

        // handle construction actions
        foreach($definition->getConstructionActions() as $action)
        {
            if ($action instanceof ServiceMethodCallNode)
            {
                if ($action->hasArguments())
                {
                    $service->addMethodCall($action->getName(), $this->createServiceArgumentsFromNode($action->getArguments()));
                } else {
                    $service->calls($action->getName());
                }
            }
            else
            {
                throw new ContainerInterpreterException("Invalid construction action of type \"" . get_class($action) . "\" given.");
            }
        }

        // handle meta data
        foreach($definition->getMetaDataAssignemnts() as $meta)
        {
            if ($meta instanceof MetaDataAssignmentNode)
            {
                if ($meta->hasData())
                {
                    $service->addMetaData($meta->getKey(), $meta->getData()->convertToNativeArray());
                } else {
                    $service->addMetaData($meta->getKey(), []);
                }
            }
            else
            {
                throw new ContainerInterpreterException("Invalid meta data assignment \"" . get_class($meta) . "\" given.");
            }
        }

        // add the node to the namespace
        $this->namespace->setService($definition->getName(), $service);
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use ClanCats\Container\{
    Exceptions\InvalidServiceException
};

class ServiceDefinition implements ServiceDefinitionInterface
{
    /**
     * Static instance constructor to allow eye candy like:
     *
     *     ServiceDefinition::for('\Acme\SessionService')
     *        ->addDependencyArgument('storage')
     *        ->addParameterArgument('session_token')
     *        ->addRawArgument(600)
     *
     * Or the shorter way
     *
     *     ServiceDefinition::for('\Acme\SessionService', ['@storage', ':session_token', 600])
     *
     * @param string            $serviceClassName The full class name of the desired service.
     * @param array             $arguments An array of constructor arguments for the service.
     */
    public static function for(string $serviceClassName, array $arguments = [])
    {
        return new static($serviceClassName, $arguments);
    }

    /**
     * Construct a single service definition object from an array
     *
     *     ServiceDefinition::fromArray([
     *         'class' => '\Acme\Demo',
     *         'arguments' => ['@foo', ':bar'],
     *         'calls' => [
     *             ['method' => 'setName', [':demo.name']]
     *         ]
     *     ])
     *
     * @param array             $serviceConfiguration
     * @return static
     */
    public static function fromArray(array $serviceConfiguration)
    {
        if (!isset($serviceConfiguration['class']))
        {
            throw new InvalidServiceException('The service configuration must define a "class" attribute.');
        }

        // construct the service definition
        $defintion = new static($serviceConfiguration['class'], $serviceConfiguration['arguments'] ?? []);

        // add service method calls if configured
        if (isset($serviceConfiguration['calls']))
        {
            foreach($serviceConfiguration['calls'] as $call)
            {
                if (isset($call['method']) && isset($call['arguments']))
                {
                    $defintion->calls($call['method'], $call['arguments']);
                }
                else
                {
                    throw new InvalidServiceException('Every service call must have the attributes "method" and "arguments".');
                }
            }
        }

        return $defintion;
    }

    /**
     * The services class name
     *
     * @var string
     */
    protected $className;

    /**
     * The consturcor arguments of the service
     *
     * @var ServiceArguments
     */
    protected $constructorArguments;

    /**
     * An array of method calls after service construction
     *
     * @var array[[<method name>, ServiceArguments]]
     */
    protected $methodCallers = [];

    /**
     * Meta data for this service definition
     *
     * @var array[string => array]
     */
    protected $metaData = [];

    /**
     * Construct a new service definition with the given classname and optional arguments as array.
     *
     * @param string            $className The full class name of the desired service.
     * @param array             $arguments An array of constructor arguments for the service.
     */
    public function __construct(string $className, array $arguments = [])
    {
        $this->className = $className;
        $this->constructorArguments = new ServiceArguments($arguments);
    }

    /**
     * Returns the service class name
     *
     * @return string
     */
    public function getClassName() : string
    {
        return $this->className;
    }

    /**
     * Add an array of arguments to the service constructor
     *
     * @param array             $arguments
     * @return self
     */
    public function arguments(array $arguments) : ServiceDefinition
    {
        $this->constructorArguments->addArgumentsFromArray($arguments); return $this;
    }

    /**
     * Add a simple raw constructor argument.
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addRawArgument($argumentValue) : ServiceDefinition
    {
        $this->constructorArguments->addRaw($argumentValue); return $this;
    }

    /**
     * Add a dependency constructor argument.
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addDependencyArgument($argumentValue) : ServiceDefinition
    {
        $this->constructorArguments->addDependency($argumentValue); return $this;
    }

    /**
     * Add a simply parameter constructor argument.
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addParameterArgument($argumentValue) : ServiceDefinition
    {
        $this->constructorArguments->addParameter($argumentValue); return $this;
    }

    /**
     * Returns the constructor arguments object
     *
     * @return ServiceArguments
     */
    public function getArguments() : ServiceArguments
    {
        return $this->constructorArguments;
    }

    /**
     * Adds a method call to the service definition, the arguments should be set as an array.
     *
     * @param string            $method The name of the method to be called.
     * @param array             $arguments The method arguments as array.
     * @return self
     */
    public function calls(string $method, array $arguments = []) : ServiceDefinition
    {
        return $this->addMethodCall($method, new ServiceArguments($arguments));
    }

    /**
     * Adds a method call to the service definition, the arguments must be set with a ServiceArguments instance.
     *
     * @param string                    $methodName The name of the method to be called.
     * @param ServiceArguments          $arguments An `ServiceArguments` instance for the method call.
     * @return self
     */
    public function addMethodCall(string $methodName, ServiceArguments $arguments) : ServiceDefinition
    {
        $this->methodCallers[] = [$methodName, $arguments]; return $this;
    }

    /**
     * Returns all registered method calls
     *
     * @return array
     */
    public function getMethodCalls() : array
    {
        return $this->methodCallers;
    }

    /**
     * Sets meta data for the given key
     *
     * @param string        $key
     * @param array         $values
     * @return self
     */
    public function addMetaData(string $key, array $values)
    {
        if (!isset($this->metaData[$key])) {
            $this->metaData[$key] = [];
        }

        $this->metaData[$key][] = $values; return $this;
    }

    /**
     * Get all metadata of the service definition
     *
     * @return array
     */
    public function getMetaData() : array
    {
        return $this->metaData;
    }
}


<?php

namespace Morphatic\AutoDeploy\Origins;

class Github extends AbstractOrigin implements OriginInterface
{
    /**
     * The name of the origin.
     *
     * @var string
     */
    public $name = 'Github';

    /**
     * Determines whether or not the Request originated from Github.
     *
     * @return bool Returns true if the request originated from Github. False otherwise.
     */
    public function isOrigin()
    {
        // Correct IP range for Github maintained here:
        // https://help.github.com/articles/what-ip-addresses-does-github-use-that-i-should-whitelist/
        $hasGithubHeader = false !== strpos($this->request->header('User-Agent'), 'GitHub-Hookshot');
        $hasGithubIp = $this->isIpInRange($this->request->server('REMOTE_ADDR'), '192.30.252.0', 22);

        return $hasGithubHeader && $hasGithubIp;
    }

    /**
     * Verifies the authenticity of a webhook request from Github.
     *
     * Follows the procedure described here: https://developer.github.com/webhooks/securing/
     *
     * @return bool Returns true if the request is authentic. False otherwise.
     */
    public function isAuthentic()
    {
        // get the Github signature
        $xhub = $this->request->header('X-Hub-Signature') ?: 'nothing';

        // reconstruct the hash on this side
        $hash = 'sha1='.hash_hmac('sha1', $this->request->getContent(), config('auto-deploy.secret'));

        // securely compare them
        return hash_equals($xhub, $hash);
    }

    /**
     * Gets the event the triggered the webhook request.
     *
     * @return string The name of the event, e.g. push, release, create, etc.
     */
    public function event()
    {
        return $this->request->header('X-GitHub-Event');
    }

    /**
     * Gets the URL to be cloned from.
     *
     * @return string The URL of the repo.
     */
    public function getRepoUrl()
    {
        return $this->request->json('repository.clone_url');
    }

    /**
     * Gets the ID of the commit that is to be cloned.
     *
     * @return string The commit ID.
     */
    public function getCommitId()
    {
        return $this->request->json('after');
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ScopeNode
};

class ScopeParser extends ContainerParser
{
    /**
     * The current scope node
     *
     * @param ScopeNode
     */
    protected $scope;

    /**
     * Prepare the current parser
     *
     * @return void
     */
    protected function prepare()
    {
        $this->scope = new ScopeNode;
    }

    /**
     * Return the current result
     *
     * @return null|Node
     */
    protected function node() : Node
    {
        return $this->scope;
    }

    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        $token = $this->currentToken();

        // is the current state in override mode?
        if ($token->isType(T::TOKEN_OVERRIDE))
        {
            // set the indicating token to the next one
            $token = $this->nextToken();

            // only allow override of service and parameter definitions
            if (!($token->isType(T::TOKEN_PARAMETER) || $token->isType(T::TOKEN_DEPENDENCY)))
            {
                throw $this->errorUnexpectedToken($this->currentToken());
            }
        }

        // is parameter definition
        if ($token->isType(T::TOKEN_PARAMETER))
        {
            $this->scope->addNode($this->parseChild(ParameterDefinitionParser::class));
        }

        // is service definition
        elseif ($token->isType(T::TOKEN_DEPENDENCY))
        {
            $this->scope->addNode($this->parseChild(ServiceDefinitionParser::class));
        }

        // import another scope
        elseif ($token->isType(T::TOKEN_IMPORT))
        {
            $this->scope->addNode($this->parseChild(ScopeImportParser::class));
        }

        // just a linebreak
        elseif ($token->isType(T::TOKEN_LINE))
        {
            $this->skipToken();
        }

        // anything else?
        else
        {
            throw $this->errorUnexpectedToken($token);
        }
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ArrayNode,
    Nodes\ArrayElementNode,
    Nodes\ParameterReferenceNode,
    Nodes\ServiceReferenceNode,
    Nodes\ValueNode
};

class ArrayParser extends ContainerParser
{
    /**
     * The current arguments node
     *
     * @param ArgumentArrayNode
     */
    protected $array;

    /**
     * Prepare the current parser
     *
     * @return void
     */
    protected function prepare()
    {
        $this->array = new ArrayNode;
    }

    /**
     * Return the current result
     *
     * @return null|Node
     */
    protected function node() : Node
    {
        return $this->array;
    }

    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        // is the current element associative?
        $associativeArray = $this->nextToken() && $this->nextToken()->isType(T::TOKEN_ASSIGN);
        $associativeKeyName = null;

        // if yes, we parse the key first
        if ($associativeArray)
        {
            $keyToken = $this->currentToken();

            // the key can be an identifier token and will
            // be used as a simple string
            if ($keyToken->isType(T::TOKEN_IDENTIFIER))
            {
                $associativeKeyName = $keyToken->getValue();
            }
            // string or numbers
            elseif ($keyToken->isType(T::TOKEN_STRING) || $keyToken->isType(T::TOKEN_NUMBER))
            {
                $associativeKeyName = ValueNode::fromToken($keyToken)->getRawValue();
            }

            $this->skipToken(2); // skip the key & assign token

            // there might be a linebreak between the key and value
            $this->skipTokenOfType([T::TOKEN_LINE]);
        }

        // placeholder for the elements value
        $elementValue = null;

        // value token
        $token = $this->currentToken();

        // check for nested array
        if ($token->isType(T::TOKEN_SCOPE_OPEN))
        {
            $elementValue = $this->parseChild(
                ArrayParser::class,
                $this->getTokensUntilClosingScope(
                    false,
                    T::TOKEN_SCOPE_OPEN,
                    T::TOKEN_SCOPE_CLOSE
                ),
                false
            );
        }

        // is a parameter reference
        elseif ($token->isValue())
        {
            $elementValue = ValueNode::fromToken($token);
        }

        elseif ($token->isType(T::TOKEN_PARAMETER))
        {
            $elementValue = $this->parseChild(ReferenceParser::class);
        }

        // is a service reference
        elseif ($token->isType(T::TOKEN_DEPENDENCY))
        {
            $elementValue = $this->parseChild(ReferenceParser::class);
        }

        // just a linebreak
        elseif ($token->isType(T::TOKEN_LINE))
        {
            $this->skipToken(); return;
        }

        // anything else?
        else
        {
            throw $this->errorUnexpectedToken($token);
        }

        // update our array node
        if ($associativeArray) {
            $this->array->addElement(new ArrayElementNode($associativeKeyName, $elementValue));
        } else {
            $this->array->push($elementValue);
        }

        // skip the value token
        $this->skipToken();

        // now ther might follow a seperator indicating another argument
        if (!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_SEPERATOR))
        {
            $this->skipToken();
        }
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Nodes;

use ClanCats\Container\Exceptions\LogicalNodeException;

use ClanCats\Container\ContainerParser\{
    Nodes\ArrayNode,
    Nodes\ValueNode,
    Nodes\AssignableNode
};

class ParameterDefinitionNode extends BaseNode
{
    /**
     * The parameters name
     *
     * @var string
     */
    protected $name;

    /**
     * The value that is being defined
     *
     * @param ValueNode
     */
    protected $value;

    /**
     * Does this definition override existing ones?
     *
     * @var bool
     */
    protected $isOverride = false;

    /**
     * Parameter definition constructor
     *
     * @param string        $node
     * @param ValueNode     $value
     */
    public function __construct(string $name, AssignableNode $value)
    {
        $this->setName($name);
        $this->setValue($value);
    }

    /**
     * Get the paramters name
     *
     * @return string
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * Get the parameters value
     *
     * @return ValueNode
     */
    public function getValue() : AssignableNode
    {
        return $this->value;
    }

    /**
     * Set the parameters name
     *
     * @param Node 			$node
     * @return void
     */
    public function setName(string $name)
    {
    	$this->name = $name;
    }

    /**
     * Set the parameters value
     *
     * @param ValueNode             $value
     * @return void
     */
    public function setValue(AssignableNode $value)
    {
        // we currently only allow Arrays & scalar values
        // it not yet possible assign a reference to a parameter
        if (!($value instanceof ValueNode || $value instanceof ArrayNode)) {
            throw new LogicalNodeException("It is not possible to pass a reference of a parameter or service to a parameter definition.");
        }

        $this->value = $value;
    }

    /**
     * Get if this definition override existing ones?
     *
     * @return bool
     */
    public function isOverride() : bool
    {
        return $this->isOverride;
    }

    /**
     * Set if this definition override existing ones.
     *
     * @param bool          $isOverride
     * @return void
     */
    public function setIsOverride(bool $isOverride)
    {
        $this->isOverride = $isOverride;
    }
}


<?php

namespace Fabulator\Endomondo;

/**
 * Class WorkoutType
 * @package Fabulator\Endomondo
 */
class WorkoutType {
    const RUNNING = 0;
    const CYCLING_TRANSPORT = 1;
    const CYCLING_SPORT = 2;
    const MOUNTAIN_BIKINGS = 3;
    const SKATING = 4;
    const ROLLER_SKIING = 5;
    const SKIING_CROSS_COUNTRY = 6;
    const SKIING_DOWNHILL = 7;
    const SNOWBOARDING = 8;
    const KAYAKING = 9;
    const KITE_SURFING = 10;
    const ROWING = 11;
    const SAILING = 12;
    const WINDSURFING = 13;
    const FINTESS_WALKING = 14;
    const GOLFING = 15;
    const HIKING = 16;
    const ORIENTEERING = 17;
    const WALKING = 18;
    const RIDING = 19;
    const SWIMMING = 20;

    // @depreciated
    const SPINNING = 21;

    const CYCLING_INDOOR = 21;
    const OTHER = 22;
    const AEROBICS = 23;
    const BADMINTON = 24;
    const BASEBALL = 25;
    const BASKETBALL = 26;
    const BOXING = 27;
    const CLIMBING_STAIRS = 28;
    const CRICKET = 29;
    const ELLIPTICAL_TRAINING = 30;
    const DANCING = 31;
    const FENCING = 32;
    const FOOTBALL_AMERICAN = 33;
    const FOOTBALL_RUGBY = 34;
    const FOOTBALL_SOCCER = 35;
    const HANDBALL = 36;
    const HOCKEY = 37;
    const PILATES = 38;
    const POLO = 39;
    const SCUBA_DIVING = 40;
    const SQUASH = 41;
    const TABLE_TENIS = 42;
    const TENNIS = 43;
    const VOLEYBALL_BEACH = 44;
    const VOLEYBALL_INDOOR = 45;
    const WEIGHT_TRAINING = 46;
    const YOGA = 47;
    const MARTINAL_ARTS = 48;
    const GYMNASTICS = 49;
    const STEP_COUNTER = 50;
    const CIRKUIT_TRAINING = 87;
    const RUNNING_TREADMILL = 88;
    const SKATEBOARDING = 89;
    const SURFING = 90;
    const SNOWSHOEING = 91;
    CONST WHEELCHAIR = 92;
    const CLIMBING = 93;
    const WALKING_TREADMILL = 94;
    const KICK_SCOOTER = 95;
    const STAND_UP_PADDLING = 96;
    const TRAIL_RUNNING = 97;
    const ROWING_INDOORS = 98;
    const FLOORBALL = 99;
    const ICE_SKATING = 100;
    const SKI_TOURING = 101;
    const ROPE_JUMPING = 102;
    const STRETCHING = 103;
    const CANICROSS = 104;
    const PADDLE_TENNIS = 105;
    const PARAGLIDING = 106;

    /**
     * @var string[]
     */
    const TYPES_NAMES = [
        self::RUNNING  => 'Running',
        self::CYCLING_TRANSPORT  => 'Cycling, transport',
        self::CYCLING_SPORT  => 'Cycling, sport',
        self::MOUNTAIN_BIKINGS  => 'Mountain biking',
        self::SKATING  => 'Skating',
        self::ROLLER_SKIING  => 'Roller skiing',
        self::SKIING_CROSS_COUNTRY  => 'Skiing, cross country',
        self::SKIING_DOWNHILL  => 'Skiing, downhill',
        self::SNOWBOARDING  => 'Snowboarding',
        self::KAYAKING  => 'Kayaking',
        self::KITE_SURFING => 'Kite surfing',
        self::ROWING => 'Rowing',
        self::SAILING => 'Sailing',
        self::WINDSURFING => 'Windsurfing',
        self::FINTESS_WALKING => 'Fitness walking',
        self::GOLFING => 'Golfing',
        self::HIKING => 'Hiking',
        self::ORIENTEERING => 'Orienteering',
        self::WALKING => 'Walking',
        self::RIDING => 'Riding',
        self::SWIMMING => 'Swimming',
        self::CYCLING_INDOOR => 'Cycling, Indoor',
        self::OTHER => 'Other',
        self::AEROBICS => 'Aerobics',
        self::BADMINTON => 'Badminton',
        self::BASEBALL => 'Baseball',
        self::BASKETBALL => 'Basketball',
        self::BOXING => 'Boxing',
        self::CLIMBING_STAIRS => 'Climbing stairs',
        self::CRICKET => 'Cricket',
        self::ELLIPTICAL_TRAINING => 'Elliptical training',
        self::DANCING => 'Dancing',
        self::FENCING => 'Fencing',
        self::FOOTBALL_AMERICAN => 'Football, American',
        self::FOOTBALL_RUGBY => 'Football, rugby',
        self::FOOTBALL_SOCCER => 'Football, soccer',
        self::HANDBALL => 'Handball',
        self::HOCKEY => 'Hockey',
        self::PILATES => 'Pilates',
        self::POLO => 'Polo',
        self::SCUBA_DIVING => 'Scuba diving',
        self::SQUASH => 'Squash',
        self::TABLE_TENIS => 'Table tennis',
        self::TENNIS => 'Tennis',
        self::VOLEYBALL_BEACH => 'Volleyball, beach',
        self::VOLEYBALL_INDOOR => 'Volleyball, indoor',
        self::WEIGHT_TRAINING => 'Weight training',
        self::YOGA => 'Yoga',
        self::MARTINAL_ARTS => 'Martial arts',
        self::GYMNASTICS => 'Gymnastics',
        self::STEP_COUNTER => 'Step counter',
        self::CIRKUIT_TRAINING => 'Circuit Training',
        self::SKATEBOARDING => 'Skateboarding',
        self::CLIMBING => 'Climbing',
        self::KICK_SCOOTER => 'Kick scooter',
        self::CANICROSS => 'Canicross',
        self::FLOORBALL => 'Floorball',
        self::ICE_SKATING => 'Ice skating',
        self::RUNNING_TREADMILL => 'Running (Treadmill)',
        self::SURFING => 'Surfing',
        self::SNOWSHOEING => 'Showshoeing',
        self::WHEELCHAIR => 'Wheelchair',
        self::WALKING_TREADMILL => 'Walking (Treadmill)',
        self::STAND_UP_PADDLING => 'Stand up paddling',
        self::TRAIL_RUNNING => 'Trail running',
        self::ROWING_INDOORS => 'Rowing (indoors)',
        self::SKI_TOURING => 'Ski touring',
        self::ROPE_JUMPING => 'Rope jumping',
        self::STRETCHING => 'Stretching',
        self::PADDLE_TENNIS => 'Paddle tennis',
        self::PARAGLIDING => 'Paragliding',
    ];

    /**
     * Is endomondo workout type defined?
     *
     * @param $id
     * @return bool
     */
    static function exist($id)
    {
        try {
            return (bool) self::getName($id);
        } catch (EndomondoWorkoutException $e) {
            return false;
        }
    }

    /**
     * Get name of workot in human readable time.
     *
     * @param $id int Workout type id
     * @return string Name of workout
     * @throws EndomondoWorkoutException when workout type is not defined
     */
    static function getName($id)
    {
        try {
            return self::TYPES_NAMES[$id];
        } catch (\Exception $e) {
            throw new EndomondoWorkoutException('Unknown workout type', $e->getCode(), $e->getPrevious());
        }
    }
}


<?php

namespace Fabulator\Endomondo;

/**
 * Class Point
 * @package Fabulator\Endomondo
 */
class Point {

    /**
     * @var \DateTime
     */
    private $time;

    /**
     * @var float
     */
    private $latitude;

    /**
     * @var float
     */
    private $longitude;

    /**
     * @var float
     */
    private $altitude;

    /**
     * @var float
     */
    private $distance;

    /**
     * @var integer
     */
    private $duration;

    /**
     * @var integer
     */
    private $heartRate;

    /**
     * @var float
     */
    private $speed;

    /**
     * @var integer
     */
    private $instruction;

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

    /**
     * Point constructor.
     */
    public function __construct() { }

    /**
     * @param \DateTime $time
     * @return $this
     */
    public function setTime(\DateTime $time) {
        $this->time = clone $time;
        return $this;
    }

    /**
     * @return \DateTime
     */
    public function getTime()
    {
        return clone $this->time;
    }

    /**
     * @param $latitude float
     * @return $this
     */
    public function setLatitude($latitude)
    {
        $this->latitude = $latitude;
        return $this;
    }

    /**
     * @return float
     */
    public function getLatitude()
    {
        return $this->latitude;
    }

    /**
     * @param $longitude float
     * @return $this
     */
    public function setLongitude($longitude)
    {
        $this->longitude = $longitude;
        return $this;
    }

    /**
     * @return float
     */
    public function getLongitude()
    {
        return $this->longitude;
    }

    /**
     * @param $altitude float
     * @return $this
     */
    public function setAltitude($altitude)
    {
        $this->altitude = $altitude;
        return $this;
    }

    /**
     * @return float
     */
    public function getAltitude()
    {
        return $this->altitude;
    }

    /**
     * Set distance in kilometres.
     *
     * @param $distance float in kilometres
     * @return $this
     */
    public function setDistance($distance)
    {
        $this->distance = $distance;
        return $this;
    }

    /**
     * Get distance in kilometres.
     *
     * @return float in kilometres
     */
    public function getDistance()
    {
        return $this->distance;
    }

    /**
     * Get duration in seconds
     *
     * @param $duration integer in seconds
     * @return $this
     */
    public function setDuration($duration)
    {
        $this->duration = $duration;
        return $this;
    }

    /**
     * Get duration in seconds
     *
     * @return int duration in seconds
     */
    public function getDuration()
    {
        return $this->duration;
    }

    /**
     * @param $heartRate integer
     * @return $this
     */
    public function setHeartRate($heartRate)
    {
        $this->heartRate = $heartRate;
        return $this;
    }

    /**
     * @return int
     */
    public function getHeartRate()
    {
        return $this->heartRate;
    }

    /**
     * @param $speed float
     * @return $this
     */
    public function setSpeed($speed)
    {
        $this->speed = $speed;
        return $this;
    }

    /**
     * @return float
     */
    public function getSpeed()
    {
        return $this->speed;
    }

    /**
     * Set some start/stop instruction during the workout.
     *
     * @param $instruction int
     * @return $this
     */
    public function setInstruction($instruction)
    {
        $this->instruction = $instruction;
        return $this;
    }

    /**
     * Some start/stop instruction during the workout.
     *
     * @return int
     */
    public function getInstruction()
    {
        return $this->instruction ?: 2;
    }

    /**
     * @param $cadence int
     * @return $this
     */
    public function setCadence($cadence)
    {
        $this->cadence = $cadence;
        return $this;
    }

    /**
     * @return int
     */
    public function getCadence()
    {
        return $this->cadence;
    }

    /**
     * Convert point to string readable by Endomondo
     *
     * @return string
     */
    public function toString()
    {
        if ($this->getTime() !== null) {
            $time = $this->getTime()->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s \U\T\C');
        } else {
            $time = '';
        }
        return $time . ';' .
            $this->getInstruction() . ';' .
            $this->getLatitude() . ';' .
            $this->getLongitude() . ';' .
            $this->getDistance() . ';' .
            $this->getSpeed() . ';' .
            $this->getAltitude() . ';' .
            $this->getHeartRate() . ';' .
            $this->getCadence() . ';' .
            "\n";
    }
}


<?php
namespace Carnage\EncryptedColumn\Dbal;

use Carnage\EncryptedColumn\Service\EncryptionService;
use Carnage\EncryptedColumn\ValueObject\ValueHolder;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use Carnage\EncryptedColumn\ValueObject\EncryptedColumn as EncryptedColumnVO;

/**
 * Object type for reading from legacy encrypted column extensions and converting to a better format
 *
 * This type is a drop in replacement for the EncryptedColumn class but drops some strictness to allow for
 * reading data that is not in an expected format. You should only use this if you have existing data in
 * your database you wish to convert
 *
 * Class EncryptedColumn
 */
class EncryptedColumnLegacySupport extends Type
{
    const ENCRYPTED = 'encrypted';

    /**
     * @var EncryptionService
     */
    private $encryptionService;

    public static function create(EncryptionService $encryptionService)
    {
        Type::addType(EncryptedColumnLegacySupport::ENCRYPTED, EncryptedColumnLegacySupport::class);
        /** @var EncryptedColumnLegacySupport $instance */
        $instance = Type::getType(EncryptedColumnLegacySupport::ENCRYPTED);
        $instance->encryptionService = $encryptionService;
    }

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return $platform->getClobTypeDeclarationSQL($fieldDeclaration);
    }

    public function requiresSQLCommentHint(AbstractPlatform $platform)
    {
        return true;
    }

    public function getName()
    {
        return self::ENCRYPTED;
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if ($value === null) {
            return null;
        }

        try {
            $decoded = $this->decodeJson($value);
        } catch (ConversionException $e) {
            //The data wasn't in the format we expected, assume it is legacy data which needs converting
            //Drop in some defaults to allow the library to handle it.
            $decoded = [
                'data' => $value,
                'classname' => ValueHolder::class,
                'serializer' => 'legacy',
                'encryptor' => 'legacy',
                'keyid' => 'legacy',
            ];
        }

        return $this->encryptionService->decryptField(EncryptedColumnVO::fromArray($decoded));
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if ($value === null) {
            return null;
        }

        return json_encode($this->encryptionService->encryptField($value));
    }

    /**
     * Based on: https://github.com/schmittjoh/serializer/blob/master/src/JMS/Serializer/JsonDeserializationVisitor.php
     *
     * @param $value
     * @return mixed
     * @throws ConversionException
     */
    private function decodeJson($value)
    {
        $decoded = json_decode($value, true);

        switch (json_last_error()) {
            case JSON_ERROR_NONE:
                if (!is_array($decoded)) {
                    throw ConversionException::conversionFailed($value, 'Json was not an array');
                }
                return $decoded;
            case JSON_ERROR_DEPTH:
                throw ConversionException::conversionFailed($value, 'Could not decode JSON, maximum stack depth exceeded.');
            case JSON_ERROR_STATE_MISMATCH:
                throw ConversionException::conversionFailed($value, 'Could not decode JSON, underflow or the nodes mismatch.');
            case JSON_ERROR_CTRL_CHAR:
                throw ConversionException::conversionFailed($value, 'Could not decode JSON, unexpected control character found.');
            case JSON_ERROR_SYNTAX:
                throw ConversionException::conversionFailed($value, 'Could not decode JSON, syntax error - malformed JSON.');
            case JSON_ERROR_UTF8:
                throw ConversionException::conversionFailed($value, 'Could not decode JSON, malformed UTF-8 characters (incorrectly encoded?)');
            default:
                throw ConversionException::conversionFailed($value, 'Could not decode Json');
        }
    }
}