├── .travis.yml ├── Tests ├── bootstrap.php └── Twig │ └── TemplateLoaderTest.php ├── JmABBundle.php ├── phpunit.xml.dist ├── composer.json ├── Entity ├── TemplateRepository.php ├── TemplateManager.php └── Template.php ├── DependencyInjection ├── Configuration.php ├── Compiler │ └── TwigPass.php └── JmABExtension.php ├── README.markdown ├── Resources ├── config │ └── services.xml └── doc │ └── index.md └── Twig └── TemplateLoader.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | 6 | before_script: 7 | - composer install --dev 8 | 9 | script: phpunit 10 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new TwigPass(), PassConfig::TYPE_REMOVE); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ./Tests 8 | 9 | 10 | 11 | 12 | 13 | ./ 14 | 15 | ./Resources 16 | ./Tests 17 | ./vendor 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jm/ab-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Simple way to manage multi-versions of templates (for AB Testing)", 5 | "keywords": ["AB Testing", "template", "twig"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jeremy Marc", 10 | "email": "jeremy.marc@me.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.2", 15 | "twig/twig": ">=1.7", 16 | "symfony/dependency-injection": ">=2.0", 17 | "symfony/http-foundation": "*" 18 | }, 19 | "target-dir": "Jm/ABBundle", 20 | "autoload": { 21 | "psr-0": { "Jm\\ABBundle": "" } 22 | } 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Entity/TemplateRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('t') 20 | ->where('t.name = :name') 21 | ->setParameter('name', $name) 22 | ->getQuery() 23 | ->useResultCache(true, $cacheTime) 24 | ; 25 | 26 | $template = $query->getSingleResult(); 27 | } catch (NoResultException $e) { 28 | $template = null; 29 | } 30 | 31 | return $template; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('jm_ab'); 22 | 23 | $rootNode->children() 24 | ->scalarNode('variation')->defaultValue('b')->end() 25 | ->booleanNode('custom_loader') 26 | ->defaultTrue() 27 | ->end() 28 | ->scalarNode('cache_time')->defaultValue(3600)->end() 29 | ; 30 | 31 | return $treeBuilder; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | JmABBundle 2 | ========== 3 | 4 | The symfony2 ABBundle provide an easy way to manage application templates which 5 | can be used for AB Testing (or not). 6 | 7 | Features include: 8 | 9 | - Manage Template 10 | - Load Template from DB using Twig Loader 11 | - Use a custom variation parameter to switch between the 2 versions (A/B) of one template 12 | 13 | Documentation 14 | ------------- 15 | 16 | The bulk of the documentation is stored in the `Resources/doc/index.md` 17 | file in this bundle: 18 | 19 | [Read the Documentation for master](https://github.com/jeremymarc/JmABBundle/blob/master/Resources/doc/index.md) 20 | 21 | Installation 22 | ------------ 23 | [![Build Status](https://travis-ci.org/jeremymarc/JmABBundle.png?branch=master)](https://travis-ci.org/jeremymarc/JmABBundle) 24 | 25 | All the installation instructions are located in [documentation](https://github.com/jeremymarc/JmABBundle/blob/master/Resources/doc/index.md). 26 | 27 | 28 | Reporting an issue or a feature request 29 | --------------------------------------- 30 | 31 | Issues and feature requests are tracked in the [Github issue tracker](https://github.com/jeremymarc/JmABBundle/issues). 32 | 33 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/TwigPass.php: -------------------------------------------------------------------------------- 1 | getParameter('jm_ab.custom_loader')) { 13 | $loader = $container->getDefinition('twig.loader'); 14 | 15 | $class = $loader->getClass(); 16 | if (preg_match("/%(.*)%/", $class, $m)) { 17 | $class = $container->getParameter($m[1]); 18 | } 19 | 20 | if ("Twig\Loader\Chain" === $class) { 21 | $loader->addMethodCall('addLoader', array($container->getDefinition('jm_ab.template_loader'))); 22 | return; 23 | } 24 | 25 | $abTwigLoader = $container->getDefinition('jm_ab.twig_loader'); 26 | $abTwigLoader->addMethodCall('addLoader', array($loader)); 27 | $container->setDefinition('twig.loader', $abTwigLoader); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DependencyInjection/JmABExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 25 | 26 | $container->setParameter('jm_ab.variation', $config['variation']); 27 | $container->setParameter('jm_ab.custom_loader', $config['custom_loader']); 28 | $container->setParameter('jm_ab.cache_time', $config['cache_time']); 29 | 30 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 31 | $loader->load('services.xml'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | b 9 | Jm\ABBundle\Entity\Template 10 | Jm\ABBundle\Entity\TemplateManager 11 | Twig_Loader_Chain 12 | 13 | 14 | 15 | 16 | 17 | %jm_ab.template.class% 18 | 19 | %jm_ab.cache_time% 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Entity/TemplateManager.php: -------------------------------------------------------------------------------- 1 | em = $em; 21 | $this->repository = $em->getRepository($class); 22 | $this->class = $class; 23 | $this->container = $container; 24 | $this->cacheTime = $cacheTime; 25 | 26 | $this->cache = array(); 27 | } 28 | 29 | public function getTemplate($name) 30 | { 31 | $name = trim($name); 32 | if (isset($this->cache[$name])) { 33 | return $this->cache[$name]; 34 | } 35 | 36 | $template = $this->findTemplateByName($name); 37 | 38 | return $this->cache[$name] = $template; 39 | } 40 | 41 | public function renderTemplate($templateName, $vars = array()) 42 | { 43 | if (0 !== strpos($templateName, 'template:')) { 44 | $templateName = "template:$templateName"; 45 | } 46 | return $this->container->get('twig')->render($templateName, $vars); 47 | } 48 | 49 | /* 50 | * Do not call findTemplateByName directly 51 | * Use getTemplate instead as it's adding a cache support 52 | */ 53 | protected function findTemplateByName($name) 54 | { 55 | return $this->repository->findTemplateByName($name, $this->cacheTime); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Resources/doc/index.md: -------------------------------------------------------------------------------- 1 | Getting started with JmABBundle 2 | =============================== 3 | 4 | The symfony2 ABBundle provide an easy way to manage application templates which can be used for AB Testing (or not). 5 | 6 | 7 | ## Installation 8 | 9 | ### Step 1: Download JmABBundle using composer 10 | 11 | Add JmABBundle in your composer.json: 12 | 13 | ```js 14 | { 15 | "require": { 16 | "jm/ab-bundle": "*" 17 | } 18 | } 19 | ``` 20 | 21 | Now tell composer to download the bundle by running the command: 22 | 23 | ``` bash 24 | $ php composer.phar update jm/ab-bundle 25 | ``` 26 | 27 | Composer will install the bundle to your project's `vendor/jm` directory. 28 | 29 | 30 | ### Step 2: Enable the bundle 31 | ```php 32 | public function registerBundles() 33 | { 34 | $bundles = array( 35 | // ... 36 | new Jm\ABBundle\JmABBundle(), 37 | ); 38 | } 39 | ``` 40 | 41 | ### Step 3: Configuration 42 | 43 | Configure the bundle using the dic configuration file 44 | ```php 45 | jm_ab: 46 | custom_loader: true 47 | variation: b 48 | cache_time: 3600 49 | ``` 50 | 51 | ##### custom_loader: (default value true) 52 | Allow the bundle to chain our custom Twig loader to the current loader 53 | (Twig_Loader_Chain). When enable, you can render a template using twig, doing : 54 | ```php 55 | $this->get('twig')->render('template:name'); 56 | ``` 57 | Or in a twig template : 58 | ```php 59 | - include "template:name"|raw 60 | ``` 61 | 62 | You can disable the loader with custom_loader: false 63 | 64 | ##### variation: (default value b) 65 | This is the default value for the variation (version B) of the page. 66 | To switch from one version to another one, just use the variation parameter in the url : 67 | ```html 68 | http://url.com/?variation_parameter -> http://url.com/?b 69 | ``` 70 | 71 | If you want to insert the Google Analytics Content Experiment script (for AB 72 | Testing), just insert the {{ GAexperimentScript }} variable in the template. 73 | It will be automatically replaced by the GA JavaScripts (only if you have 74 | specified the experiment code in the Template). 75 | 76 | You can load a Template from a controller using TemplateManager : 77 | ```php 78 | $this->get('jm_ab.template_manager')->getTemplate('name', $vars); 79 | ``` 80 | or with custom_loader set to true : 81 | ```php 82 | $this->get('jm_ab.template_manager')->renderTemplate('name') or ; 83 | $this->get('jm_ab.template_manager')->renderTemplate('template:name', 84 | $vars); 85 | ``` 86 | Note that 'template:' and $vars are optionals. 87 | 88 | ##### cache_time (default value 3600 / 1h) 89 | 90 | Value of the cache for Doctrine Result Cache. Default value is 1 hour. 91 | When the doctrine cache is reset, the twig template will be automatically 92 | refresh (it's based on the Template's updatedAt value). 93 | 94 | -------------------------------------------------------------------------------- /Twig/TemplateLoader.php: -------------------------------------------------------------------------------- 1 | container = $container; 19 | } 20 | 21 | /** 22 | * Gets the source code of a template, given its name. 23 | * 24 | * @param string $name The name of the template to load 25 | * 26 | * @return string The template source code 27 | * 28 | * @throws Twig_Error_Loader When $name is not found 29 | */ 30 | public function getSource($name) 31 | { 32 | $name = $this->parse($name); 33 | $template = $this->getTemplate($name); 34 | $source = $this->getTemplateVariation($template); 35 | 36 | return $source; 37 | } 38 | 39 | /** 40 | * Gets the cache key to use for the cache for a given template name. 41 | * 42 | * @param string $name The name of the template to load 43 | * 44 | * @return string The cache key 45 | * 46 | * @throws Twig_Error_Loader When $name is not found 47 | */ 48 | public function getCacheKey($fullName) 49 | { 50 | $name = $this->parse($fullName); 51 | $template = $this->getTemplate($name); 52 | 53 | return 54 | __CLASS__ 55 | . '#' . $name 56 | . '#' . ($this->container->get('request')->get($this->container->getParameter('jm_ab.variation_parameter')) === null ? 'A' : 'B') 57 | // force reload even if Twig has autoReload to false 58 | . '#' . $template->getUpdatedAt()->getTimestamp() 59 | ; 60 | } 61 | 62 | /** 63 | * Returns true if the template is still fresh. 64 | * 65 | * @param string $name The template name 66 | * @param timestamp $time The last modification time of the cached template 67 | * 68 | * @return Boolean true if the template is fresh, false otherwise 69 | * 70 | * @throws Twig_Error_Loader When $name is not found 71 | */ 72 | public function isFresh($name, $time) 73 | { 74 | $name = $this->parse($name); 75 | $template = $this->getTemplate($name); 76 | 77 | return $template->getUpdatedAt()->getTimestamp() <= $time; 78 | } 79 | 80 | private function canHandle($name) 81 | { 82 | return 0 === strpos($name, 'template:'); 83 | } 84 | 85 | private function parse($name) 86 | { 87 | if (!preg_match('#^template:(.*)$#', $name, $m)) { 88 | throw new \Twig_Error_Loader(sprintf("Unable to find template %s", $name)); 89 | } 90 | 91 | return $m[1]; 92 | } 93 | 94 | private function getTemplate($name) 95 | { 96 | if (!$template = $this->container->get('jm_ab.template_manager')->getTemplate($name)) { 97 | throw new \Twig_Error_Loader(sprintf("Unable to find template %s", $name)); 98 | } 99 | 100 | return $template; 101 | } 102 | 103 | private function getTemplateVariation(Template $template) 104 | { 105 | $content = $template->getBody(); 106 | if (null !== $this->container->get('request')->get($this->container->getParameter('jm_ab.variation_parameter')) && $this->isValidBody($template->getVariationBody())) { 107 | $content = $template->getVariationBody(); 108 | } 109 | 110 | //replace {{GAexperimentScript}} by the GA script 111 | if ($template->getExperimentCode()) { 112 | $content = preg_replace('/{{( )?GAexperimentScript( )?}}/', $template->getAnalyticsScript(), $content); 113 | } 114 | 115 | return $content; 116 | } 117 | 118 | private function isValidBody($body) 119 | { 120 | return $body !== null && strlen(trim($body)) > 1; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Entity/Template.php: -------------------------------------------------------------------------------- 1 | id; 78 | } 79 | 80 | /** 81 | * Set name 82 | * 83 | * @param string $name 84 | * @return Page 85 | */ 86 | public function setName($name) 87 | { 88 | $this->name = $name; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Get name 95 | * 96 | * @return string 97 | */ 98 | public function getName() 99 | { 100 | return $this->name; 101 | } 102 | 103 | /** 104 | * Set body 105 | * 106 | * @param string $body 107 | * @return Page 108 | */ 109 | public function setBody($body) 110 | { 111 | $this->body = $body; 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Get body 118 | * 119 | * @return string 120 | */ 121 | public function getBody() 122 | { 123 | return $this->body; 124 | } 125 | 126 | /** 127 | * Get variationBody. 128 | * 129 | * @return variationBody. 130 | */ 131 | public function getVariationBody() 132 | { 133 | return $this->variationBody; 134 | } 135 | 136 | /** 137 | * Set variationBody. 138 | * 139 | * @param variationBody the value to set. 140 | */ 141 | public function setVariationBody($variationBody) 142 | { 143 | $this->variationBody = $variationBody; 144 | } 145 | 146 | /** 147 | * Get experimentCode. 148 | * 149 | * @return experimentCode. 150 | */ 151 | public function getExperimentCode() 152 | { 153 | return $this->experimentCode; 154 | } 155 | 156 | /** 157 | * Set experimentCode. 158 | * 159 | * @param experimentCode the value to set. 160 | */ 161 | public function setExperimentCode($experimentCode) 162 | { 163 | $this->experimentCode = $experimentCode; 164 | } 165 | 166 | /** 167 | * Set createdAt 168 | * 169 | * @param \DateTime $createdAt 170 | * @return DateTime 171 | */ 172 | public function setCreatedAt($createdAt) 173 | { 174 | $this->createdAt = $createdAt; 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * Get creationAt 181 | * 182 | * @return \DateTime 183 | */ 184 | public function getCreatedAt() 185 | { 186 | return $this->createdAt; 187 | } 188 | 189 | /** 190 | * Set updatedAt 191 | * 192 | * @param \DateTime $updatedAt 193 | * @return DateTime 194 | */ 195 | public function setUpdatedAt($updatedAt) 196 | { 197 | $this->updatedAt = $updatedAt; 198 | 199 | return $this; 200 | } 201 | 202 | /** 203 | * Get updatedAt 204 | * 205 | * @return \DateTime 206 | */ 207 | public function getUpdatedAt() 208 | { 209 | return $this->updatedAt; 210 | } 211 | 212 | public function __toString() 213 | { 214 | return $this->getName() ?: ''; 215 | } 216 | 217 | /** 218 | * @ORM\PrePersist 219 | */ 220 | public function beforePersist() 221 | { 222 | $this->setCreatedAt(new \DateTime()); 223 | $this->setUpdatedAt(new \DateTime()); 224 | } 225 | 226 | /** 227 | * @ORM\PreUpdate 228 | */ 229 | public function beforeUpdate() 230 | { 231 | $this->setUpdatedAt(new \DateTime()); 232 | } 233 | 234 | public function getAnalyticsScript() 235 | { 236 | if (null === $this->getExperimentCode() || strlen($this->getExperimentCode()) < 5) { 237 | return; 238 | } 239 | 240 | $template = << 242 | 254 | 255 | EOF; 256 | return str_replace('%EXPERIMENT_CODE%', $this->getExperimentCode(), $template); 257 | } 258 | 259 | protected function isValidBody($body) 260 | { 261 | return (null !== $body && strlen(trim($body)) > 0); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Tests/Twig/TemplateLoaderTest.php: -------------------------------------------------------------------------------- 1 | container = $this->getContainer(); 16 | $this->templateLoader = $this->getTemplateLoader($this->container); 17 | } 18 | 19 | /** 20 | * @expectedException Twig_Error_Loader 21 | */ 22 | public function shouldGenerateTwigErrorLoaderException() 23 | { 24 | $name = 'invalide-template-name'; 25 | $this->templateLoader->getSource($name); 26 | } 27 | 28 | public function testGetSource() 29 | { 30 | $name = 'template:valid'; 31 | $variationParameter = 'b'; 32 | 33 | $manager = $this->getTemplateManager(); 34 | $request = $this->getRequest(); 35 | 36 | $template = new Template; 37 | $template->setName('test') 38 | ->setBody('body1') 39 | ->setVariationBody('body2'); 40 | 41 | $request->expects($this->once()) 42 | ->method('get') 43 | ->with($variationParameter) 44 | ->will($this->returnValue(null)) 45 | ; 46 | 47 | $this->container->expects($this->once()) 48 | ->method('getParameter') 49 | ->with('jm_ab.variation_parameter') 50 | ->will($this->returnValue($variationParameter)) 51 | ; 52 | 53 | $this->container->expects($this->exactly(2)) 54 | ->method('get') 55 | ->with($this->logicalOr( 56 | $this->equalTo('jm_ab.template_manager'), 57 | $this->equalTo('request') 58 | )) 59 | ->will($this->returnCallback( 60 | function($param) use ($manager, $request) { 61 | if ('jm_ab.template_manager' == $param) { 62 | return $manager; 63 | } 64 | 65 | return $request; 66 | }) 67 | ) 68 | ; 69 | 70 | $manager->expects($this->once()) 71 | ->method('getTemplate') 72 | ->with('valid') 73 | ->will($this->returnValue($template)) 74 | ; 75 | 76 | $content = $this->templateLoader->getSource($name); 77 | $this->assertEquals($content, $template->getBody()); 78 | } 79 | 80 | public function testGetSourceWithVariationBody() 81 | { 82 | $name = 'template:valid'; 83 | $variationParameter = 'b'; 84 | 85 | $manager = $this->getTemplateManager(); 86 | $request = $this->getRequest(); 87 | 88 | $template = new Template; 89 | $template->setName('test') 90 | ->setBody('body1') 91 | ->setVariationBody('body2'); 92 | 93 | $request->expects($this->once()) 94 | ->method('get') 95 | ->with($variationParameter) 96 | ->will($this->returnValue(1)) 97 | ; 98 | 99 | $this->container->expects($this->once()) 100 | ->method('getParameter') 101 | ->with('jm_ab.variation_parameter') 102 | ->will($this->returnValue($variationParameter)) 103 | ; 104 | 105 | $this->container->expects($this->exactly(2)) 106 | ->method('get') 107 | ->with($this->logicalOr( 108 | $this->equalTo('jm_ab.template_manager'), 109 | $this->equalTo('request') 110 | )) 111 | ->will($this->returnCallback( 112 | function($param) use ($manager, $request) { 113 | if ('jm_ab.template_manager' == $param) { 114 | return $manager; 115 | } 116 | 117 | return $request; 118 | }) 119 | ) 120 | ; 121 | 122 | $manager->expects($this->once()) 123 | ->method('getTemplate') 124 | ->with('valid') 125 | ->will($this->returnValue($template)) 126 | ; 127 | 128 | $content = $this->templateLoader->getSource($name); 129 | $this->assertEquals($content, $template->getVariationBody()); 130 | } 131 | 132 | public function testGetCacheKey() 133 | { 134 | $name = 'test'; 135 | $variationParameter = 'b'; 136 | $manager = $this->getTemplateManager(); 137 | $now = new \DateTime(); 138 | 139 | $template = new Template; 140 | $template->setUpdatedAt($now); 141 | 142 | $request = $this->getRequest(); 143 | $request->expects($this->once()) 144 | ->method('get') 145 | ->with($variationParameter) 146 | ->will($this->returnValue(1)) 147 | ; 148 | 149 | $this->container->expects($this->once()) 150 | ->method('getParameter') 151 | ->with('jm_ab.variation_parameter') 152 | ->will($this->returnValue($variationParameter)) 153 | ; 154 | $this->container->expects($this->exactly(2)) 155 | ->method('get') 156 | ->with($this->logicalOr( 157 | $this->equalTo('jm_ab.template_manager'), 158 | $this->equalTo('request') 159 | )) 160 | ->will($this->returnCallback( 161 | function($param) use ($manager, $request) { 162 | if ('jm_ab.template_manager' == $param) { 163 | return $manager; 164 | } 165 | 166 | return $request; 167 | }) 168 | ) 169 | ; 170 | $manager->expects($this->once()) 171 | ->method('getTemplate') 172 | ->with($name) 173 | ->will($this->returnValue($template)) 174 | ; 175 | 176 | $loader = $this->getTemplateLoader($this->container); 177 | $key = $loader->getCacheKey('template:'. $name); 178 | $expectedKey = 'Jm\ABBundle\Twig\TemplateLoader#test#B#' . $template->getUpdatedAt()->getTimestamp(); 179 | 180 | $this->assertEquals($key, $expectedKey); 181 | } 182 | 183 | public function testIsFresh() 184 | { 185 | $name = 'test'; 186 | $now = new \DateTime(); 187 | $yesterday = new \DateTime(); //template update cached version is from yesterday 188 | $yesterday->modify('- 1 day'); 189 | 190 | $template = new Template(); 191 | $template->setUpdatedAt($now); //we've just modified the template 192 | 193 | $manager = $this->getTemplateManager(); 194 | $manager->expects($this->once()) 195 | ->method('getTemplate') 196 | ->with($name) 197 | ->will($this->returnValue($template)) 198 | ; 199 | 200 | $this->container->expects($this->once()) 201 | ->method('get') 202 | ->with('jm_ab.template_manager') 203 | ->will($this->returnValue($manager)) 204 | ; 205 | 206 | $loader = $this->getTemplateLoader($this->container); 207 | $isFresh = $loader->isFresh('template:' . $name, $yesterday->getTimestamp()); 208 | $this->assertFalse($isFresh); 209 | } 210 | 211 | private function getTemplateLoader($container) 212 | { 213 | return new TemplateLoader($container); 214 | } 215 | 216 | private function getTemplateManager() 217 | { 218 | return $this 219 | ->getMockBuilder('Jm\ABBundle\Entity\TemplateManager') 220 | ->disableOriginalConstructor() 221 | ->getMock() 222 | ; 223 | } 224 | 225 | private function getContainer() 226 | { 227 | return $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); 228 | } 229 | 230 | public function getRequest() 231 | { 232 | return $this->getMock('Symfony\Component\HttpFoundation\Request'); 233 | } 234 | } 235 | --------------------------------------------------------------------------------