<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\Adapter\Phpunit;

use Humbug\Adapter\Phpunit\XmlConfiguration\Visitor;
use Humbug\Exception\LogicException;

class XmlConfiguration
{
    /**
     *
     * @var \DOMDocument
     */
    private $dom;

    /**
     * @var \DOMXPath
     */
    private $xpath;

    /**
     * @var \DOMElement
     */
    private $rootElement;

    /**
     * @var string|boolean
     */
    private $originalBootstrap;

    public function __construct(\DOMDocument $dom)
    {
        if (!$dom->documentElement) {
            throw new LogicException('No document element present. Document should not be empty!');
        }

        $this->dom = $dom;
        $this->xpath = new \DOMXPath($this->dom);
        $this->rootElement = $this->dom->documentElement;
    }

    public function hasBootstrap()
    {
        return $this->rootElement->hasAttribute('bootstrap');
    }

    public function getBootstrap()
    {
        return $this->rootElement->getAttribute('bootstrap');
    }

    public function setBootstrap($bootstrap)
    {
        if (null === $this->originalBootstrap) {
            $actualBootstrap = $this->getBootstrap();
            $this->originalBootstrap = $actualBootstrap ?: false;
        }

        return $this->rootElement->setAttribute('bootstrap', $bootstrap);
    }

    public function hasOriginalBootstrap()
    {
        return ($this->getOriginalBootstrap() !== null);
    }

    public function getOriginalBootstrap()
    {
        if (null !== $this->originalBootstrap) {
            return $this->originalBootstrap ?: null;
        }

        return $this->getBootstrap() ?: null;
    }

    /**
     * Given how PHPUnit nests suites, if there's more than one, the suite containing
     * actual tests will be two levels down the hierarchy from where we'd normally
     * find it with a single testsuite.
     *
     * @return integer
     */
    public function getRootTestSuiteNestingLevel()
    {
        $list = $this->xpath->query('//testsuite');
        if ($list->length == 1) {
            return 0;
        }
        return 1;
    }

    public function turnOffCacheTokens()
    {
        return $this->rootElement->setAttribute('cacheTokens', 'false');
    }

    public function cleanupLoggers()
    {
        $this->removeDocumentChildElementsByName('logging');
    }

    public function cleanupFilters()
    {
        $this->removeDocumentChildElementsByName('filter');
    }

    public function cleanupListeners()
    {
        $this->removeDocumentChildElementsByName('listeners');
    }

    public function addListener(Visitor $visitor)
    {
        $listenersList = $this->xpath->query('/phpunit/listeners');

        if ($listenersList->length) {
            $listeners = $listenersList->item(0);
        } else {
            $listeners = $this->dom->createElement('listeners');
            $this->rootElement->appendChild($listeners);
        }

        $listener = $this->dom->createElement('listener');
        $listeners->appendChild($listener);
        $visitor->visitElement($listener);
    }

    public function addLogger($type, $target)
    {
        $loggingList = $this->xpath->query('/phpunit/logging');

        if ($loggingList->length) {
            $logging = $loggingList->item(0);
        } else {
            $logging = $this->dom->createElement('logging');
            $this->rootElement->appendChild($logging);
        }

        $log = $this->dom->createElement('log');
        $logging->appendChild($log);

        $log->setAttribute('type', $type);
        $log->setAttribute('target', $target);
    }

    /**
     * Adds a <php><env name="XXX" value="YYY"/></php> to set environment variables
     * and generates the <php> block if not present.
     *
     * @param string $name  Environment variable name
     * @param string $value Value of the variable to set
     */
    public function addEnvironmentVariable($name, $value)
    {
        $phpNodeList = $this->xpath->query('/phpunit/php');

        if ($phpNodeList->length) {
            $phpNode = $phpNodeList->item(0);
        } else {
            $phpNode = $this->dom->createElement('php');
            $this->rootElement->appendChild($phpNode);
        }

        $env = $this->dom->createElement('env');
        $phpNode->appendChild($env);

        $env->setAttribute('name', $name);
        $env->setAttribute('value', $value);
    }

    public function addWhiteListFilter(array $whiteListDirectories, array $excludeDirectories = [])
    {
        if (empty($whiteListDirectories)) {
            return;
        }

        $filter = $this->dom->createElement('filter');
        $this->rootElement->appendChild($filter);

        $whiteList = $this->dom->createElement('whitelist');
        $filter->appendChild($whiteList);

        foreach ($whiteListDirectories as $dirName) {
            $directory = $this->dom->createElement('directory', $dirName);
            $whiteList->appendChild($directory);
            $directory->setAttribute('suffix', '.php');
        }

        if (empty($excludeDirectories)) {
            return;
        }

        $exclude = $this->dom->createElement('exclude');
        $whiteList->appendChild($exclude);

        foreach ($excludeDirectories as $dirName) {
            $directory = $this->dom->createElement('directory', $dirName);
            $exclude->appendChild($directory);
        }
    }

    public function replacePathsToAbsolutePaths(Visitor $pathVisitor, Visitor $wildcardVisitor)
    {
        $replacePaths = [
            '/phpunit/testsuites/testsuite/exclude',
            '//directory',
            '//file',
            '/phpunit/@bootstrap'
        ];

        $replaceQuery = implode('|', $replacePaths);

        $replaceNodes = $this->xpath->query($replaceQuery);

        foreach ($replaceNodes as $replace) {
            if (false !== strpos($replace->nodeValue, '*')) {
                $wildcardVisitor->visitElement($replace);
            } else {
                $pathVisitor->visitElement($replace);
            }
        }
    }

    public function generateXML()
    {
        return $this->dom->saveXML();
    }

    private function removeDocumentChildElementsByName($name)
    {
        $nodes = $this->xpath->query('/phpunit/' . $name);

        foreach ($nodes as $node) {
            $this->rootElement->removeChild($node);
        }
    }
}


<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug;

use Humbug\Exception\RuntimeException;
use Humbug\Exception\InvalidArgumentException;
use Symfony\Component\Finder\Finder;

class Container
{
    private $inputOptions;

    protected $tempDirectory;

    protected $adapter;

    protected $adapterOptions = [];

    protected $mutables = [];

    protected $generator;

    protected $bootstrap = '';

    protected $timeout = null;

    protected $testRunDirectory;

    protected $baseDirectory;

    protected $cacheDirectory;

    protected $workingCacheDirectory;

    protected $srcList;

    public function __construct(array $inputOptions)
    {
        $this->inputOptions = $inputOptions;
        if (isset($inputOptions['options'])) {
            $this->setAdapterOptionsFromString($this->inputOptions['options']);
        }
    }

    /**
     * Retrieve any of the original input options
     *
     * @param string $option
     * @return string
     */
    public function get($option)
    {
        if (!array_key_exists($option, $this->inputOptions)) {
            throw new \InvalidArgumentException('Option "'. $option . ' not exists');
        }

        return $this->inputOptions[$option];
    }

    /**
     * Set the directory from which tests must be run (only if not base)
     *
     * @param string $dir
     */
    public function setTestRunDirectory($dir)
    {
        $this->testRunDirectory = rtrim($dir, ' \\/');
        return $this;
    }

    /**
     * Get the directory from which tests must be run
     *
     * @return string
     */
    public function getTestRunDirectory()
    {
        return $this->testRunDirectory;
    }

    /**
     * Set the base directory.
     *
     * @param string $dir
     */
    public function setBaseDirectory($dir)
    {
        $this->baseDirectory = rtrim($dir, ' \\/');
        return $this;
    }

    /**
     * Get the base directory.
     *
     * @return string
     */
    public function getBaseDirectory()
    {
        return $this->baseDirectory;
    }

    public function setCacheDirectory($dir)
    {
        if (!is_dir($dir)) {
            throw new InvalidArgumentException(sprintf(
                'The cache directory does not exist: %s',
                $dir
            ));
        }
        $this->cacheDirectory = $dir;
    }

    /**
     * Get the cache directory.
     *
     * @return string
     */
    public function getCacheDirectory()
    {
        if (!is_null($this->cacheDirectory)) {
            return $this->cacheDirectory;
        }
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            if (!getenv('APPDATA')) {
                throw new RuntimeException(
                    'The APPDATA environment variable must be set for humbug.'
                );
            }
            $home = strtr(getenv('APPDATA'), '\\', '/') . '/Humbug';
        } else {
            if (!getenv('HOME')) {
                throw new RuntimeException(
                    'The HOME environment variable must be set for humbug.'
                );
            }
            $home = rtrim(getenv('HOME'), '/') . '/.humbug';
        }
        $cache = $home . '/cache';
        foreach ([$home, $cache] as $dir) {
            if (!is_dir($dir)) {
                mkdir($dir, 0777);
            }
        }
        file_put_contents($home . '/.htaccess', 'Deny from all');
        $this->cacheDirectory = $cache;
        return $cache;
    }

    public function getWorkingCacheDirectory()
    {
        if (!is_null($this->workingCacheDirectory)) {
            return $this->workingCacheDirectory;
        }
        $wcd = $this->getCacheDirectory() . '/' . md5($this->getBaseDirectory());
        if (!is_dir($wcd)) {
            mkdir($wcd, 0777);
        }
        $this->workingCacheDirectory = $wcd;
        return $wcd;
    }

    /**
     * Set the base directory.
     *
     * @param \stdClass $list
     */
    public function setSourceList(\stdClass $list)
    {
        $this->srcList = $list;
        return $this;
    }

    /**
     * Get the base directory.
     *
     * @return \stdClass
     */
    public function getSourceList()
    {
        return $this->srcList;
    }

    /**
     * Set the cache directory of the project being mutated
     *
     * @param string $dir
     */
    public function setTempDirectory($dir)
    {
        $dir = rtrim($dir, ' \\/');
        if (!is_dir($dir) || !is_readable($dir)) {
            throw new InvalidArgumentException('Invalid cache directory: "'.$dir.'"');
        }
        $this->tempDirectory = $dir;
        return $this;
    }

    /**
     * Get the cache directory of the project being mutated
     *
     * @return string
     */
    public function getTempDirectory()
    {
        if (is_null($this->tempDirectory)) {
            $root = sys_get_temp_dir();
            if (!is_dir($root . '/humbug')) {
                mkdir($root . '/humbug', 0777, true);
            }
            $this->tempDirectory = $root . '/humbug';
        }
        return $this->tempDirectory;
    }

    /**
     * Options to pass to adapter's underlying command
     *
     * @param string $optionString
     */
    public function setAdapterOptionsFromString($optionString)
    {
        $this->adapterOptions = array_merge(
            $this->adapterOptions,
            explode(' ', $optionString)
        );
        return $this;
    }

    /**
     * Get a space delimited string of testing tool options
     *
     * @return string
     */
    public function getAdapterOptions()
    {
        return $this->adapterOptions;
    }

    /**
     * Get a test framework adapter. Creates a new one based on the configured
     * adapter name passed on the CLI if not already set.
     *
     * @return \Humbug\Adapter\AdapterAbstract
     */
    public function getAdapter()
    {
        if (is_null($this->adapter)) {
            $name = ucfirst(strtolower($this->get('adapter')));
            $class = '\\Humbug\\Adapter\\' . $name;
            $this->adapter = new $class;
        }
        return $this->adapter;
    }

    /**
     * Set a test framework adapter.
     *
     * @param \Humbug\Adapter\AdapterAbstract $adapter
     */
    public function setAdapter(Adapter\AdapterAbstract $adapter)
    {
        $this->adapter = $adapter;
        return $this;
    }

    /**
     * Generate Mutants!
     *
     * @return array
     */
    public function getMutableFiles(Finder $finder)
    {
        if (empty($this->mutables)) {
            $generator = $this->getGenerator();
            $generator->generate($finder);
            $this->mutables = $generator->getMutables();
        }
        return $this->mutables;
    }

    /**
     * Set a specific Generator of mutations (stuck with a subclass).
     * TODO Add interface
     *
     * @param \Humbug\Generator $generator
     * @return $this
     */
    public function setGenerator(Generator $generator)
    {
        $this->generator = $generator;
        return $this;
    }

    /**
     * Get a specific Generator of mutations.
     *
     * @return \Humbug\Generator
     */
    public function getGenerator()
    {
        if (!isset($this->generator)) {
            $this->generator = new Generator;
        }
        return $this->generator;
    }

    public function setTimeout($seconds)
    {
        $this->timeout = $seconds;
        return $this;
    }

    /**
     * Routed through Console Input class
     */

    public function getTimeout()
    {
        if (!is_null($this->timeout)) {
            return $this->timeout;
        }
        return $this->get('timeout');
    }

    public function getAdapterConstraints()
    {
        return $this->get('constraints');
    }

    public function setBootstrap($bootstrap)
    {
        $this->bootstrap = realpath($bootstrap);
    }

    public function getBootstrap()
    {
        return $this->bootstrap;
    }
}


