├── .gitignore ├── .travis.yml ├── DependencyInjection ├── Configuration.php └── DunglasActionExtension.php ├── DunglasActionBundle.php ├── LICENSE ├── README.md ├── Tests ├── AutomaticRegistrationTest.php ├── DependencyInjection │ └── ConfigurationTest.php ├── Fixtures │ ├── AnonymousAction │ │ └── AnAnonymousAction.php │ ├── IsolatedAction │ │ └── AnIsolatedAction.php │ ├── NotScannedBundle │ │ ├── Controller │ │ │ └── MyController.php │ │ └── NotScannedBundle.php │ ├── TestBundle │ │ ├── Action │ │ │ ├── AbstractAction.php │ │ │ ├── DummyAction.php │ │ │ ├── MultiController.php │ │ │ ├── OverrideAction.php │ │ │ ├── RouteAnnotationAction.php │ │ │ ├── SensioFrameworkExtraAction.php │ │ │ └── SetterAction.php │ │ ├── Command │ │ │ ├── Bar.php │ │ │ └── FooCommand.php │ │ ├── Controller │ │ │ └── HelloController.php │ │ ├── DummyService.php │ │ ├── EventSubscriber │ │ │ └── MySubscriber.php │ │ ├── TestBundle.php │ │ └── Twig │ │ │ └── DummyExtension.php │ └── TestKernel.php ├── FunctionalTest.php └── bootstrap.php ├── UPGRADE.md ├── appveyor.yml ├── composer.json └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | /phpunit.xml 4 | /Tests/Fixtures/cache 5 | /Tests/Fixtures/log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | cache: 4 | directories: 5 | - $HOME/.composer/cache 6 | 7 | php: 8 | - '5.5' 9 | - '5.6' 10 | - '7.0' 11 | - '7.1' 12 | - nightly 13 | - hhvm 14 | 15 | matrix: 16 | fast_finish: true 17 | include: 18 | - php: '7.1' 19 | env: 'COMPOSER_FLAGS=--prefer-lowest' 20 | - php: '7.1' 21 | env: 'SYMFONY_VERSION=^3.3@dev' 22 | allow_failures: 23 | - php: nightly 24 | - env: 'SYMFONY_VERSION=^3.3@dev' 25 | 26 | before_install: 27 | - phpenv config-rm xdebug.ini || echo "xdebug not available" 28 | - if [ "$TRAVIS_PHP_VERSION" = "nightly" ]; then COMPOSER_FLAGS="$COMPOSER_FLAGS --ignore-platform-reqs"; fi; 29 | - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/symfony:${SYMFONY_VERSION}; fi 30 | 31 | install: 32 | - composer update $COMPOSER_FLAGS --no-interaction --prefer-dist --no-progress --no-suggest --ansi 33 | 34 | script: 35 | - vendor/bin/phpunit 36 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\DependencyInjection; 11 | 12 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 13 | use Symfony\Component\Config\Definition\ConfigurationInterface; 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 16 | 17 | /** 18 | * {@inheritdoc} 19 | * 20 | * @author Kévin Dunglas 21 | */ 22 | class Configuration implements ConfigurationInterface 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function getConfigTreeBuilder() 28 | { 29 | $treeBuilder = new TreeBuilder(); 30 | $treeBuilder->root('dunglas_action') 31 | ->fixXmlConfig('directory', 'directories') 32 | ->children() 33 | ->arrayNode('methods') 34 | ->info('The list of methods to autowire.') 35 | ->prototype('scalar')->end() 36 | ->defaultValue(['__construct', 'get*', 'set*']) 37 | ->end() 38 | ->arrayNode('directories') 39 | ->info('List of directories relative to the kernel root directory containing classes.') 40 | ->prototype('scalar')->end() 41 | ->defaultValue([ 42 | '../src/*Bundle/Action', 43 | '../src/*Bundle/Command', 44 | '../src/*Bundle/Controller', 45 | '../src/*Bundle/EventSubscriber', 46 | '../src/*Bundle/Twig', 47 | ]) 48 | ->end() 49 | ->arrayNode('tags') 50 | ->info('List of tags to add when implementing the corresponding class.') 51 | ->useAttributeAsKey('class') 52 | ->prototype('array') 53 | // Converts 'console.command' to ['console.command'] 54 | ->beforeNormalization()->ifString()->then(function ($v) { 55 | return [$v]; 56 | })->end() 57 | ->prototype('array') 58 | // Converts 'console.command' to ['console.command', []] 59 | ->beforeNormalization()->ifString()->then(function ($v) { 60 | return [$v, []]; 61 | })->end() 62 | ->validate() 63 | ->ifTrue(function ($v) { 64 | return count($v) !== 2 || !is_string($v[0]) || !is_array($v[1]); 65 | }) 66 | ->thenInvalid('Invalid tag format. They must be as following: [\'my_tag.name\', [\'attribute\' => \'value\']]') 67 | ->end() 68 | ->prototype('variable')->end() 69 | ->end() 70 | ->end() 71 | ->defaultValue([ 72 | Command::class => [['console.command', []]], 73 | EventSubscriberInterface::class => [['kernel.event_subscriber', []]], 74 | \Twig_ExtensionInterface::class => [['twig.extension', []]], 75 | ]) 76 | ->end() 77 | ->end() 78 | ->end(); 79 | 80 | return $treeBuilder; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DependencyInjection/DunglasActionExtension.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\DependencyInjection; 11 | 12 | use Symfony\Component\Config\Resource\DirectoryResource; 13 | use Symfony\Component\DependencyInjection\ContainerAwareInterface; 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | use Symfony\Component\DependencyInjection\Reference; 16 | use Symfony\Component\Finder\Finder; 17 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 18 | 19 | /** 20 | * {@inheritdoc} 21 | * 22 | * @author Kévin Dunglas 23 | */ 24 | class DunglasActionExtension extends Extension 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function load(array $configs, ContainerBuilder $container) 30 | { 31 | $configuration = new Configuration(); 32 | $config = $this->processConfiguration($configuration, $configs); 33 | $kernelRootDir = $container->getParameter('kernel.root_dir'); 34 | 35 | $directoryList = []; 36 | foreach ($config['directories'] as $pattern) { 37 | list($classes, $directories) = $this->getClasses($this->getDirectory($kernelRootDir, $pattern)); 38 | $directoryList = array_merge($directoryList, $directories); 39 | 40 | foreach ($classes as $class) { 41 | $this->registerClass($container, $class, $config['tags'], $config['methods']); 42 | } 43 | } 44 | 45 | $directories = []; 46 | foreach ($directoryList as $directory => $v) { 47 | $directory = realpath($directory); 48 | $container->addResource(new DirectoryResource($directory, '/\.php$/')); 49 | $directories[$directory] = true; 50 | } 51 | 52 | $container->setParameter('dunglas_action.directories', $directories); 53 | } 54 | 55 | /** 56 | * @param string $kernelRootDir 57 | * @param string $pattern 58 | * 59 | * @return string 60 | */ 61 | private function getDirectory($kernelRootDir, $pattern) 62 | { 63 | $firstCharacter = substr($pattern, 0, 1); 64 | if ('/' === $firstCharacter || DIRECTORY_SEPARATOR === $firstCharacter) { 65 | return $pattern; 66 | } 67 | 68 | return $kernelRootDir.DIRECTORY_SEPARATOR.$pattern; 69 | } 70 | 71 | /** 72 | * Gets the list of class names in the given directory. 73 | * 74 | * @param string $directory 75 | * 76 | * @return array 77 | */ 78 | private function getClasses($directory) 79 | { 80 | $classes = []; 81 | $directoryList = []; 82 | $includedFiles = []; 83 | 84 | $finder = new Finder(); 85 | try { 86 | $finder->in($directory)->files()->name('*.php'); 87 | } catch (\InvalidArgumentException $e) { 88 | return [[], []]; 89 | } 90 | 91 | foreach ($finder as $file) { 92 | $directoryList[$file->getPath()] = true; 93 | $sourceFile = $file->getRealpath(); 94 | if (!preg_match('(^phar:)i', $sourceFile)) { 95 | $sourceFile = realpath($sourceFile); 96 | } 97 | 98 | require_once $sourceFile; 99 | $includedFiles[$sourceFile] = true; 100 | } 101 | 102 | $declared = get_declared_classes(); 103 | foreach ($declared as $className) { 104 | $reflectionClass = new \ReflectionClass($className); 105 | $sourceFile = $reflectionClass->getFileName(); 106 | 107 | if ($reflectionClass->isAbstract()) { 108 | continue; 109 | } 110 | 111 | if (method_exists($reflectionClass, 'isAnonymous') && $reflectionClass->isAnonymous()) { 112 | continue; 113 | } 114 | 115 | if (isset($includedFiles[$sourceFile])) { 116 | $classes[$className] = true; 117 | } 118 | } 119 | 120 | return [array_keys($classes), $directoryList]; 121 | } 122 | 123 | /** 124 | * Registers an action in the container. 125 | * 126 | * @param ContainerBuilder $container 127 | * @param string $className 128 | * @param array $tags 129 | * @param string[] $methods 130 | */ 131 | private function registerClass(ContainerBuilder $container, $className, array $tags, array $methods) 132 | { 133 | if ($container->has($className)) { 134 | return; 135 | } 136 | 137 | $definition = $container->register($className, $className); 138 | if (method_exists($definition, 'setAutowiredMethods')) { 139 | $definition->setAutowiredMethods($methods); 140 | } else { 141 | $definition->setAutowired(true); 142 | } 143 | 144 | // Inject the container if applicable 145 | if (is_a($className, ContainerAwareInterface::class, true)) { 146 | $definition->addMethodCall('setContainer', [new Reference('service_container')]); 147 | } 148 | 149 | foreach ($tags as $tagClassName => $classTags) { 150 | if (!is_a($className, $tagClassName, true)) { 151 | continue; 152 | } 153 | 154 | foreach ($classTags as $classTag) { 155 | $definition->addTag($classTag[0], $classTag[1]); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /DunglasActionBundle.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle; 11 | 12 | use Symfony\Component\HttpKernel\Bundle\Bundle; 13 | 14 | /** 15 | * @author Kévin Dunglas 16 | */ 17 | final class DunglasActionBundle extends Bundle 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Kévin Dunglas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DunglasActionBundle: Symfony controllers, redesigned 2 | 3 | [![Build Status](https://travis-ci.org/dunglas/DunglasActionBundle.svg?branch=master)](https://travis-ci.org/dunglas/DunglasActionBundle) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/jpjsasx59syknghe?svg=true)](https://ci.appveyor.com/project/dunglas/dunglasactionbundle) 5 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/7022bce4-9d67-4ade-9b19-cf7e417c0a80/mini.png)](https://insight.sensiolabs.com/projects/7022bce4-9d67-4ade-9b19-cf7e417c0a80) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/dunglas/DunglasActionBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/dunglas/DunglasActionBundle/?branch=master) 7 | [![StyleCI](https://styleci.io/repos/50048652/shield)](https://styleci.io/repos/50048652) 8 | 9 | This bundle is a replacement for [the controller system](https://symfony.com/doc/current/book/controller.html) of the [Symfony framework](https://symfony.com) and for its [command system](https://symfony.com/doc/current/cookbook/console/console_command.html). 10 | 11 | It is as convenient as the original but doesn't suffer from its drawbacks: 12 | 13 | * Action and console classes are automatically **registered as services** by the bundle 14 | * Their dependencies are **explicitly injected** in the constructor (no more ugly access to the service container) using the [autowiring feature of the Dependency Injection Component](https://dunglas.fr/2015/10/new-in-symfony-2-83-0-services-autowiring/) 15 | * Only one action per class thanks to the [`__invoke()` method](http://php.net/manual/en/language.oop5.magic.php#object.invoke) 16 | (but you're still free to create classes with more than 1 action if you want to) 17 | * 100% compatible with common libraries and bundles including [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/) 18 | annotations 19 | 20 | DunglasActionBundle allows to create **reusable**, **framework agnostic** (especially when used with [the PSR-7 bridge](https://dunglas.fr/2015/06/using-psr-7-in-symfony/)) 21 | and **easy to unit test** classes. 22 | 23 | See https://github.com/symfony/symfony/pull/16863#issuecomment-162221353 for the history behind this bundle. 24 | 25 | ## Note for Symfony >=3.3 users 26 | 27 | If you use Symfony at version 3.3 or superior, you do not need to use this bundle as all the features were ported 28 | in Symfony. You can learn more about it in the [Symfony blog](http://symfony.com/blog/the-new-symfony-3-3-service-configuration-changes-explained) 29 | or in the [Symfony documentation](http://symfony.com/doc/current/service_container/3.3-di-changes.html). 30 | 31 | ## Installation 32 | 33 | Use [Composer](https://getcomposer.org/) to install this bundle: 34 | 35 | composer require dunglas/action-bundle 36 | 37 | Add the bundle in your application kernel: 38 | 39 | ```php 40 | // app/AppKernel.php 41 | 42 | public function registerBundles() 43 | { 44 | return [ 45 | // ... 46 | new Dunglas\ActionBundle\DunglasActionBundle(), 47 | // ... 48 | ]; 49 | } 50 | ``` 51 | 52 | Optional: to use the `@Route` annotation add the following lines in `app/config/routing.yml`: 53 | 54 | ```yaml 55 | app: 56 | resource: '@AppBundle/Action/' # Use @AppBundle/Controller/ if you prefer 57 | type: 'annotation' 58 | ``` 59 | 60 | If you don't want to use annotations but prefer raw YAML, use the following syntax: 61 | 62 | ```yaml 63 | foo: 64 | path: /foo/{bar} 65 | defaults: { _controller: 'AppBundle\Action\Homepage' } # this is the name of the autoregistered service corresponding to this action 66 | ``` 67 | 68 | ## Usage 69 | 70 | 1. Create [an invokable class](http://www.lornajane.net/posts/2012/phps-magic-__invoke-method-and-the-callable-typehint) 71 | in the `Action\` namespace of your bundle: 72 | 73 | ```php 74 | 75 | // src/AppBundle/Action/MyAction.php 76 | 77 | namespace AppBundle\Action; 78 | 79 | use Symfony\Component\Routing\Annotation\Route; 80 | use Symfony\Component\Routing\RouterInterface; 81 | use Symfony\Component\HttpFoundation\RedirectResponse; 82 | use Symfony\Component\HttpFoundation\Request; 83 | use Symfony\Component\HttpFoundation\Response; 84 | 85 | class Homepage 86 | { 87 | private $router; 88 | private $twig; 89 | 90 | /** 91 | * The action is automatically registered as a service and dependencies are autowired. 92 | * Typehint any service you need, it will be automatically injected. 93 | */ 94 | public function __construct(RouterInterface $router, \Twig_Environment $twig) 95 | { 96 | $this->router = $router; 97 | $this->twig = $twig; 98 | } 99 | 100 | /** 101 | * @Route("/myaction", name="my_action") 102 | * 103 | * Using annotations is not mandatory, XML and YAML configuration files can be used instead. 104 | * If you want to decouple your actions from the framework, don't use annotations. 105 | */ 106 | public function __invoke(Request $request) 107 | { 108 | if (!$request->isMethod('GET')) { 109 | // Redirect to the current URL using the the GET method if it's not the current one 110 | return new RedirectResponse($this->router->generateUrl('my_action'), 301); 111 | } 112 | 113 | return new Response($this->twig->render('mytemplate.html.twig')); 114 | } 115 | } 116 | ``` 117 | 118 | Alternatively, you can create a typical Symfony controller class with several `*Action` methods in the `Controller` directory 119 | of your bundle, it will be autowired the same way. 120 | 121 | **There is no step 2! You're already done.** 122 | 123 | All classes inside `Action/` and `Controller/` directories of your project bundles are automatically registered as services. 124 | By convention, the service name is the Fully Qualified Name of the class. 125 | 126 | For instance, the class in the example is automatically registered with the name `AppBundle\Action\Homepage`. 127 | 128 | There are other classes/tags supported: 129 | 130 | | Class Name | Tag automatically added | Directory 131 | | ------------------------ | ----------------------- | --------- 132 | | Command | console.command | Command 133 | | EventSubscriberInterface | kernel.event_subscriber | EventSubscriber 134 | | Twig_ExtensionInterface | twig.extension | Twig 135 | 136 | Thanks to the [autowiring feature](http://symfony.com/blog/new-in-symfony-2-8-service-auto-wiring) of the Dependency Injection 137 | Component, you can just typehint dependencies you need in the constructor, they will be automatically initialized and injected. 138 | 139 | Service definition can easily be customized by explicitly defining a service named according to the same convention: 140 | 141 | ```yaml 142 | # app/config/services.yml 143 | 144 | services: 145 | # This is a custom service definition 146 | 'AppBundle\Action\MyAction': 147 | arguments: [ '@router', '@twig' ] 148 | 149 | 'AppBundle\Command\MyCommand': 150 | arguments: [ '@router', '@twig' ] 151 | tags: 152 | - { name: console.command } 153 | 154 | # With Symfony < 3.3 155 | 'AppBundle\EventSubscriber\MySubscriber': 156 | class: 'AppBundle\EventSubscriber\MySubscriber' 157 | tags: 158 | - { name: kernel.event_subscriber } 159 | ``` 160 | 161 | This bundle also hooks into the Routing Component (if it is available): when the `@Route` annotation is used as in the example, 162 | the route is automatically registered: the bundle guesses the service to map with the path specified in the annotation. 163 | 164 | [Dive into the TestBundle](Tests/Fixtures/TestBundle) to discover more examples such as using custom services with ease 165 | (no configuration at all) or classes containing several actions. 166 | 167 | ## Using the Symfony Micro Framework 168 | 169 | You might be interested to see how this bundle can be used together with [the Symfony "Micro" framework](https://symfony.com/doc/current/cookbook/configuration/micro-kernel-trait.html). 170 | 171 | Here we go: 172 | 173 | ```php 174 | // MyMicroKernel.php 175 | 176 | use AppBundle\Action\Homepage; 177 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 178 | use Symfony\Component\Config\Loader\LoaderInterface; 179 | use Symfony\Component\DependencyInjection\ContainerBuilder; 180 | use Symfony\Component\HttpKernel\Kernel; 181 | use Symfony\Component\Routing\RouteCollectionBuilder; 182 | 183 | final class MyMicroKernel extends Kernel 184 | { 185 | use MicroKernelTrait; 186 | 187 | public function registerBundles() 188 | { 189 | return [ 190 | new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 191 | new Dunglas\ActionBundle\DunglasActionBundle(), 192 | new AppBundle\AppBundle(), 193 | ]; 194 | } 195 | 196 | protected function configureRoutes(RouteCollectionBuilder $routes) 197 | { 198 | // Specify explicitly the controller 199 | $routes->add('/', Homepage::class, 'my_route'); 200 | // Alternatively, use @Route annotations 201 | // $routes->import('@AppBundle/Action/', '/', 'annotation'); 202 | } 203 | 204 | protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) 205 | { 206 | $c->loadFromExtension('framework', ['secret' => 'MySecretKey']); 207 | } 208 | } 209 | ``` 210 | 211 | Amazing isn't it? 212 | 213 | Want to see a more advanced example? [Checkout our test micro kernel](Tests/Fixtures/TestKernel.php). 214 | 215 | ## Configuration 216 | 217 | ```yaml 218 | # app/config/config.yml 219 | 220 | dunglas_action: 221 | directories: # List of directories relative to the kernel root directory containing classes to auto-register. 222 | - '../src/*Bundle/{Controller,Action,Command,EventSubscriber}' 223 | # This one is not registered by default 224 | - '../src/*Bundle/My/Uncommon/Directory' 225 | tags: 226 | 'Symfony\Component\Console\Command\Command': console.command 227 | 'Symfony\Component\EventDispatcher\EventSubscriberInterface': kernel.event_subscriber 228 | 'My\Custom\Interface\To\Auto\Tag': 229 | - 'my_custom.tag' 230 | - [ 'my_custom.tag_with_attributes', { attribute: 'value' } ] 231 | ``` 232 | 233 | ## Credits 234 | 235 | This bundle is brought to you by [Kévin Dunglas](https://dunglas.fr) and [awesome contributors](https://github.com/dunglas/DunglasActionBundle/graphs/contributors). 236 | Sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop). 237 | -------------------------------------------------------------------------------- /Tests/AutomaticRegistrationTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests; 11 | 12 | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 13 | 14 | /** 15 | * @author Guilhem N 16 | */ 17 | class AutomaticRegistrationTest extends KernelTestCase 18 | { 19 | public function testCommandRegistration() 20 | { 21 | static::bootKernel(); 22 | $container = static::$kernel->getContainer(); 23 | 24 | $commandId = 'dunglas\actionbundle\tests\fixtures\testbundle\command\foocommand'; 25 | $this->assertTrue($container->has($commandId)); 26 | $this->assertContains($commandId, $container->getParameter('console.command.ids')); 27 | 28 | $commandId = 'dunglas\actionbundle\tests\fixtures\testbundle\command\bar'; 29 | $this->assertTrue($container->has($commandId)); 30 | $this->assertNotContains($commandId, $container->getParameter('console.command.ids')); 31 | } 32 | 33 | public function testEventSubscriberRegistration() 34 | { 35 | static::bootKernel(); 36 | $container = static::$kernel->getContainer(); 37 | 38 | $listenerId = 'dunglas\actionbundle\tests\fixtures\testbundle\eventsubscriber\mysubscriber'; 39 | $this->assertTrue($container->has($listenerId)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\DependencyInjection; 11 | 12 | use Dunglas\ActionBundle\DependencyInjection\Configuration; 13 | use Symfony\Component\Config\Definition\Processor; 14 | 15 | /** 16 | * @author Guilhem N 17 | */ 18 | class ConfigurationTest extends \PHPUnit_Framework_TestCase 19 | { 20 | public function testTagsNormalization() 21 | { 22 | $processor = new Processor(); 23 | 24 | // 'console.command' conversion 25 | $config = $processor->processConfiguration(new Configuration(true), [[ 26 | 'tags' => [__CLASS__ => 'console.command'], 27 | ]]); 28 | $this->assertEquals( 29 | [__CLASS__ => [['console.command', []]]], 30 | $config['tags'] 31 | ); 32 | 33 | // ['console.command'] conversion 34 | $config = $processor->processConfiguration(new Configuration(true), [[ 35 | 'tags' => [__CLASS__ => [['console.command', []], 'kernel.event_subscriber']], 36 | ]]); 37 | $this->assertEquals( 38 | [__CLASS__ => [ 39 | ['console.command', []], 40 | ['kernel.event_subscriber', []], 41 | ]], 42 | $config['tags'] 43 | ); 44 | } 45 | 46 | public function testValidTag() 47 | { 48 | $processor = new Processor(); 49 | 50 | $tags = [__CLASS__ => [['console.command', []]]]; 51 | $config = $processor->processConfiguration(new Configuration(true), [['tags' => $tags]]); 52 | $this->assertEquals($tags, $config['tags']); 53 | } 54 | 55 | /** 56 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 57 | * @expectedExceptionMessage Invalid tag format. They must be as following: ['my_tag.name', ['attribute' => 'value']] 58 | */ 59 | public function testInvalidTag() 60 | { 61 | $processor = new Processor(); 62 | 63 | $processor->processConfiguration(new Configuration(true), [[ 64 | 'tags' => [__CLASS__ => [['console.command', 'invalid']]], 65 | ]]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/Fixtures/AnonymousAction/AnAnonymousAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\AnonymousAction; 11 | 12 | class AnAnonymousAction 13 | { 14 | protected $anonymous; 15 | 16 | public function __construct() 17 | { 18 | $dummyService = null; 19 | $this->anonymous = new class($dummyService) { 20 | private $dummyService; 21 | 22 | public function __construct($dummyService) 23 | { 24 | $this->dummyService = $dummyService; 25 | } 26 | }; 27 | } 28 | 29 | public function __invoke() 30 | { 31 | return 'Ho hi'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Fixtures/IsolatedAction/AnIsolatedAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\IsolatedAction; 11 | 12 | use Symfony\Component\HttpFoundation\Response; 13 | 14 | /** 15 | * @author Kévin Dunglas 16 | */ 17 | class AnIsolatedAction 18 | { 19 | public function __invoke() 20 | { 21 | return new Response('Isolated.'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Fixtures/NotScannedBundle/Controller/MyController.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\NotScannedBundle\Controller; 11 | 12 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 13 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 14 | use Symfony\Component\HttpFoundation\Response; 15 | 16 | /** 17 | * @author Kévin Dunglas 18 | */ 19 | class MyController extends Controller 20 | { 21 | /** 22 | * @Route("/traditional") 23 | */ 24 | public function testAction() 25 | { 26 | return new Response('traditional'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/Fixtures/NotScannedBundle/NotScannedBundle.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\NotScannedBundle; 11 | 12 | use Symfony\Component\HttpKernel\Bundle\Bundle; 13 | 14 | /** 15 | * @author Kévin Dunglas 16 | */ 17 | final class NotScannedBundle extends Bundle 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/AbstractAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | abstract class AbstractAction 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/DummyAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\DummyService; 13 | use Symfony\Component\HttpFoundation\Response; 14 | 15 | /** 16 | * @author Kévin Dunglas 17 | */ 18 | final class DummyAction 19 | { 20 | private $dummy; 21 | 22 | public function __construct(DummyService $dummy) 23 | { 24 | $this->dummy = $dummy; 25 | } 26 | 27 | public function __invoke() 28 | { 29 | $this->dummy->doSomething(); 30 | 31 | return new Response('Here we are!'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/MultiController.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Symfony\Component\HttpFoundation\Response; 13 | use Symfony\Component\Routing\Annotation\Route; 14 | 15 | /** 16 | * @author Kévin Dunglas 17 | */ 18 | class MultiController 19 | { 20 | /** 21 | * @Route("/first") 22 | */ 23 | public function firstAction() 24 | { 25 | return new Response('first'); 26 | } 27 | 28 | /** 29 | * @Route("/second") 30 | */ 31 | public function secondAction() 32 | { 33 | return new Response('second'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/OverrideAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\DummyService; 13 | use Symfony\Component\HttpFoundation\Response; 14 | use Symfony\Component\Routing\Annotation\Route; 15 | 16 | class OverrideAction 17 | { 18 | public function __construct(DummyService $dummyService = null) 19 | { 20 | if (null !== $dummyService) { 21 | throw new \InvalidArgumentException('$dummyService must be null!'); 22 | } 23 | } 24 | 25 | /** 26 | * @Route("/override") 27 | */ 28 | public function __invoke() 29 | { 30 | return new Response('Override'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/RouteAnnotationAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Symfony\Component\HttpFoundation\Response; 13 | use Symfony\Component\Routing\Annotation\Route; 14 | 15 | /** 16 | * @author Kévin Dunglas 17 | */ 18 | class RouteAnnotationAction 19 | { 20 | /** 21 | * @Route("/annotation") 22 | */ 23 | public function __invoke() 24 | { 25 | return new Response('Hey, ho, let\'s go!'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/SensioFrameworkExtraAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 13 | use Symfony\Component\HttpFoundation\Response; 14 | 15 | /** 16 | * @Route(service="Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action\SensioFrameworkExtraAction") 17 | * 18 | * @author Kévin Dunglas 19 | */ 20 | class SensioFrameworkExtraAction 21 | { 22 | /** 23 | * @Route("/extra") 24 | */ 25 | public function __invoke() 26 | { 27 | return new Response('How are you?'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Action/SetterAction.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action; 11 | 12 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\DummyService; 13 | use Symfony\Component\HttpFoundation\Response; 14 | 15 | /** 16 | * @author Kévin Dunglas 17 | */ 18 | class SetterAction 19 | { 20 | /** 21 | * @var DummyService 22 | */ 23 | private $dummyService; 24 | 25 | public function setDummyService(DummyService $dummyService) 26 | { 27 | $this->dummyService = $dummyService; 28 | } 29 | 30 | public function __invoke() 31 | { 32 | $this->dummyService->doSomething(); 33 | 34 | return new Response('setter'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Command/Bar.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Command; 11 | 12 | class Bar 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Command/FooCommand.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Command; 11 | 12 | use Symfony\Component\Console\Command\Command; 13 | 14 | class FooCommand extends Command 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Controller/HelloController.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Controller; 11 | 12 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 13 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 14 | use Symfony\Component\HttpFoundation\Response; 15 | 16 | /** 17 | * This legacy controller is here to ensure that old-school and bleeding edge 18 | * can be used on the same project. 19 | * 20 | * @author Kévin Dunglas 21 | */ 22 | class HelloController extends Controller 23 | { 24 | /** 25 | * @Route("/legacy") 26 | */ 27 | public function testAction() 28 | { 29 | return new Response($this->get('router')->generate('isolated')); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/DummyService.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle; 11 | 12 | /** 13 | * @author Kévin Dunglas 14 | */ 15 | class DummyService 16 | { 17 | public function doSomething() 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/EventSubscriber/MySubscriber.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\EventSubscriber; 11 | 12 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 13 | 14 | /** 15 | * @author Guilhem N 16 | */ 17 | class MySubscriber implements EventSubscriberInterface 18 | { 19 | public static function getSubscribedEvents() 20 | { 21 | return []; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/TestBundle.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle; 11 | 12 | use Symfony\Component\HttpKernel\Bundle\Bundle; 13 | 14 | /** 15 | * @author Kévin Dunglas 16 | */ 17 | final class TestBundle extends Bundle 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestBundle/Twig/DummyExtension.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Twig; 11 | 12 | /** 13 | * @author Kévin Dunglas 14 | */ 15 | class DummyExtension extends \Twig_Extension 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /Tests/Fixtures/TestKernel.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | use Dunglas\ActionBundle\DunglasActionBundle; 11 | use Dunglas\ActionBundle\Tests\Fixtures\IsolatedAction\AnIsolatedAction; 12 | use Dunglas\ActionBundle\Tests\Fixtures\NotScannedBundle\NotScannedBundle; 13 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action\DummyAction; 14 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action\OverrideAction; 15 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action\SetterAction; 16 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\TestBundle; 17 | use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; 18 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 19 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 20 | use Symfony\Component\Config\Loader\LoaderInterface; 21 | use Symfony\Component\DependencyInjection\ContainerBuilder; 22 | use Symfony\Component\HttpKernel\Kernel; 23 | use Symfony\Component\Routing\RouteCollectionBuilder; 24 | 25 | /** 26 | * @author Kévin Dunglas 27 | */ 28 | final class TestKernel extends Kernel 29 | { 30 | use MicroKernelTrait; 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function registerBundles() 36 | { 37 | return [ 38 | new FrameworkBundle(), 39 | new DunglasActionBundle(), 40 | new SensioFrameworkExtraBundle(), 41 | new TestBundle(), 42 | new NotScannedBundle(), 43 | ]; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | protected function configureRoutes(RouteCollectionBuilder $routes) 50 | { 51 | // Specify explicitly the controller 52 | $routes->add('/', DummyAction::class, 'dummy'); 53 | $routes->add('/isolated', AnIsolatedAction::class, 'isolated'); 54 | $routes->add('/setter', SetterAction::class, 'setter'); 55 | 56 | // Use the @Route annotation 57 | $routes->import('@TestBundle/Action/', '/', 'annotation'); 58 | 59 | // Cohabitation between old school controllers and actions 60 | $routes->import('@TestBundle/Controller/', '/', 'annotation'); 61 | $routes->import('@NotScannedBundle/Controller/', '/', 'annotation'); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) 68 | { 69 | $c->loadFromExtension('framework', [ 70 | 'secret' => 'MySecretKey', 71 | 'test' => null, 72 | ]); 73 | 74 | $c->loadFromExtension('dunglas_action', [ 75 | 'directories' => [ 76 | '%kernel.root_dir%/TestBundle/Controller', 77 | 'IsolatedAction', 78 | 'NotExistingDirectory', 79 | '*Bundle/Action', 80 | '*Bundle/Command', 81 | '*Bundle/EventSubscriber', 82 | '*Bundle/Twig', 83 | ], 84 | ]); 85 | 86 | if (method_exists(ReflectionClass::class, 'isAnonymous')) { 87 | $c->loadFromExtension('dunglas_action', [ 88 | 'directories' => [ 89 | 'AnonymousAction', 90 | ], 91 | ]); 92 | } 93 | 94 | $c->register(OverrideAction::class, OverrideAction::class); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Tests/FunctionalTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | namespace Dunglas\ActionBundle\Tests; 11 | 12 | use Dunglas\ActionBundle\Tests\Fixtures\AnonymousAction\AnAnonymousAction; 13 | use Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Twig\DummyExtension; 14 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 15 | use Symfony\Component\DependencyInjection\Definition; 16 | 17 | /** 18 | * @author Kévin Dunglas 19 | */ 20 | class FunctionalTest extends WebTestCase 21 | { 22 | public function testDummyAction() 23 | { 24 | $client = static::createClient(); 25 | 26 | static::$kernel->getContainer()->has('Dunglas\ActionBundle\Tests\Fixtures\TestBundle\Action\DummyAction'); 27 | 28 | $crawler = $client->request('GET', '/'); 29 | $this->assertSame('Here we are!', $crawler->text()); 30 | } 31 | 32 | public function testSensioFrameworkExtraAction() 33 | { 34 | $client = static::createClient(); 35 | 36 | $crawler = $client->request('GET', '/extra'); 37 | $this->assertSame('How are you?', $crawler->text()); 38 | } 39 | 40 | public function testRouteAnnotationAction() 41 | { 42 | $client = static::createClient(); 43 | 44 | $crawler = $client->request('GET', '/annotation'); 45 | $this->assertSame('Hey, ho, let\'s go!', $crawler->text()); 46 | } 47 | 48 | public function testOverrideAction() 49 | { 50 | $client = static::createClient(); 51 | 52 | $crawler = $client->request('GET', '/override'); 53 | $this->assertSame('Override', $crawler->text()); 54 | } 55 | 56 | public function testMultiController() 57 | { 58 | $client = static::createClient(); 59 | 60 | $crawler = $client->request('GET', '/first'); 61 | $this->assertSame('first', $crawler->text()); 62 | 63 | $crawler = $client->request('GET', '/second'); 64 | $this->assertSame('second', $crawler->text()); 65 | } 66 | 67 | public function testLegacy() 68 | { 69 | $client = static::createClient(); 70 | 71 | $crawler = $client->request('GET', '/legacy'); 72 | $this->assertSame('/isolated', $crawler->text()); 73 | } 74 | 75 | public function testIsolated() 76 | { 77 | $client = static::createClient(); 78 | 79 | $crawler = $client->request('GET', '/isolated'); 80 | $this->assertSame('Isolated.', $crawler->text()); 81 | } 82 | 83 | public function testAbstractClassNotRegistered() 84 | { 85 | static::bootKernel(); 86 | $this->assertFalse(static::$kernel->getContainer()->has('dunglas\actionBundle\tests\fixtures\testbundle\action\abstractaction')); 87 | } 88 | 89 | /** 90 | * @requires PHP 7.0 91 | */ 92 | public function testAnonymousClassNotRegistered() 93 | { 94 | static::bootKernel(); 95 | $this->assertTrue(static::$kernel->getContainer()->has('dunglas\actionBundle\tests\fixtures\anonymousaction\ananonymousaction')); 96 | /** @var AnAnonymousAction $action */ 97 | $action = static::$kernel->getContainer()->get('dunglas\actionBundle\tests\fixtures\anonymousaction\ananonymousaction'); 98 | $this->assertEquals('Ho hi', $action()); 99 | } 100 | 101 | public function testCanAccessTraditionalController() 102 | { 103 | $client = static::createClient(); 104 | 105 | $crawler = $client->request('GET', '/traditional'); 106 | $this->assertSame('traditional', $crawler->text()); 107 | } 108 | 109 | public function testTwigExtension() 110 | { 111 | static::bootKernel(); 112 | $this->assertTrue(static::$kernel->getContainer()->has(DummyExtension::class)); 113 | } 114 | 115 | public function testSetterAutowiring() 116 | { 117 | if (!method_exists(Definition::class, 'setAutowiredMethods')) { 118 | $this->markTestSkipped('Setter autowiring requires Symfony 3.3+'); 119 | } 120 | 121 | $client = static::createClient(); 122 | 123 | $crawler = $client->request('GET', '/setter'); 124 | $this->assertSame('setter', $crawler->text()); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * This source file is subject to the MIT license that is bundled 7 | * with this source code in the file LICENSE. 8 | */ 9 | 10 | use Doctrine\Common\Annotations\AnnotationRegistry; 11 | 12 | date_default_timezone_set('UTC'); 13 | 14 | $loader = require __DIR__.'/../vendor/autoload.php'; 15 | 16 | AnnotationRegistry::registerLoader([$loader, 'loadClass']); 17 | 18 | return $loader; 19 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade 2 | 3 | ## From 0.3 to 0.4 4 | 5 | * Twig extensions inside the `*Bundle/Twig` directory are now automatically registered, change the `directories` config 6 | key to exclude this directory if you don't want this behavior. 7 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: false 2 | platform: x86 3 | clone_folder: c:\projects\dunglas\action-bundle 4 | 5 | cache: 6 | - '%LOCALAPPDATA%\Composer\files' 7 | 8 | init: 9 | - SET PATH=c:\tools\php71;%PATH% 10 | 11 | install: 12 | - ps: Set-Service wuauserv -StartupType Manual 13 | - cinst -y php 14 | - cd c:\tools\php71 15 | - copy php.ini-production php.ini /Y 16 | - echo date.timezone="UTC" >> php.ini 17 | - echo extension_dir=ext >> php.ini 18 | - echo extension=php_openssl.dll >> php.ini 19 | - echo memory_limit=1G >> php.ini 20 | - cd %APPVEYOR_BUILD_FOLDER% 21 | - php -r "readfile('http://getcomposer.org/installer');" | php 22 | - php composer.phar install --no-interaction --no-progress 23 | - appveyor DownloadFile https://phar.phpunit.de/phpunit.phar 24 | 25 | test_script: 26 | - cd %APPVEYOR_BUILD_FOLDER% 27 | - php phpunit.phar --verbose 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dunglas/action-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Symfony controllers, redesigned", 5 | "keywords": ["Symfony", "controllers", "action", "autowiring", "routing"], 6 | "homepage": "https://dunglas.fr/2016/01/dunglasactionbundle-symfony-controllers-redesigned/", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Kévin Dunglas", 11 | "email": "dunglas@gmail.com", 12 | "homepage": "https://dunglas.fr" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.5.9", 17 | "symfony/config": "^2.8 || ^3.0", 18 | "symfony/dependency-injection": "^2.8 || ^3.0", 19 | "symfony/finder": "^2.8 || ^3.0", 20 | "symfony/http-kernel": "^2.8 || ^3.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^4.6 || ^5.0", 24 | "sensio/framework-extra-bundle": "^3.0.10", 25 | "symfony/browser-kit": "^2.8 || ^3.0", 26 | "symfony/console": "^2.8 || ^3.0", 27 | "symfony/css-selector": "^2.8 || ^3.0", 28 | "symfony/expression-language": "^2.8 || ^3.0", 29 | "symfony/framework-bundle": "^2.8 || ^3.0", 30 | "twig/twig": "^1.28 || ^2.0" 31 | }, 32 | "conflict": { 33 | "symfony/framework-bundle": "<2.8.5 || >3.0,<3.0.5" 34 | }, 35 | "autoload": { 36 | "psr-4": { "Dunglas\\ActionBundle\\": "" }, 37 | "exclude-from-classmap": [ 38 | "/Tests/" 39 | ] 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "1.0.x-dev" 44 | } 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Tests 19 | 20 | 21 | 22 | 23 | 24 | . 25 | 26 | Tests 27 | vendor 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------