Search code examples
redisprofilersymfony

Symfony3 Profiler Storage


in the Docs

http://symfony.com/doc/master/cookbook/profiler/storage.html

you still can find Information about Profiler Storage. I just checked the code and could not find any clues how to set a custom storage. I also find no Documentation stating this except some @legacy notes in the Original Source at 2.8.

Is there a Reason why this was removed? I was using redis to store this data with a lifetime of eta 1hour. Now I need to run a manual cleanup to whipe all files in that directory. If anybody has some clues or hints on helping me with this issue are appreceated ^^

Chris


Solution

  • Thanks to the Tip of Matteo I was able to solve this quite flexible. The Team of Symfony removed this, because it was hard coded into the Profiler Subsystem. Instead of fixing this by adding a class parameter I had to solve it. :)

    Ok, here is the code, If somebody needs this to.

    First of all we need the Original Classes from Symfony 2.7 (at least I reused them as I only need the Redis Option ( I use it, because I can Compress the data using igbinary)

    Next you need to implement a Compiler Pass.

            namespace AcmeBunlde\DependencyInjection\CompilerPass;
    
            use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
            use Symfony\Component\DependencyInjection\ContainerBuilder;
    
            class ProfilerCompilerPass implements CompilerPassInterface
            {
    
                /**
                 * You can modify the container here before it is dumped to PHP code.
                 *
                 * @param ContainerBuilder $container
                 */
                public function process(ContainerBuilder $container)
                {
                    $definition = $container->getDefinition('profiler');
                    $definition->addArgument('%acmebundle.profiler.defaultEnabled%');
                    $definition->addArgument('%acmebundle.profiler.class%');
                    $definition->addArgument('%acmebundle.profiler.dsn%');
                    $definition->addArgument('%acmebundle.profiler.username%');
                    $definition->addArgument('%acmebundle.profiler.password%');
                    $definition->addArgument('%acmebundle.profiler.ttl%');
                    $definition->setClass('acmebundle\Profiler\Profiler');
                }
            }
    

    This needs to be loaded inside the Bundle Loader:

        public function build(ContainerBuilder $container)
        {
            ...
            $container->addCompilerPass(new ProfilerCompilerPass());
        }
    

    After this we need to add the Configuration for the New Profiler Storage in the DependencyInjection Folder.

        namespace AcmeBundle\DependencyInjection;
    
        use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
        use Symfony\Component\Config\Definition\Builder\TreeBuilder;
        use Symfony\Component\Config\Definition\ConfigurationInterface;
    
        /**
         * This is the class that validates and merges configuration from your app/config files
         *
         * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
         * @author Chris
         */
        class Configuration implements ConfigurationInterface
            /**
             * {@inheritdoc}
             */
            public function getConfigTreeBuilder()
            {
                $treeBuilder = new TreeBuilder();
                $rootNode = $treeBuilder->root('library');
    
                $rootNode
                    ->children()
                        ->arrayNode('profiler')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->booleanNode('defaultStorage')
                                    ->defaultTrue()
                                ->end()
                                ->scalarNode('class')
                                    ->defaultValue('')
                                ->end()
                                ->scalarNode('dsn')
                                    ->defaultValue('')
                                ->end()
                                ->scalarNode('username')
                                    ->defaultValue('')
                                ->end()
                                ->scalarNode('password')
                                    ->defaultValue('')
                                ->end()
                                ->scalarNode('ttl')
                                    ->defaultValue('3600')
                                ->end()
                            ->end()
                        ->end();
                return $treeBuilder();
            }
        }
    

    Now set the Default Values in The Dependency Injection Bundle Loader

        <?php
    
        namespace AcmeBundle\DependencyInjection;
    
        use Symfony\Component\DependencyInjection\ContainerBuilder;
        use Symfony\Component\Config\FileLocator;
        use Symfony\Component\HttpKernel\DependencyInjection\Extension;
        use Symfony\Component\DependencyInjection\Loader;
        use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
    
        /**
         * This is the class that loads and manages your bundle configuration
         *
         * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
         * @author Chris 
         */
        class AcmeExtension extends Extension
        {
            /**
             * {@inheritdoc}
             */
            public function load(array $configs, ContainerBuilder $container)
            {
    
                $configuration = new Configuration();
                $config = $this->processConfiguration($configuration, $configs);
                ...
                $container->setParameter('acmebundle.profiler.defaultEnabled',$config['profiler']['defaultStorage']);
                $container->setParameter('acmebundle.profiler.class',$config['profiler']['class']);
                $container->setParameter('acmebundle.profiler.dsn',$config['profiler']['dsn']);
                $container->setParameter('acmebundle.profiler.username',$config['profiler']['username']);
                $container->setParameter('acmebundle.profiler.password',$config['profiler']['password']);
                $container->setParameter('acmebundle.profiler.ttl',$config['profiler']['ttl']);
                ...
            }
            ...
        }
    

    As Last Step you need to build a basic container for adding the new Profiler Handler. I have choosen to implement it not to complex:

        <?php
    
            namespace AcmeBundle\Profiler;
            use Psr\Log\LoggerInterface;
            use \Symfony\Component\HttpKernel\Profiler\Profiler as ProfilerSrc;
            use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
    
            /**
             * Profiler.
             */
            class Profiler extends ProfilerSrc
            {
                public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger, $defaultEnabled=true,$class=null,$dsn=null,$username=null,$password=null,$ttl=3600)
                {
                    if($defaultEnabled!==true)
                    {
                        $storage = new $class($dsn,$username,$password,$ttl);
                    }
                    parent::__construct($storage , $logger);
                }
            }
    

    I have also added a Library to define the Constructor of the Storage Interface.

        <?php
    
            namespace AcmeBundle\Profiler;
    
            use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface as ProfilerStorageSource;
    
            interface ProfilerStorageInterface extends ProfilerStorageSource
            {
                /**
                 * ProfilerStorageInterface constructor.
                 *
                 * @param $dsn
                 * @param $username
                 * @param $password
                 * @param $ttl
                 */
                public function __construct($dsn,$username,$password,$ttl);
            }
    

    All you need to do now is to define some Options in your config_dev.yml file.

    acmebundle:
        profiler:
            defaultEnabled: false
            class:CLASSNAME INCLUDING NAMESPACE
            dsn: redis://localhost/1
            username: 
            password
            ttl: 3600
    

    with defaultEnabled = true you can reenable to Original Handler. the rest is, I believe self explaining. username + password is from the original feature set. (ttl == lifetime)

    I hope this helps somebody else as well :)