<?php

/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */
namespace Humbug;

use Humbug\TestSuite\Mutant\FileGenerator;
use Humbug\TestSuite\Mutant\Result;
use Humbug\Utility\CoverageData;
use Humbug\Utility\Diff;
use Humbug\Utility\Tokenizer;
use Humbug\Exception\NoCoveringTestsException;
use Serializable;

class Mutant implements Serializable
{
    /**
     * The mutation's parameters
     * @var Mutation
     */
    protected $mutation;

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

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

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

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

    /**
     * @var Result
     */
    protected $result;

    /**
     * @var Diff
     */
    protected $diff;

    public function __construct(Mutation $mutation, FileGenerator $generator, CoverageData $coverage, $baseDirectory)
    {
        $this->mutation = $mutation;

        try {
            $this->tests = $coverage->getTestClasses(
                $mutation->getFile(),
                $mutation->getLine()
            );
            $this->testMethods = $coverage->getTestMethods(
                $mutation->getFile(),
                $mutation->getLine()
            );
        } catch (NoCoveringTestsException $e) {
            $this->tests = [];
            $this->testMethods = [];
        }

        $this->file = $generator->generateFile($mutation);
        $this->baseDirectory = $baseDirectory;
        $this->diff = Diff::getInstance();
    }

    /**
     * @param Diff $diff
     */
    public function setDiffGenerator(Diff $diff)
    {
        $this->diff = $diff;
    }

    /**
     * @return string
     */
    public function getDiff()
    {
        return $this->diff->difference(
            file_get_contents($this->mutation->getFile()),
            file_get_contents($this->file)
        );
    }

    /**
     * @return Mutation
     */
    public function getMutation()
    {
        return $this->mutation;
    }

    /**
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * @return bool
     */
    public function hasTests()
    {
        return !empty($this->tests);
    }

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

    /**
     * For debug and logging purposes
     * @return array
     */
    public function toArray()
    {
        return [
            'file' => $this->getMutationFileRelativePath(),
            'mutator' => $this->mutation->getMutator(),
            'class' => $this->mutation->getClass(),
            'method' => $this->mutation->getMethod(),
            'line' => $this->mutation->getLine(),
            'diff' => $this->getDiff(),
            'tests' => $this->testMethods
        ];
    }

    public function serialize()
    {
        $data = [
            'mutation' => $this->mutation,
            'tests' => $this->tests,
            'file' => $this->file,
            'baseDirectory' => $this->baseDirectory
        ];
        return serialize($data);
    }

    public function unserialize($string)
    {
        $data = unserialize($string);
        $this->mutation = $data['mutation'];
        $this->tests = $data['tests'];
        $this->file = $data['file'];
        $this->baseDirectory = $data['baseDirectory'];
        $this->diff = Diff::getInstance();
    }

    private function getMutationFileRelativePath()
    {
        $path = explode(DIRECTORY_SEPARATOR, $this->mutation->getFile());
        $baseDirectory = explode(DIRECTORY_SEPARATOR, $this->baseDirectory);

        return join(DIRECTORY_SEPARATOR, array_diff($path, $baseDirectory));
    }
}


<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\Adapter\Phpunit;

class Job
{
    /**
     * Generate a new Job script to be executed under a separate PHP process
     *
     * @param null|string   $mutantFile
     * @param array         $args
     * @param string        $bootstrap
     * @param null|string   $replacingFile
     * @return string
     */
    public static function generate($mutantFile = null, $bootstrap = '', $replacingFile = null)
    {
        $loadHumbug = '';
        if ('phar:' === substr(__FILE__, 0, 5)) {
            $loadHumbug = '\Phar::loadPhar(\''
                . str_replace('phar://', '', \Phar::running())
                . '\', \'humbug.phar\');';
            $humbugBootstrap = 'phar://humbug.phar' . '/bootstrap.php';
        } else {
            $humbugBootstrap = realpath(__DIR__ . '/../../../bootstrap.php');
        }

        $file = sys_get_temp_dir() . '/humbug.phpunit.bootstrap.php';

        if (!is_null($mutantFile)) {
            $mutantFile = addslashes($mutantFile);
            $replacingFile = addslashes($replacingFile);
            $prepend = <<<PREPEND
<?php
{$loadHumbug}
require_once '{$humbugBootstrap}';
use Humbug\StreamWrapper\IncludeInterceptor;
IncludeInterceptor::intercept('{$replacingFile}', '{$mutantFile}');
IncludeInterceptor::enable();
PREPEND;
            if (!empty($bootstrap)) {
                $buffer = $prepend . "\nrequire_once '{$bootstrap}';";
            } else {
                $buffer = $prepend;
            }
            file_put_contents($file, $buffer);
        } else {
            if (!empty($bootstrap)) {
                $buffer = "<?php\n{$loadHumbug}\nrequire_once '{$humbugBootstrap}';\nrequire_once '{$bootstrap}';";
            } else {
                $buffer = "<?php\n{$loadHumbug}\nrequire_once '{$humbugBootstrap}';";
            }
            file_put_contents($file, $buffer);
        }
    }
}


<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\Mutator;

use Humbug\Utility\Tokenizer;

abstract class MutatorAbstract
{
    /**
     * Perform a mutation against the given original source code tokens for
     * a mutable element
     *
     * @param array $tokens
     * @param int $index
     * @return string
     */
    public static function mutate(array &$tokens, $index)
    {
        static::getMutation($tokens, $index);
        return Tokenizer::reconstructFromTokens($tokens);
    }

    private static function shouldSkip($token, array $excludeTokens)
    {
        if (! is_array($token) && in_array($token, $excludeTokens, true)) {
            return true;
        }

        return in_array($token[0], $excludeTokens, true);
    }

    /**
     * Finds the next token in token array after a given index.
     * @param array $tokens Token array to lookup
     * @param int $index Position to start lookup at
     * @param array $excludeTokens Excluded tokens list
     *
     * @return int|string|false The next match if found, or false. Token is guaranteed to be a scalar if a match is found.
     */
    protected static function getNextToken(array &$tokens, $index, array $excludeTokens = [])
    {
        $tokenCount = count($tokens);
        while ($index < $tokenCount && isset($tokens[$index+1])
        && self::shouldSkip($tokens[$index + 1], $excludeTokens)) {
            $index++;
        }

        if (! isset($tokens[$index+1])) {
            return false;
        }

        return is_array($tokens[$index+1]) ? $tokens[$index+1][0] : $tokens[$index+1];
    }

    /**
     * Finds the next token value in token array before a given index.
     * @param array $tokens Token array to lookup
     * @param int $index Position to start lookup at
     * @param array $excludeTokens Excluded tokens list
     *
     * @return int|string|false The previous match if found, or false. Token is guaranteed to be a scalar if a match is found.
     */
    protected static function getPreviousToken(array &$tokens, $index, array $excludeTokens = [])
    {
        while ($index > 0 && isset($tokens[$index-1])
        && self::shouldSkip($tokens[$index - 1], $excludeTokens)) {
            $index--;
        }

        if (! isset($tokens[$index-1])) {
            return false;
        }

        return is_array($tokens[$index-1]) ? $tokens[$index-1][0] : $tokens[$index-1];
    }
}


<?php

/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 *
 * @author     rafal.wartalski@gmail.com
 */

namespace Humbug;

use Humbug\Adapter\AdapterAbstract;
use Symfony\Component\Process\Process;

class ProcessRunner
{
    /**
     * @param Process $process
     * @param AdapterAbstract $testFrameworkAdapter
     * @param callable $onProgressCallback
     *
     * @return bool
     */
    public function run(
        Process $process,
        AdapterAbstract $testFrameworkAdapter,
        \Closure $onProgressCallback = null
    ) {
        $hasFailure = false;

        $process->run(function (
            $out,
            $data
        ) use (
            $process,
            $testFrameworkAdapter,
            $onProgressCallback,
            &$hasFailure
        ) {
            if ($out == Process::ERR) {
                $hasFailure = true;
            }

            if (!$testFrameworkAdapter->ok($data)) {
                $hasFailure = true;
            }

            $oksCount = $testFrameworkAdapter->hasOks($data);

            if ($oksCount !== false && $onProgressCallback) {
                $onProgressCallback($oksCount);
            }
        });

        $process->stop();

        return $hasFailure;
    }
}


<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\Mutator\ReturnValue;

use Humbug\Mutator\MutatorAbstract;

class FloatNegation extends MutatorAbstract
{

    /**
     * Replace any float with its sign inverted (i.e. multiply by -1)
     *
     * @param array $tokens
     * @param int $index
     * @return array
     */
    public static function getMutation(array &$tokens, $index)
    {
        $tokenCount = count($tokens);
        for ($i=$index+1; $i < $tokenCount; $i++) {
            if (is_array($tokens[$i]) && $tokens[$i][0] == T_WHITESPACE) {
                continue;
            } elseif (is_array($tokens[$i]) && $tokens[$i][0] == T_DNUMBER) {
                // Dump the negation - string for negative ints
                if (self::getPreviousToken($tokens, $i) == '-') {
                    $tokens[$i-1] = [
                        T_WHITESPACE,
                        ''
                    ];
                    break;
                }
                // otherwise multiply by -1 to make negative for positive ints
                $num = (float) $tokens[$i][1];
                $replace = -1 * $num;
                $tokens[$i] = [
                    T_DNUMBER,
                    sprintf("%.2f", $replace)
                ];
                break;
            }
        }
    }

    public static function mutates(array &$tokens, $index)
    {
        $t = $tokens[$index];
        if (is_array($t) && $t[0] == T_RETURN) {
            $has = false;
            $tokenCount = count($tokens);
            for ($i=$index+1; $i < $tokenCount; $i++) {
                if (is_array($tokens[$i]) && $tokens[$i][0] == T_WHITESPACE) {
                    continue;
                } elseif (is_array($tokens[$i]) && $tokens[$i][0] == T_DNUMBER && $tokens[$i][1] != 0) {
                    $has = true;
                    continue;
                } elseif (!is_array($tokens[$i]) && $tokens[$i] == ';') {
                    // return statement terminated
                    if ($has === true) {
                        return true;
                    }
                    return false;
                } else {
                    $has = false;
                }
            }
        }
        return false;
    }
}


<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\Adapter;

use Humbug\Container;
use Humbug\Adapter\Phpunit\TestClassLocator;
use Humbug\Adapter\Phpunit\XmlConfiguration;
use Humbug\Adapter\Phpunit\XmlConfigurationBuilder;
use Humbug\Adapter\Phpunit\Job;
use Humbug\Adapter\Phpunit\Process\PhpunitExecutableFinder;
use Humbug\Utility\CoverageData;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

class Phpunit extends AdapterAbstract
{

    /**
     * @var TestClassLocator
     */
    private $locator;

    /**
     * Runs the tests suite according to Runner set options and the execution
     * order of test case (if any). It then returns an array of two elements.
     * First element is a boolean result value indicating if tests passed or not.
     * Second element is an array containing the key "stdout" which stores the
     * output from the last test run.
     *
     * @param   Container         $container
     * @param   bool              $firstRun
     * @param   null|string       $interceptFile
     * @param   null|string       $mutantFile
     * @param   array             $testSuites
     * @return  Process
     */
    public function getProcess(
        Container $container,
        $firstRun = false,
        $interceptFile = null,
        $mutantFile = null,
        array $testSuites = []
    ) {
        $jobopts = [
            'testdir'       => $container->getTestRunDirectory(),
            'basedir'       => $container->getBaseDirectory(),
            'timeout'       => $container->getTimeout(),
            'cachedir'      => $container->getTempDirectory(),
            'command'       => $container->getAdapterOptions(),
            'constraints'   => $container->getAdapterConstraints()
        ];

        /**
         * We like standardised easy to parse outout
         */
        array_unshift($jobopts['command'], '--teamcity');

        /*
         * We only need a single fail!
         */
        if (!in_array('--stop-on-failure', $jobopts['command'])) {
            array_unshift($jobopts['command'], '--stop-on-failure');
        }

        /**
         * Setup a PHPUnit XML config file for the purposes of explicitly setting
         * test case order (this will preserve anything else from the original)
         *
         * On first runs we log to junit XML so we can sort tests by performance.
         *
         * TODO: Assemble config just once if no coverage data available!
         */
        $xmlConfiguration = $this->assembleXmlConfiguration($container, $firstRun, $testSuites);

        $configFile = $container->getTempDirectory() . '/phpunit.humbug.xml';

        file_put_contents($configFile, $xmlConfiguration->generateXML());

        foreach ($jobopts['command'] as $key => $value) {
            if ($value == '--configuration' || $value == '-C') {
                unset($jobopts['command'][$key]);
                unset($jobopts['command'][$key+1]);
            } elseif (preg_match('%\\-\\-configuration=%', $value)) {
                unset($jobopts['command'][$key]);
            }
        }
        array_unshift($jobopts['command'], '--configuration=' . $configFile);

        /**
         * Initial command is expected, of course.
         */
        $phpunitFinder = new PhpunitExecutableFinder;
        $command = $phpunitFinder->find();
        array_unshift($jobopts['command'], $command);

        /**
         * Log the first run so we can analyse test times to make future
         * runs more efficient in terms of deferring slow test classes to last
         */
        $timeout = 0;
        if ($firstRun) {
            $jobopts['command'] = array_merge(
                $jobopts['command'],
                explode(' ', $jobopts['constraints'])
            );
        } else {
            $timeout = $container->getTimeout();
        }

        Job::generate(
            $mutantFile,
            $container->getBootstrap(),
            $interceptFile
        );

        $process = new Process(implode(' ', $jobopts['command']), $jobopts['testdir'], array_replace($_ENV, $_SERVER));
        $process->setTimeout($timeout);

        return $process;
    }

    /**
     * Load coverage data from and return
     *
     * @param Container $container
     * @return \Humbug\Utility\CoverageData
     */
    public function getCoverageData(Container $container)
    {
        $coverage = new CoverageData(
            $container->getTempDirectory() . '/coverage.humbug.php'
        );
        return $coverage;
    }

    public function getClassFile($class, Container $container)
    {
        if (is_null($this->locator)) {
            $this->locator = new TestClassLocator($container);
        }
        return $this->locator->locate($class);
    }

    /**
     * Wrangle XML to create a PHPUnit configuration, based on the original, that
     * allows for more control over what tests are run, allows JUnit logging,
     * and ensures that Code Coverage (for Humbug use) whitelists all of the
     * relevant source code.
     *
     *
     * @return XmlConfiguration
     */
    private function assembleXmlConfiguration(Container $container, $firstRun = false, array $testSuites = [])
    {
        $configurationDir = $this->resolveConfigurationDir($container);
        $xmlConfigurationBuilder = new XmlConfigurationBuilder($configurationDir);

        if ($firstRun) {
            $xmlConfigurationBuilder->setPhpCoverage($container->getTempDirectory() . '/coverage.humbug.php');
            $xmlConfigurationBuilder->setTextCoverage($container->getTempDirectory() . '/coverage.humbug.txt');
            $xmlConfigurationBuilder->setJunitLog($container->getTempDirectory() . '/junit.humbug.xml');
            $whiteListSrc = $this->getWhiteListSrc($container);
            $excludeDirs = $this->getExcludeDirs($container);
            $xmlConfigurationBuilder->setCoverageFilter($whiteListSrc, $excludeDirs);
            $xmlConfigurationBuilder->setTimeCollectionListener($this->getPathToTimeCollectorFile($container));
        } else {
            $xmlConfigurationBuilder->setFilterListener($testSuites, $this->getPathToTimeCollectorFile($container));
        }

        $xmlConfigurationBuilder->setAcceleratorListener();
        $xmlConfiguration = $xmlConfigurationBuilder->getConfiguration();

        if ($xmlConfiguration->hasOriginalBootstrap()) {
            $container->setBootstrap($xmlConfiguration->getOriginalBootstrap());
        }
        return $xmlConfiguration;
    }

    private function getPathToTimeCollectorFile(Container $container)
    {
        return $container->getTempDirectory() . '/phpunit.times.humbug.json';
    }

    /**
     * @param Container $container
     * @return string
     */
    private function resolveConfigurationDir(Container $container)
    {
        $configurationDir = $container->getTestRunDirectory();
        if (empty($configurationDir)) {
            $configurationDir = $container->getBaseDirectory();
        }
        return realpath($configurationDir);
    }

    /**
     * @param Container $container
     * @return array
     */
    private function getWhiteListSrc(Container $container)
    {
        $srcList = $container->getSourceList();
        return isset($srcList->directories) ? $this->getRealPathList($srcList->directories) : [];
    }

    /**
     * @param Container $container
     * @return array
     */
    private function getExcludeDirs(Container $container)
    {
        $srcList = $container->getSourceList();
        return isset($srcList->excludes) ? $this->getRealPathList($srcList->excludes) : [];
    }

    /**
     * @param array $directories
     * @return array
     */
    private function getRealPathList(array $directories)
    {
        return array_map('realpath', $directories);
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\Configuration\MethodGenerator;

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\BeanException;
use bitExpert\Disco\InitializedBean;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\BeanFactoryConfigurationProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\BeanPostProcessorsProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\ForceLazyInitProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\SessionBeansProperty;
use bitExpert\Disco\Proxy\LazyBean\LazyBeanFactory;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\Proxy\LazyLoadingInterface;
use ReflectionType;
use Zend\Code\Generator\MethodGenerator;
use Zend\Code\Generator\ParameterGenerator;
use Zend\Code\Reflection\MethodReflection;

/**
 * The bean method generator will generate a method for each bean definition in the generated
 * configuration class. The method contains the logic to deal with the bean creation as well
 * as taking the configuration options like lazy creation or session-awareness of the bean into
 * account. These configuration options are defined via annotations.
 */
class BeanMethod extends ParameterAwareMethodGenerator
{
    /**
     * Creates a new {@link \bitExpert\Disco\Proxy\Configuration\MethodGenerator\BeanMethod}.
     *
     * @param MethodReflection $originalMethod
     * @param Bean $beanMetadata
     * @param ReflectionType|null $beanType
     * @param ForceLazyInitProperty $forceLazyInitProperty
     * @param SessionBeansProperty $sessionBeansProperty
     * @param BeanPostProcessorsProperty $postProcessorsProperty
     * @param BeanFactoryConfigurationProperty $beanFactoryConfigurationProperty
     * @param GetParameter $parameterValuesMethod
     * @param WrapBeanAsLazy $wrapBeanAsLazy
     * @return MethodGenerator
     * @throws \Zend\Code\Generator\Exception\InvalidArgumentException
     * @throws \ProxyManager\Exception\InvalidProxiedClassException
     */
    public static function generateMethod(
        MethodReflection $originalMethod,
        Bean $beanMetadata,
        ?ReflectionType $beanType,
        ForceLazyInitProperty $forceLazyInitProperty,
        SessionBeansProperty $sessionBeansProperty,
        BeanPostProcessorsProperty $postProcessorsProperty,
        BeanFactoryConfigurationProperty $beanFactoryConfigurationProperty,
        GetParameter $parameterValuesMethod,
        WrapBeanAsLazy $wrapBeanAsLazy
    ): MethodGenerator {
        if (null === $beanType) {
            throw new InvalidProxiedClassException(
                sprintf(
                    'Method "%s" on "%s" is missing the return type hint!',
                    $originalMethod->name,
                    $originalMethod->class
                )
            );
        }
        $beanType = (string) $beanType;

        $method = static::fromReflection($originalMethod);
        $methodParams = static::convertMethodParamsToString($beanMetadata->getParameters(), $parameterValuesMethod);
        $beanId = $originalMethod->name;
        $body = '';

        if (in_array($beanType, ['array', 'callable', 'bool', 'float', 'int', 'string'], true)) {
            // return type is a primitive, simply call parent method and return immediately
            $body .= 'return parent::' . $beanId . '(' . $methodParams . ');' . PHP_EOL;
        } elseif (class_exists($beanType) || interface_exists($beanType)) {
            if ($beanMetadata->isLazy()) {
                $body = static::generateLazyBeanCode(
                    '',
                    $beanId,
                    $beanType,
                    $beanMetadata,
                    $methodParams,
                    $forceLazyInitProperty,
                    $sessionBeansProperty,
                    $postProcessorsProperty,
                    $beanFactoryConfigurationProperty
                );
            } else {
                $body = static::generateNonLazyBeanCode(
                    '',
                    $beanId,
                    $beanType,
                    $beanMetadata,
                    $methodParams,
                    $forceLazyInitProperty,
                    $sessionBeansProperty,
                    $postProcessorsProperty,
                    $wrapBeanAsLazy
                );
            }
        } else {
            // return type is unknown, throw an exception
            throw new InvalidProxiedClassException(
                sprintf(
                    'Return type of method "%s" on "%s" cannot be found! Did you use the full qualified name?',
                    $originalMethod->getName(),
                    $originalMethod->getDeclaringClass()->getName()
                )
            );
        }

        $method->setBody($body);
        $method->setDocBlock('{@inheritDoc}');
        return $method;
    }

    /**
     * @override Enforces generation of \ProxyManager\Generator\MethodGenerator.
     *
     * {@inheritDoc}
     * @throws \Zend\Code\Generator\Exception\InvalidArgumentException
     */
    public static function fromReflection(MethodReflection $reflectionMethod): MethodGenerator
    {
        $method = parent::fromReflection($reflectionMethod);

        /*
         * When overwriting methods PHP 7 enforces the same method parameters to be defined as in the base class. Since
         * the {@link \bitExpert\Disco\AnnotationBeanFactory} calls the generated methods without any parameters we
         * simply set a default value of null for each of the method parameters.
         */
        $method->setParameters([]);
        foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
            $parameter = ParameterGenerator::fromReflection($reflectionParameter);
            $parameter->setDefaultValue(null);
            $method->setParameter($parameter);
        }

        return $method;
    }

    /**
     * Helper method to generate the method body for managing lazy bean instances.
     *
     * @param string $padding
     * @param string $beanId
     * @param string $beanType
     * @param Bean $beanMetadata
     * @param string $methodParams
     * @param ForceLazyInitProperty $forceLazyInitProperty
     * @param SessionBeansProperty $sessionBeansProperty
     * @param BeanPostProcessorsProperty $postProcessorsProperty
     * @param BeanFactoryConfigurationProperty $beanFactoryConfigurationProperty
     * @return string
     */
    protected static function generateLazyBeanCode(
        string $padding,
        string $beanId,
        string $beanType,
        Bean $beanMetadata,
        string $methodParams,
        ForceLazyInitProperty $forceLazyInitProperty,
        SessionBeansProperty $sessionBeansProperty,
        BeanPostProcessorsProperty $postProcessorsProperty,
        BeanFactoryConfigurationProperty $beanFactoryConfigurationProperty
    ): string {
        $content = '';

        if ($beanMetadata->isSession()) {
            $content .= $padding . 'if($this->' . $sessionBeansProperty->getName() . '->has("' . $beanId . '")) {' .
                PHP_EOL;
            if ($beanMetadata->isSingleton()) {
                $content .= $padding . '    $sessionInstance = clone $this->' . $sessionBeansProperty->getName() .
                    '->get("' . $beanId . '");' . PHP_EOL;
            } else {
                $content .= $padding . '    $sessionInstance = $this->' . $sessionBeansProperty->getName() . '->get("' .
                    $beanId . '");' . PHP_EOL;
            }
            $content .= $padding . '    return $sessionInstance;' . PHP_EOL;
            $content .= $padding . '}' . PHP_EOL;
        }

        if ($beanMetadata->isSingleton()) {
            $content .= $padding . 'static $instance = null;' . PHP_EOL;
            $content .= $padding . 'if ($instance !== null) {' . PHP_EOL;
            $content .= $padding . '    return $instance;' . PHP_EOL;
            $content .= $padding . '}' . PHP_EOL;
        }

        $content .= $padding . '$factory = new \\' . LazyBeanFactory::class . '("' . $beanId . '", $this->' .
            $beanFactoryConfigurationProperty->getName() . '->getProxyManagerConfiguration());' . PHP_EOL;
        $content .= $padding . '$initializer = function (&$instance, \\' . LazyLoadingInterface::class .
            ' $proxy, $method, array $parameters, &$initializer) {' . PHP_EOL;
        $content .= $padding . '    try {' . PHP_EOL;
        $content .= $padding . '        $initializer = null;' . PHP_EOL;

        if ($beanMetadata->isSession()) {
            $content .= $padding . '        $backupForceLazyInit = $this->' . $forceLazyInitProperty->getName() . ';'
                . PHP_EOL;
            $content .= $padding . '        $this->' . $forceLazyInitProperty->getName() . ' = true;' . PHP_EOL;
        }

        $content .= $padding . self::generateBeanCreationCode(
            $padding . '        ',
            $beanId,
            $methodParams,
            $postProcessorsProperty
        );

        if ($beanMetadata->isSession()) {
            $content .= $padding . '        $this->' . $forceLazyInitProperty->getName() . ' = $backupForceLazyInit;' .
                PHP_EOL;
        }

        $content .= $padding . '    } catch (\Throwable $e) {' . PHP_EOL;
        $content .= $padding . '        $message = sprintf(' . PHP_EOL;
        $content .= $padding . '            \'Either return type declaration missing or unknown for bean with id "' .
            $beanId . '": %s\',' . PHP_EOL;
        $content .= $padding . '            $e->getMessage()' . PHP_EOL;
        $content .= $padding . '        );' . PHP_EOL;
        $content .= $padding . '        throw new \\' . BeanException::class . '($message, 0, $e);' . PHP_EOL;
        $content .= $padding . '    }' . PHP_EOL;
        $content .= $padding . '    return true;' . PHP_EOL;
        $content .= $padding . '};' . PHP_EOL;
        $content .= $padding . PHP_EOL;
        $content .= $padding . '$initializer->bindTo($this);' . PHP_EOL;
        $content .= $padding . '$instance = $factory->createProxy("' . $beanType . '", $initializer);' . PHP_EOL;

        if ($beanMetadata->isSession()) {
            $content .= $padding . '$this->' . $sessionBeansProperty->getName() . '->add("' . $beanId . '", $instance);'
                . PHP_EOL;
        }

        $content .= $padding . 'return $instance;' . PHP_EOL;
        return $content;
    }

    /**
     * Helper method to generate the code to initialize a bean.
     *
     * @param string $padding
     * @param string $beanId
     * @param string $methodParams
     * @param BeanPostProcessorsProperty $postProcessorsProperty
     * @return string
     */
    protected static function generateBeanCreationCode(
        string $padding,
        string $beanId,
        string $methodParams,
        BeanPostProcessorsProperty $postProcessorsProperty
    ): string {
        $content = $padding . '$instance = parent::' . $beanId . '(' . $methodParams . ');' . PHP_EOL;
        $content .= $padding . 'if ($instance instanceof \\' . InitializedBean::class . ') {
        ' . PHP_EOL;
        $content .= $padding . '    $instance->postInitialization();' . PHP_EOL;
        $content .= $padding . '}' . PHP_EOL;
        $content .= PHP_EOL;
        $content .= $padding . 'foreach ($this->' . $postProcessorsProperty->getName() . ' as $postProcessor) {
        ' .
            PHP_EOL;
        $content .= $padding . '    $postProcessor->postProcess($instance, "' . $beanId . '");' . PHP_EOL;
        $content .= $padding . '}' . PHP_EOL;
        return $content;
    }

    /**
     * Helper method to generate the method body for managing non-lazy bean instances.
     *
     * @param string $padding
     * @param string $beanId
     * @param string $beanType
     * @param Bean $beanMetadata
     * @param string $methodParams
     * @param ForceLazyInitProperty $forceLazyInitProperty
     * @param SessionBeansProperty $sessionBeansProperty
     * @param BeanPostProcessorsProperty $postProcessorsProperty
     * @param WrapBeanAsLazy $wrapBeanAsLazy
     * @return string
     */
    protected static function generateNonLazyBeanCode(
        string $padding,
        string $beanId,
        string $beanType,
        Bean $beanMetadata,
        string $methodParams,
        ForceLazyInitProperty $forceLazyInitProperty,
        SessionBeansProperty $sessionBeansProperty,
        BeanPostProcessorsProperty $postProcessorsProperty,
        WrapBeanAsLazy $wrapBeanAsLazy
    ): string {
        $content = $padding . '$backupForceLazyInit = $this->' . $forceLazyInitProperty->getName() . ';' . PHP_EOL;

        if ($beanMetadata->isSession()) {
            $content .= $padding . 'if($this->' . $sessionBeansProperty->getName() . '->has("' . $beanId . '")) {'
                . PHP_EOL;
            if ($beanMetadata->isSingleton()) {
                $content .= $padding . '    $sessionInstance = clone $this->' . $sessionBeansProperty->getName()
                    . '->get("' . $beanId . '");' . PHP_EOL;
            } else {
                $content .= $padding . '    $sessionInstance = $this->' . $sessionBeansProperty->getName() . '->get("' .
                    $beanId . '");' . PHP_EOL;
            }
            $content .= $padding . '    return ($backupForceLazyInit) ? $this->' . $wrapBeanAsLazy->getName() . '("' .
                $beanId . '", "' . $beanType . '", $sessionInstance) : $sessionInstance;' . PHP_EOL;
            $content .= $padding . '}' . PHP_EOL;
        }

        if ($beanMetadata->isSingleton()) {
            $content .= $padding . 'static $instance = null;' . PHP_EOL;
            $content .= $padding . 'if ($instance !== null) {' . PHP_EOL;
            $content .= $padding . '    return ($backupForceLazyInit) ? $this->' . $wrapBeanAsLazy->getName() . '("' .
                $beanId . '", "' . $beanType . '", $instance) : $instance;' . PHP_EOL;
            $content .= $padding . '}' . PHP_EOL;
        }

        if ($beanMetadata->isSession()) {
            $content .= $padding . '$this->' . $forceLazyInitProperty->getName() . ' = true;' . PHP_EOL;
        }

        $content .= self::generateBeanCreationCode($padding, $beanId, $methodParams, $postProcessorsProperty);

        if ($beanMetadata->isSession()) {
            $content .= $padding . '$this->' . $forceLazyInitProperty->getName() . ' = $backupForceLazyInit;' . PHP_EOL;
            $content .= $padding . '$this->' . $sessionBeansProperty->getName() . '->add("' . $beanId . '", $instance);'
                . PHP_EOL;
        }

        $content .= $padding . 'return ($backupForceLazyInit) ? $this->' . $wrapBeanAsLazy->getName() . '("' .
            $beanId . '", "' . $beanType . '", $instance) : $instance;' . PHP_EOL;

        return $content;
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Annotations;

final class AnnotationAttributeParser
{
    /**
     * Helper function to cast any value to a boolean representation.
     *
     * @param mixed $value
     * @return bool
     */
    public static function parseBooleanValue($value): bool
    {
        if (\is_bool($value)) {
            return $value;
        }

        if (\is_string($value)) {
            $value = \strtolower($value);
            return 'true' === $value;
        }

        if (\is_object($value) || \is_array($value) || \is_callable($value)) {
            return false;
        }

        // anything else is simply casted to bool
        return (bool) $value;
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Store;

/**
 * The {@link \bitExpert\Disco\Store\SerializableBeanStore} contains all session-aware beans which
 * can be persisted in your preferable format.
 */
class SerializableBeanStore implements BeanStore
{
    /**
     * @var array
     */
    protected $beans;

    /**
     * Creates a new {@link \bitExpert\Disco\Store\SerializableBeanStore}.
     */
    public function __construct()
    {
        $this->beans = [];
    }

    /**
     * {@inheritdoc}
     */
    public function add(string $beanId, $bean): void
    {
        $this->beans[$beanId] = $bean;
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $beanId)
    {
        if (!isset($this->beans[$beanId])) {
            throw new \InvalidArgumentException(
                sprintf('Bean "%s" not defined in store!', $beanId)
            );
        }

        return $this->beans[$beanId];
    }

    /**
     * {@inheritdoc}
     */
    public function has(string $beanId): bool
    {
        return isset($this->beans[$beanId]);
    }

    /**
     * {@inheritDoc}
     */
    public function __sleep()
    {
        return ['beans'];
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\Configuration\MethodGenerator;

use bitExpert\Disco\Annotations\BeanPostProcessor;
use Zend\Code\Generator\MethodGenerator;
use Zend\Code\Reflection\MethodReflection;

class BeanPostProcessorMethod extends ParameterAwareMethodGenerator
{
    /**
     * Creates a new {@link \bitExpert\Disco\Proxy\Configuration\MethodGenerator\BeanPostProcessorMethod}.
     *
     * @param MethodReflection $originalMethod
     * @param BeanPostProcessor $beanPostProcessorMetadata
     * @param GetParameter $parameterValuesMethod
     * @return MethodGenerator
     * @throws \Zend\Code\Generator\Exception\InvalidArgumentException
     */
    public static function generateMethod(
        MethodReflection $originalMethod,
        BeanPostProcessor $beanPostProcessorMetadata,
        GetParameter $parameterValuesMethod
    ): MethodGenerator {
        $method = static::fromReflection($originalMethod);

        $methodParams = static::convertMethodParamsToString(
            $beanPostProcessorMetadata->getParameters(),
            $parameterValuesMethod
        );
        $beanId = $originalMethod->name;
        $body = 'return parent::' . $beanId . '(' . $methodParams . ');' . PHP_EOL;

        $method->setBody($body);
        $method->setDocBlock('{@inheritDoc}');
        return $method;
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco;

use bitExpert\Disco\Proxy\Configuration\AliasContainerInterface;
use bitExpert\Disco\Proxy\Configuration\ConfigurationFactory;

/**
 * {@link \bitExpert\Disco\BeanFactory} implementation.
 *
 * @api
 */
class AnnotationBeanFactory implements BeanFactory
{
    /**
     * @var AliasContainerInterface
     */
    protected $beanConfig;

    /**
     * Creates a new {@link \bitExpert\Disco\BeanFactory}.
     *
     * @param string                   $configClassName
     * @param array                    $parameters
     * @param BeanFactoryConfiguration $config
     */
    public function __construct($configClassName, array $parameters = [], BeanFactoryConfiguration $config = null)
    {
        if ($config === null) {
            $config = new BeanFactoryConfiguration(sys_get_temp_dir());
        }

        $configFactory = new ConfigurationFactory($config);
        $this->beanConfig = $configFactory->createInstance($config, $configClassName, $parameters);
    }

    /**
     * {@inheritDoc}
     * @throws BeanException
     * @throws BeanNotFoundException
     */
    public function get($id)
    {
        if (!is_string($id) || empty($id)) {
            throw new BeanException('Id must be a non-empty string.');
        }

        $instance = null;

        try {
            if (is_callable([$this->beanConfig, $id])) {
                $instance = $this->beanConfig->$id();
            } elseif ($this->beanConfig->hasAlias($id)) {
                $instance = $this->beanConfig->getAlias($id);
            }
        } catch (\Throwable $e) {
            $message = sprintf(
                'Exception occurred while instantiating "%s": %s',
                $id,
                $e->getMessage()
            );

            throw new BeanException($message, $e->getCode(), $e);
        }

        if (null === $instance) {
            throw new BeanNotFoundException(sprintf('"%s" is not defined!', $id));
        }

        return $instance;
    }

    /**
     * {@inheritDoc}
     */
    public function has($id)
    {
        if (!is_string($id) || empty($id)) {
            return false;
        }

        return is_callable([$this->beanConfig, $id]) || $this->beanConfig->hasAlias($id);
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco;

use bitExpert\Disco\Store\BeanStore;
use bitExpert\Disco\Store\SerializableBeanStore;
use InvalidArgumentException;
use ProxyManager\Autoloader\AutoloaderInterface;
use ProxyManager\Configuration;
use ProxyManager\FileLocator\FileLocator;
use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
use ProxyManager\GeneratorStrategy\GeneratorStrategyInterface;

/**
 * BeanFactory configuration class.
 *
 * @api
 */
class BeanFactoryConfiguration
{
    /**
     * @var BeanStore
     */
    protected $sessionBeanStore;
    /**
     * @var string
     */
    protected $proxyTargetDir;
    /**
     * @var GeneratorStrategyInterface
     */
    protected $proxyWriterGenerator;
    /**
     * @var AutoloaderInterface
     */
    protected $proxyAutoloader;

    /**
     * Creates a new {@link \bitExpert\Disco\BeanFactoryConfiguration}.
     *
     * @param string $proxyTargetDir
     * @throws InvalidArgumentException
     */
    public function __construct($proxyTargetDir)
    {
        try {
            $proxyFileLocator = new FileLocator($proxyTargetDir);
        } catch (\Exception $e) {
            throw new InvalidArgumentException(
                sprintf(
                    'Proxy target directory "%s" does not exist!',
                    $proxyTargetDir
                ),
                $e->getCode(),
                $e
            );
        }

        $this->setProxyTargetDir($proxyTargetDir);
        $this->setSessionBeanStore(new SerializableBeanStore());
        $this->setProxyWriterGenerator(new FileWriterGeneratorStrategy($proxyFileLocator));
    }

    /**
     * Sets the directory in which ProxyManager will store the generated proxy classes in.
     *
     * @param string $proxyTargetDir
     * @throws InvalidArgumentException
     */
    public function setProxyTargetDir(string $proxyTargetDir): void
    {
        if (!is_dir($proxyTargetDir)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Proxy target directory "%s" does not exist!',
                    $proxyTargetDir
                ),
                10
            );
        }

        if (!is_writable($proxyTargetDir)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Proxy target directory "%s" is not writable!',
                    $proxyTargetDir
                ),
                20
            );
        }

        $this->proxyTargetDir = $proxyTargetDir;
    }

    /**
     * Sets the {@link \ProxyManager\GeneratorStrategy\GeneratorStrategyInterface} which
     * ProxyManager will use the generate the proxy classes.
     *
     * @param GeneratorStrategyInterface $writergenerator
     */
    public function setProxyWriterGenerator(GeneratorStrategyInterface $writergenerator): void
    {
        $this->proxyWriterGenerator = $writergenerator;
    }

    /**
     * Sets the {@link \ProxyManager\Autoloader\AutoloaderInterface} that should be
     * used by ProxyManager to load the generated classes.
     *
     * @param AutoloaderInterface $autoloader
     * @throws \RuntimeException
     */
    public function setProxyAutoloader(AutoloaderInterface $autoloader): void
    {
        if ($this->proxyAutoloader instanceof AutoloaderInterface) {
            spl_autoload_unregister($this->proxyAutoloader);
        }

        $this->proxyAutoloader = $autoloader;

        spl_autoload_register($this->proxyAutoloader);
    }

    /**
     * Returns the ProxyManager configuration based on the current
     * {@link \bitExpert\Disco\BeanFactoryConfiguration}.
     *
     * @return Configuration
     */
    public function getProxyManagerConfiguration(): Configuration
    {
        $proxyManagerConfiguration = new Configuration();
        $proxyManagerConfiguration->setProxiesTargetDir($this->proxyTargetDir);

        if ($this->proxyWriterGenerator instanceof GeneratorStrategyInterface) {
            $proxyManagerConfiguration->setGeneratorStrategy($this->proxyWriterGenerator);
        }

        if ($this->proxyAutoloader instanceof AutoloaderInterface) {
            $proxyManagerConfiguration->setProxyAutoloader($this->proxyAutoloader);
        }

        return $proxyManagerConfiguration;
    }

    /**
     * Returns the configured {@link \bitExpert\Disco\Store\BeanStore} used to store
     * the session-aware beans in.
     *
     * @return BeanStore
     */
    public function getSessionBeanStore(): BeanStore
    {
        return $this->sessionBeanStore;
    }

    /**
     * Sets the {@link \bitExpert\Disco\Store\BeanStore} instance used to store the
     * session-aware beans.
     *
     * @param BeanStore $sessionBeanStore
     */
    public function setSessionBeanStore(BeanStore $sessionBeanStore): void
    {
        $this->sessionBeanStore = $sessionBeanStore;
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\LazyBean;

use Closure;
use ProxyManager\Configuration;
use ProxyManager\Factory\AbstractBaseFactory;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;

/**
 * Factory responsible of producing virtual proxy instances.
 */
class LazyBeanFactory extends AbstractBaseFactory
{
    /**
     * @var LazyBeanGenerator|null
     */
    private $generator;
    /**
     * @var string
     */
    private $beanId;

    /**
     * Creates a new {@link \bitExpert\Disco\Proxy\LazyBean\LazyBeanFactory}.
     *
     * @param string $beanId
     * @param \ProxyManager\Configuration $configuration
     */
    public function __construct($beanId, Configuration $configuration = null)
    {
        parent::__construct($configuration);

        $this->beanId = $beanId;
    }

    /**
     * @param string  $className
     * @param Closure $initializer
     *
     * @return object
     */
    public function createProxy(string $className, Closure $initializer): object
    {
        $proxyClassName = $this->generateProxy($className);

        return new $proxyClassName($this->beanId, $initializer);
    }

    /**
     * {@inheritDoc}
     */
    protected function getGenerator(): ProxyGeneratorInterface
    {
        return $this->generator ?: $this->generator = new LazyBeanGenerator();
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\Configuration;

use bitExpert\Disco\BeanFactoryConfiguration;
use ProxyManager\Factory\AbstractBaseFactory;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;

/**
 * Factory responsible of producing a proxy of the configuration instance.
 */
class ConfigurationFactory extends AbstractBaseFactory
{
    /**
     * @var ConfigurationGenerator
     */
    private $generator;

    /**
     * Creates a new {@link \bitExpert\Disco\Proxy\Configuration\ConfigurationFactory}.
     *
     * @param BeanFactoryConfiguration $config
     */
    public function __construct(BeanFactoryConfiguration $config)
    {
        parent::__construct($config->getProxyManagerConfiguration());

        $this->generator = new ConfigurationGenerator();
    }

    /**
     * Creates an instance of the given $configClassName.
     *
     * @param BeanFactoryConfiguration $config
     * @param string $configClassName name of the configuration class
     * @param array $parameters
     * @return AliasContainerInterface
     */
    public function createInstance(
        BeanFactoryConfiguration $config,
        string $configClassName,
        array $parameters = []
    ): AliasContainerInterface {
        $proxyClassName = $this->generateProxy($configClassName);
        return new $proxyClassName($config, $parameters);
    }

    /**
     * {@inheritDoc}
     */
    protected function getGenerator(): ProxyGeneratorInterface
    {
        return $this->generator;
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\Configuration;

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\BeanPostProcessor;
use bitExpert\Disco\Annotations\Configuration;
use bitExpert\Disco\Annotations\Parameters;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\BeanMethod;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\BeanPostProcessorMethod;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\Constructor;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\GetAlias;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\GetParameter;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\HasAlias;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\MagicSleep;
use bitExpert\Disco\Proxy\Configuration\MethodGenerator\WrapBeanAsLazy;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\AliasesProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\BeanFactoryConfigurationProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\BeanPostProcessorsProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\ForceLazyInitProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\ParameterValuesProperty;
use bitExpert\Disco\Proxy\Configuration\PropertyGenerator\SessionBeansProperty;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Exception;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ReflectionClass;
use ReflectionMethod;
use Zend\Code\Generator\ClassGenerator;
use Zend\Code\Generator\Exception\InvalidArgumentException;
use Zend\Code\Reflection\MethodReflection;

/**
 * Generator for configuration classes.
 */
class ConfigurationGenerator implements ProxyGeneratorInterface
{
    /**
     * Creates a new {@link \bitExpert\Disco\Proxy\Configuration\ConfigurationGenerator}.
     */
    public function __construct()
    {
        // registers all required annotations
        AnnotationRegistry::registerFile(__DIR__ . '/../../Annotations/Bean.php');
        AnnotationRegistry::registerFile(__DIR__ . '/../../Annotations/Alias.php');
        AnnotationRegistry::registerFile(__DIR__ . '/../../Annotations/BeanPostProcessor.php');
        AnnotationRegistry::registerFile(__DIR__ . '/../../Annotations/Configuration.php');
        AnnotationRegistry::registerFile(__DIR__ . '/../../Annotations/Parameter.php');
    }

    /**
     * {@inheritDoc}
     * @throws InvalidProxiedClassException
     * @throws InvalidArgumentException
     * @throws \ReflectionException
     */
    public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator)
    {
        CanProxyAssertion::assertClassCanBeProxied($originalClass, false);

        $annotation = null;
        $forceLazyInitProperty = new ForceLazyInitProperty();
        $sessionBeansProperty = new SessionBeansProperty();
        $postProcessorsProperty = new BeanPostProcessorsProperty();
        $parameterValuesProperty = new ParameterValuesProperty();
        $beanFactoryConfigurationProperty = new BeanFactoryConfigurationProperty();
        $aliasesProperty = new AliasesProperty();
        $getParameterMethod = new GetParameter($parameterValuesProperty);
        $wrapBeanAsLazyMethod = new WrapBeanAsLazy($beanFactoryConfigurationProperty);

        try {
            $reader = new AnnotationReader();
            $annotation = $reader->getClassAnnotation($originalClass, Configuration::class);
        } catch (Exception $e) {
            throw new InvalidProxiedClassException($e->getMessage(), $e->getCode(), $e);
        }

        if (null === $annotation) {
            throw new InvalidProxiedClassException(
                sprintf(
                    '"%s" seems not to be a valid configuration class. @Configuration annotation missing!',
                    $originalClass->name
                )
            );
        }

        $classGenerator->setExtendedClass($originalClass->getName());
        $classGenerator->setImplementedInterfaces([AliasContainerInterface::class]);
        $classGenerator->addPropertyFromGenerator($forceLazyInitProperty);
        $classGenerator->addPropertyFromGenerator($sessionBeansProperty);
        $classGenerator->addPropertyFromGenerator($postProcessorsProperty);
        $classGenerator->addPropertyFromGenerator($parameterValuesProperty);
        $classGenerator->addPropertyFromGenerator($beanFactoryConfigurationProperty);
        $classGenerator->addPropertyFromGenerator($aliasesProperty);

        $postProcessorMethods = [];
        $parentAliases = [];
        $localAliases = [];
        $methods = $originalClass->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
        foreach ($methods as $method) {
            $methodReflection = new MethodReflection(
                $method->class,
                $method->name
            );

            /** @var \bitExpert\Disco\Annotations\Bean|null $beanAnnotation */
            $beanAnnotation = $reader->getMethodAnnotation($method, Bean::class);
            if (null === $beanAnnotation) {
                /** @var \bitExpert\Disco\Annotations\BeanPostProcessor $beanAnnotation */
                $beanAnnotation = $reader->getMethodAnnotation($method, BeanPostProcessor::class);
                if ($beanAnnotation instanceof BeanPostProcessor) {
                    $postProcessorMethods[] = $method->name;

                    $proxyMethod = BeanPostProcessorMethod::generateMethod(
                        $methodReflection,
                        $beanAnnotation,
                        $getParameterMethod
                    );
                    $classGenerator->addMethodFromGenerator($proxyMethod);
                    continue;
                }

                if ($method->isProtected()) {
                    continue;
                }

                // every method needs either @Bean or @PostPostprocessor annotation
                throw new InvalidProxiedClassException(
                    sprintf(
                        'Method "%s" on "%s" is missing the @Bean annotation!',
                        $method->name,
                        $originalClass->name
                    )
                );
            }

            foreach ($beanAnnotation->getAliases() as $beanAlias) {
                $alias = $beanAlias->isTypeAlias() ? (string) $method->getReturnType() : $beanAlias->getName();
                if (empty($alias)) {
                    continue;
                }

                $hasAlias = '';
                if ($method->getDeclaringClass()->name === $originalClass->name) {
                    $hasAlias = $localAliases[$alias] ?? '';
                } else {
                    $hasAlias= $parentAliases[$alias] ?? '';
                }

                if ($hasAlias !== '') {
                    throw new InvalidProxiedClassException(
                        sprintf(
                            'Alias "%s" of method "%s" on "%s" is already used by method "%s" of another Bean!'
                            . ' Did you use a type alias twice?',
                            $alias,
                            $method->name,
                            $originalClass->name,
                            $hasAlias
                        )
                    );
                }

                if ($method->getDeclaringClass()->name === $originalClass->name) {
                    $localAliases[$alias] = $method->name;
                } else {
                    $parentAliases[$alias] = $method->name;
                }
            }

            $proxyMethod = BeanMethod::generateMethod(
                $methodReflection,
                $beanAnnotation,
                $method->getReturnType(),
                $forceLazyInitProperty,
                $sessionBeansProperty,
                $postProcessorsProperty,
                $beanFactoryConfigurationProperty,
                $getParameterMethod,
                $wrapBeanAsLazyMethod
            );

            $classGenerator->addMethodFromGenerator($proxyMethod);
        }

        $aliasesProperty->setDefaultValue($parentAliases + $localAliases);

        $classGenerator->addMethodFromGenerator(
            new Constructor(
                $parameterValuesProperty,
                $sessionBeansProperty,
                $beanFactoryConfigurationProperty,
                $postProcessorsProperty,
                $postProcessorMethods
            )
        );
        $classGenerator->addMethodFromGenerator($wrapBeanAsLazyMethod);
        $classGenerator->addMethodFromGenerator($getParameterMethod);
        $classGenerator->addMethodFromGenerator(
            new MagicSleep(
                $originalClass,
                $sessionBeansProperty
            )
        );
        $classGenerator->addMethodFromGenerator(
            new GetAlias($aliasesProperty)
        );
        $classGenerator->addMethodFromGenerator(
            new HasAlias($aliasesProperty)
        );
    }
}


<?php

/*
 * This file is part of the Disco package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\Disco\Proxy\Configuration\MethodGenerator;

use bitExpert\Disco\Annotations\Parameter;
use Zend\Code\Generator\MethodGenerator;

/**
 * Base class for all annotations that are parameter-aware.
 */
class ParameterAwareMethodGenerator extends MethodGenerator
{
    /**
     * Converts the Parameter annotations to the respective getParameter() method calls to retrieve the configuration
     * values.
     *
     * @param Parameter[] $methodParameters
     * @param GetParameter $parameterValuesMethod
     * @return string
     */
    protected static function convertMethodParamsToString(
        array $methodParameters,
        GetParameter $parameterValuesMethod
    ): string {
        $parameters = [];
        foreach ($methodParameters as $methodParameter) {
            /** @var Parameter $methodParameter */
            $defaultValue = $methodParameter->getDefaultValue();
            switch (\gettype($defaultValue)) {
                case 'string':
                    $defaultValue = '"' . $defaultValue . '"';
                    break;
                case 'boolean':
                    $defaultValue = $defaultValue ? 'true' : 'false';
                    break;
                case 'NULL':
                    $defaultValue = 'null';
                    break;
                default:
                    break;
            }

            $template = empty($defaultValue) ? '$this->%s("%s", %s)' : '$this->%s("%s", %s, %s)';
            $required = $methodParameter->isRequired() ? 'true' : 'false';
            $methodName = $parameterValuesMethod->getName();
            $parameters[] = \sprintf($template, $methodName, $methodParameter->getName(), $required, $defaultValue);
        }

        return \implode(', ', $parameters);
    }
}


<?php

/**
 * Class Sheep_Debug_IndexController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_IndexController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * Request profile index page
     */
    public function indexAction()
    {
        $this->_forward('search');
    }

    /**
     * View request profile list
     */
    public function searchAction()
    {
        /** @var Sheep_Debug_Model_Resource_RequestInfo_Collection $requests */
        $requests = $this->_getFilteredRequests();

        $this->loadLayout('sheep_debug');
        /** @var Mage_Page_Block_Html $rootBlock */
        $rootBlock = $this->getLayout()->getBlock('root');
        $rootBlock->setHeaderTitle($this->__('Request profiles'));

        /** @var Sheep_Debug_Block_View $profileListBlock */
        $profileListBlock = $this->getLayout()->getBlock('sheep_debug_list');
        $profileListBlock->setData('results', $requests);

        $this->renderLayout();
    }

    /**
     * View latest request profile
     */
    public function latestAction()
    {
        /** @var Sheep_Debug_Model_Resource_RequestInfo_Collection $profileRequests */
        $profileRequests = Mage::getModel('sheep_debug/requestInfo')->getCollection();
        $profileRequests->addOrder('id', Varien_Data_Collection_Db::SORT_ORDER_DESC);
        $profileRequests->setCurPage(1)->setPageSize(1);

        /** @var Sheep_Debug_Model_RequestInfo $lastProfileRequest */
        $lastProfileRequest = $profileRequests->getFirstItem();

        $this->_forward('view', null, null, array('token' => $lastProfileRequest->getToken()));
    }

    /**
     * View request profile page
     */
    public function viewAction()
    {
        $token = (string)$this->getRequest()->getParam('token');
        if (!$token) {
            $this->getResponse()->setHttpResponseCode(400);
            return $this->_getRefererUrl();
        }

        /** @var Sheep_Debug_Model_RequestInfo $requestInfo */
        $requestInfo = Mage::getModel('sheep_debug/requestInfo')->load($token, 'token');
        if (!$requestInfo->getId()) {
            $this->getResponse()->setHttpResponseCode(404);
            return $this->_getRefererUrl();
        }

        $section = $this->getRequest()->getParam('panel', 'request');
        if (!in_array($section, array('request', 'performance', 'events', 'db', 'logging', 'email', 'layout', 'config'))) {
            $section = 'request';
        }

        Mage::register('sheep_debug_request_info', $requestInfo);

        $blockName = 'sheep_debug_' . $section;
        $blockTemplate = "sheep_debug/view/panel/{$section}.phtml";

        // Add section block to content area
        $this->loadLayout();
        $layout = $this->getLayout();
        $sectionBlock = $layout->createBlock('sheep_debug/view', $blockName, array('template' => $blockTemplate));
        $layout->getBlock('sheep_debug_content')->insert($sectionBlock);

        $layout->getBlock('root')->setHeaderTitle($this->__('Profile for request %s (%s)', $requestInfo->getRequestPath(), $requestInfo->getToken()));

        $this->renderLayout();
    }

    /**
     * Returns lines from log file
     */
    public function viewLogAction()
    {
        $token = $this->getRequest()->getParam('token');
        $log = $this->getRequest()->getParam('log');

        if (!$token || !$log) {
            $this->getResponse()->setHttpResponseCode(400)->setBody('Invalid parameters');
            return;
        }

        /** @var Sheep_Debug_Model_RequestInfo $requestProfile */
        $requestProfile = Mage::getModel('sheep_debug/requestInfo')->load($token, 'token');
        if (!$requestProfile->getId()) {
            $this->getResponse()->setHttpResponseCode(404)->setBody('Request profile not found');
            return;
        }

        try {
            $content = $requestProfile->getLogging()->getLoggedContent($log);
            $this->getResponse()->setHttpResponseCode(200)->setBody($content);
        } catch (Exception $e) {
            $this->getResponse()->setHttpResponseCode(200)->setBody('Unable to retrieve logged content');
        }
    }

    /**
     * Deletes all request profiles
     */
    public function purgeProfilesAction()
    {
        $count = $this->getService()->purgeAllProfiles();
        $this->getSession()->addSuccess($this->__('%d request profiles were deleted', $count));

        $this->_redirect('/');
    }

    /**
     * Initialise request profile collection based on filters set on request
     *
     * @return Sheep_Debug_Model_Resource_RequestInfo_Collection
     */
    protected function _getFilteredRequests()
    {
        /** @var Sheep_Debug_Model_Resource_RequestInfo_Collection $requests */
        $requests = Mage::getModel('sheep_debug/requestInfo')->getCollection();
        $requests->setCurPage(1);
        $requests->setPageSize(Mage::helper('sheep_debug/filter')->getLimitDefaultValue());

        if ($sessionId = $this->getRequest()->getParam('session_id')) {
            $requests->addSessionIdFilter($sessionId);
        }

        if ($ip = $this->getRequest()->getParam('ip')) {
            $requests->addIpFilter($ip);
        }

        if ($method = $this->getRequest()->getParam('method')) {
            $requests->addHttpMethodFilter($method);
        }

        if ($limit = $this->getRequest()->getParam('limit')) {
            $requests->setPageSize($limit);
        }

        if ($path = $this->getRequest()->getParam('path')) {
            $requests->addRequestPathFilter($path);
        }

        if ($token = $this->getRequest()->getParam('token')) {
            $requests->addTokenFilter($token);
        }

        if ($startDate = $this->getRequest()->getParam('start')) {
            $requests->addAfterFilter($startDate);
        }

        if ($endDate = $this->getRequest()->getParam('end')) {
            $requests->addEarlierFilter($endDate);
        }

        if ($page = (int)$this->getRequest()->getParam('page')) {
            $requests->setCurPage($page);
        }

        $requests->addOrder('id', Varien_Data_Collection_Db::SORT_ORDER_DESC);

        return $requests;
    }

}


<?php

/**
 * Class Sheep_Debug_Block_Toolbar
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Block_Toolbar extends Sheep_Debug_Block_Abstract
{
    /** @var  Sheep_Debug_Block_Panel[] */
    protected $visiblePanels;

    /**
     * Render toolbar only if request is allowed
     *
     * @return string
     */
    public function renderView()
    {
        // Render Debug toolbar only if allowed
        if (!$this->helper->canShowToolbar()) {
            return '';
        }

        return parent::renderView();
    }

    /**
     * Returns Sheep Debug module version
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->helper->getModuleVersion();
    }

    /**
     * Returns sorted visible debug panels
     *
     * @return Sheep_Debug_Block_Panel[]
     */
    public function getVisiblePanels()
    {
        if ($this->visiblePanels === null) {
            $this->visiblePanels = array();

            $panels = $this->getSortedChildBlocks();
            foreach ($panels as $panel) {
                if (!$panel instanceof Sheep_Debug_Block_Panel) {
                    continue;
                }

                $this->visiblePanels[] = $panel;
            }
        }
        return $this->visiblePanels;
    }

}


<?php

/**
 * Class Sheep_Debug_Model_RequestInfo
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 *
 * @method boolean getIsStarted()
 * @method void setIsStarted(boolean $value)
 * @method string getToken()
 * @method Sheep_Debug_Model_RequestInfo setToken(string $value)
 * @method string getHttpMethod()
 * @method Sheep_Debug_Model_RequestInfo setHttpMethod(string $value)
 * @method int getStoreId()
 * @method Sheep_Debug_Model_RequestInfo setStoreId(int $value)
 * @method string getRequestPath()
 * @method Sheep_Debug_Model_RequestInfo setRequestPath(string $value)
 * @method int getResponseCode()
 * @method Sheep_Debug_Model_RequestInfo setResponseCode(int $value)
 * @method string getIp()
 * @method Sheep_Debug_Model_RequestInfo setIp(string $value)
 * @method string getSessionId()
 * @method Sheep_Debug_Model_RequestInfo setSessionId(string $value)
 * @method string getDate()
 * @method Sheep_Debug_Model_RequestInfo setDate(string $value)
 * @method Sheep_Debug_Model_RequestInfo setRenderingTime(float $value)
 * @method float getQueryTime()
 * @method Sheep_Debug_Model_RequestInfo setQueryTime(float $value)
 * @method int getQueryCount()
 * @method Sheep_Debug_Model_RequestInfo setQueryCount(int $value)
 * @method Sheep_Debug_Model_RequestInfo setTime(float $value)
 * @method Sheep_Debug_Model_RequestInfo setPeakMemory(int $value)
 * @method  getInfo()
 * @method Sheep_Debug_Model_RequestInfo setInfo($value)
 */
class Sheep_Debug_Model_RequestInfo extends Mage_Core_Model_Abstract
{
    /** @var Sheep_Debug_Model_Logging */
    protected $logging;

    /** @var Sheep_Debug_Model_Controller */
    protected $action;

    /** @var Sheep_Debug_Model_Design */
    protected $design;

    /** @var Sheep_Debug_Model_Block[] */
    protected $blocks = array();

    /** @var Sheep_Debug_Model_Model[] */
    protected $models = array();

    /** @var Sheep_Debug_Model_Collection[] */
    protected $collections = array();

    /** @var array Zend_Db_Profiler_Query */
    protected $queries = null;

    protected $timers = array();
    protected $events;
    protected $observers;

    /** @var Sheep_Debug_Model_Email[] */
    protected $emails = array();

    /**
     * Initialises logging by registering commong log files
     */
    public function initLogging()
    {
        $helper = Mage::helper('sheep_debug');
        $this->logging = Mage::getModel('sheep_debug/logging');

        $this->logging->addFile($helper->getLogFilename($this->getStoreId()));
        $this->logging->addFile($helper->getExceptionLogFilename($this->getStoreId()));

        Mage::dispatchEvent('sheep_debug_init_logging', array('logging' => $this->logging));

        $this->logging->startRequest();
    }

    /**
     * Marks logging as completed.
     */
    public function completeLogging()
    {
        $this->getLogging()->endRequest();
    }

    /**
     * Returns timers registered by Varien Profiler
     *
     * @see Varien_Profiler
     * @return array
     */
    public function getTimers()
    {
        return $this->timers;
    }

    /**
     * Returns list of events dispatched during request.
     * This is extracted from data captured by Varien Profiler
     *
     * @return array
     */
    public function getEvents()
    {
        if ($this->events === null) {
            $this->events = array();

            foreach ($this->getTimers() as $timerName => $timer) {
                if (strpos($timerName, 'DISPATCH EVENT:') === 0) {
                    $this->events[str_replace('DISPATCH EVENT:', '', $timerName)] = array(
                        'name'     => str_replace('DISPATCH EVENT:', '', $timerName),
                        'count'    => $timer['count'],
                        'sum'      => round($timer['sum'] * 1000, 2), // ms
                        'mem_diff' => $timer['realmem'] / pow(1024, 2), // mb
                    );
                }
            }
        }

        return $this->events;
    }

    /**
     * Returns a list of called observers during request.
     * Observers are determined based on Varien Profiler timers
     *
     * @return array
     */
    public function getObservers()
    {
        if ($this->observers === null) {
            $this->observers = array();

            foreach ($this->getTimers() as $timerName => $timer) {
                if (strpos($timerName, 'OBSERVER') === 0) {
                    $this->observers[] = array(
                        'name'     => $timerName,
                        'count'    => $timer['count'],
                        'sum'      => round($timer['sum'] * 1000, 2), // ms
                        'mem_diff' => $timer['realmem'] / pow(1024, 2), // MB
                    );
                }
            }
        }

        return $this->observers;
    }

    /**
     * Sets Varien Profiler timers
     *
     * @param array $timers
     */
    public function setTimers($timers)
    {
        $this->timers = $timers;
    }

    /**
     * Adds sent e-mail info
     *
     * @param Sheep_Debug_Model_Email $email
     */
    public function addEmail(Sheep_Debug_Model_Email $email)
    {
        $this->emails[] = $email;
    }

    /**
     * Returns e-mails sent during request
     *
     * @return Sheep_Debug_Model_Email[]
     */
    public function getEmails()
    {
        return $this->emails;
    }

    /**
     * Returns captured logging information
     *
     * @return Sheep_Debug_Model_Logging
     */
    public function getLogging()
    {
        return $this->logging;
    }

    /**
     * Captures information from controller
     *
     * @param Mage_Core_Controller_Varien_Action $controllerAction
     */
    public function initController($controllerAction = null)
    {
        /** @var Sheep_Debug_Model_Controller $controller */
        $controller = Mage::getModel('sheep_debug/controller');
        $controller->init($controllerAction);
        $this->action = $controller;
    }

    /**
     * Returns request/response/controller model
     *
     * @return Sheep_Debug_Model_Controller
     */
    public function getController()
    {
        return $this->action;
    }

    /**
     * Captures layout information
     *
     * @param Mage_Core_Model_Layout $layout
     * @param Mage_Core_Model_Design_Package $design
     */
    public function addLayout(Mage_Core_Model_Layout $layout, Mage_Core_Model_Design_Package $design)
    {
        $this->getDesign()->init($layout, $design);
    }

    /**
     * Returns design model
     *
     * @return Sheep_Debug_Model_Design
     */
    public function getDesign()
    {
        return $this->design;
    }

    /**
     * Adds block
     *
     * @param Mage_Core_Block_Abstract $block
     * @return Sheep_Debug_Model_Block
     */
    public function addBlock(Mage_Core_Block_Abstract $block)
    {
        $blockInfo = Mage::getModel('sheep_debug/block');
        $blockInfo->init($block);
        $key = $blockInfo->getName();

        return $this->blocks[$key] = $blockInfo;
    }

    /**
     * Returns block information associated to specified block name
     *
     * @param $blockName
     * @return Sheep_Debug_Model_Block
     * @throws Exception
     */
    public function getBlock($blockName)
    {
        if (!array_key_exists($blockName, $this->blocks)) {
            throw new Exception('Unable to find block with name ' . $blockName);
        }

        return $this->blocks[$blockName];
    }

    /**
     * Returns information about instantiated/rendered blocks
     *
     * @return Sheep_Debug_Model_Block[]
     */
    public function getBlocks()
    {
        return $this->blocks;
    }

    /**
     * Returns information about instantiated/rendered blocks
     *
     * @return array
     */
    public function getBlocksAsArray()
    {
        $helper = Mage::helper('sheep_debug');
        $data = array();
        foreach ($this->getBlocks() as $block) {
            $data[] = array(
                'name'     => $block->getName(),
                'class'    => $block->getClass(),
                'template' => $block->getTemplateFile(),
                'time (ms)' => $block->getRenderedDuration() ? $helper->formatNumber($block->getRenderedDuration(), 0) : '',
                'count'    => $block->getRenderedCount()
            );
        }

        return $data;
    }

    /**
     * Adds loaded collection
     *
     * @param Varien_Data_Collection_Db $collection
     */
    public function addCollection(Varien_Data_Collection_Db $collection)
    {
        $info = Mage::getModel('sheep_debug/collection');
        $info->init($collection);
        $key = $info->getClass();

        if (!array_key_exists($key, $this->collections)) {
            $this->collections[$key] = $info;
        }

        $this->collections[$key]->incrementCount();
    }

    /**
     * Returns information about loaded collections
     *
     * @return Sheep_Debug_Model_Collection[]
     */
    public function getCollections()
    {
        return $this->collections;
    }

    /**
     * Returns information about loaded collections
     *
     * @return array
     */
    public function getCollectionsAsArray()
    {
        $data = array();

        foreach ($this->getCollections() as $collection) {
            $data[] = array(
                'type'  => $collection->getType(),
                'class' => $collection->getClass(),
                'sql'   => $collection->getQuery(),
                'count' => $collection->getCount()
            );
        }

        return $data;
    }

    /**
     * Adds loaded model
     *
     * @param Mage_Core_Model_Abstract $model
     */
    public function addModel(Mage_Core_Model_Abstract $model)
    {
        $modelInfo = Mage::getModel('sheep_debug/model');
        $modelInfo->init($model);
        $key = $modelInfo->getClass();

        if (!array_key_exists($key, $this->models)) {
            $this->models[$key] = $modelInfo;
        }

        $this->models[$key]->incrementCount();
    }

    /**
     * Returns information about captured models
     *
     * @return Sheep_Debug_Model_Model[]
     */
    public function getModels()
    {
        return $this->models;
    }

    /**
     * Returns information about captured models
     *
     * @return array
     */
    public function getModelsAsArray()
    {
        $data = array();

        foreach ($this->getModels() as $model) {
            $data[] = array(
                'resource_name' => $model->getResource(),
                'class'         => $model->getClass(),
                'count'         => $model->getCount()
            );
        }

        return $data;
    }

    /**
     * Sets request query from current sql profiler
     */
    public function initQueries()
    {
        $this->queries = array();

        $profiler = Mage::helper('sheep_debug')->getSqlProfiler();
        if ($profiler->getEnabled() && $profiler instanceof Sheep_Debug_Model_Db_Profiler) {
            /** @var Zend_Db_Profiler_Query[] $queries */
            $this->queries = $profiler->getQueryModels() ?: array();
            $this->setQueryCount($profiler->getTotalNumQueries());
            $this->setQueryTime($profiler->getTotalElapsedSecs());
        }
    }

    /**
     * Returns SQL queries executed during request.
     *
     * @return Zend_Db_Profiler_Query[]
     */
    public function getQueries()
    {
        if ($this->queries === null) {
            $this->initQueries();
        }

        return $this->queries;
    }

    /**
     * Returns peak memory in bytes.
     *
     * @return int
     */
    public function getPeakMemory()
    {
        return $this->hasData('peak_memory') ? $this->getData('peak_memory') : Mage::helper('sheep_debug')->getMemoryUsage();
    }

    /**
     * Returns script execution time in seconds
     *
     * @return float
     */
    public function getTime()
    {
        return $this->hasData('time') ? $this->getData('time') : Mage::helper('sheep_debug')->getCurrentScriptDuration();
    }

    /**
     * Returns rendering time in miliseconds
     *
     * @return float
     */
    public function getRenderingTime()
    {
        return $this->hasData('rendering_time') ? $this->getData('rendering_time') : Sheep_Debug_Model_Block::getTotalRenderingTime();
    }

    protected $_eventPrefix = 'sheep_debug_requestInfo';

    protected function _construct()
    {
        $this->_init('sheep_debug/requestInfo');
        $this->design = Mage::getModel('sheep_debug/design');
    }

    /**
     * Generates a unique id that identifies a request.
     *
     * @return string
     */
    public function generateToken()
    {
        return sprintf('%x', crc32(uniqid($this->getController()->getSessionId(), true)));
    }

    /**
     * Serialize fields that are stored in info blob
     *
     * @return string
     */
    public function getSerializedInfo()
    {
        return serialize(array(
            'logging'     => $this->getLogging(),
            'action'      => $this->getController(),
            'design'      => $this->getDesign(),
            'blocks'      => $this->getBlocks(),
            'models'      => $this->getModels(),
            'collections' => $this->getCollections(),
            'queries'     => $this->getQueries(),
            'timers'      => $this->getTimers(),
            'emails'      => $this->getEmails()
        ));
    }

    /**
     * Unserialize info blob
     *
     * @return mixed
     */
    public function getUnserializedInfo()
    {
        return unserialize($this->getInfo());
    }

    /**
     * Returns absolute url for current request
     *
     * @return string
     */
    public function getAbsoluteUrl()
    {
        return Mage::getUrl('', array('_store' => $this->getStoreId(), '_direct' => ltrim($this->getRequestPath(), '/')));
    }

    /**
     * Initialize persistent fields that are used as filters and prepare info blob
     *
     * @return $this
     */
    protected function _beforeSave()
    {
        parent::_beforeSave();

        if (!$this->getId()) {
            $this->setToken($this->generateToken());
            $this->setHttpMethod($this->getController()->getHttpMethod());
            $this->setResponseCode($this->getController()->getResponseCode());
            $this->setIp($this->getController()->getRemoteIp());
        }

        $this->setRequestPath($this->getController()->getRequestOriginalPath());
        $this->setSessionId($this->getController()->getSessionId());
        $this->setInfo($this->getSerializedInfo());

        return $this;
    }

    /**
     * Initialize fields that are saved in info blob
     *
     * @return Mage_Core_Model_Abstract
     */
    protected function _afterLoad()
    {
        $info = $this->getUnserializedInfo();

        $this->logging = $info['logging'];
        $this->action = $info['action'];
        $this->design = $info['design'];
        $this->blocks = $info['blocks'];
        $this->models = $info['models'];
        $this->collections = $info['collections'];
        $this->queries = $info['queries'];
        $this->timers = $info['timers'];
        $this->emails = $info['emails'];

        return parent::_afterLoad();
    }

}