From 802774b8550154288d9133687c8a2c935a7bb70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFck=20Piera?= Date: Sun, 11 Oct 2026 19:16:58 +0200 Subject: [PATCH 1/2] Move to the standard bundle directory structure --- CHANGELOG.md | 1 + DependencyInjection/GifExceptionExtension.php | 43 -------------- GifExceptionBundle.php | 18 ------ README.md | 4 +- Resources/config/services.php | 31 ---------- castor.php | 2 +- composer.json | 7 +-- {Resources/doc/images => docs}/demo.gif | Bin phpunit.xml.dist | 6 +- .../images/403/cat-lizzard.gif | Bin .../images/403/cat-puppy.gif | Bin .../images/403/dog-cat-stairs.gif | Bin .../public => public}/images/403/gandalf.gif | Bin .../images/403/goat-motorbike.gif | Bin .../images/403/men-in-black-flash.gif | Bin .../images/403/netbook-horse.gif | Bin .../images/403/raccoon-denied.gif | Bin .../images/404/confused-travolta.gif | Bin .../images/404/looping-bird.gif | Bin .../images/404/missing-stapler.gif | Bin .../images/404/not-the-droids.gif | Bin .../images/404/nothing-to-see-here.gif | Bin .../images/404/raccoon-lost-candy.gif | Bin .../images/503/raccoon-screaming.gif | Bin .../images/503/swivel-chair.gif | Bin .../images/503/talk-to-the-hand.gif | Bin .../images/other/athletic.gif | Bin .../public => public}/images/other/bottle.gif | Bin .../images/other/captain-america.gif | Bin .../images/other/car-accident.gif | Bin .../images/other/carousel-fail-o.gif | Bin .../public => public}/images/other/castle.gif | Bin .../public => public}/images/other/dunk.gif | Bin .../images/other/facepalm.gif | Bin .../images/other/garbage.gif | Bin .../public => public}/images/other/hitme.gif | Bin .../images/other/kangaroo.gif | Bin .../public => public}/images/other/karate.gif | Bin .../images/other/life-sometimes.gif | Bin .../public => public}/images/other/metro.gif | Bin .../public => public}/images/other/nope.gif | Bin .../public => public}/images/other/panda.gif | Bin .../public => public}/images/other/pingou.gif | Bin .../public => public}/images/other/plane.gif | Bin .../public => public}/images/other/plouf.gif | Bin .../public => public}/images/other/pool.gif | Bin .../images/other/truck-bridge.gif | Bin .../images/other/try-fail-repeat.gif | Bin .../public => public}/images/other/tupper.gif | Bin .../images/other/you-blew-it.gif | Bin .../EventListener}/ReplaceImageListener.php | 0 src/GifExceptionBundle.php | 54 ++++++++++++++++++ tests/GifsTest.php | 2 +- 53 files changed, 62 insertions(+), 106 deletions(-) delete mode 100644 DependencyInjection/GifExceptionExtension.php delete mode 100644 GifExceptionBundle.php delete mode 100644 Resources/config/services.php rename {Resources/doc/images => docs}/demo.gif (100%) rename {Resources/public => public}/images/403/cat-lizzard.gif (100%) rename {Resources/public => public}/images/403/cat-puppy.gif (100%) rename {Resources/public => public}/images/403/dog-cat-stairs.gif (100%) rename {Resources/public => public}/images/403/gandalf.gif (100%) rename {Resources/public => public}/images/403/goat-motorbike.gif (100%) rename {Resources/public => public}/images/403/men-in-black-flash.gif (100%) rename {Resources/public => public}/images/403/netbook-horse.gif (100%) rename {Resources/public => public}/images/403/raccoon-denied.gif (100%) rename {Resources/public => public}/images/404/confused-travolta.gif (100%) rename {Resources/public => public}/images/404/looping-bird.gif (100%) rename {Resources/public => public}/images/404/missing-stapler.gif (100%) rename {Resources/public => public}/images/404/not-the-droids.gif (100%) rename {Resources/public => public}/images/404/nothing-to-see-here.gif (100%) rename {Resources/public => public}/images/404/raccoon-lost-candy.gif (100%) rename {Resources/public => public}/images/503/raccoon-screaming.gif (100%) rename {Resources/public => public}/images/503/swivel-chair.gif (100%) rename {Resources/public => public}/images/503/talk-to-the-hand.gif (100%) rename {Resources/public => public}/images/other/athletic.gif (100%) rename {Resources/public => public}/images/other/bottle.gif (100%) rename {Resources/public => public}/images/other/captain-america.gif (100%) rename {Resources/public => public}/images/other/car-accident.gif (100%) rename {Resources/public => public}/images/other/carousel-fail-o.gif (100%) rename {Resources/public => public}/images/other/castle.gif (100%) rename {Resources/public => public}/images/other/dunk.gif (100%) rename {Resources/public => public}/images/other/facepalm.gif (100%) rename {Resources/public => public}/images/other/garbage.gif (100%) rename {Resources/public => public}/images/other/hitme.gif (100%) rename {Resources/public => public}/images/other/kangaroo.gif (100%) rename {Resources/public => public}/images/other/karate.gif (100%) rename {Resources/public => public}/images/other/life-sometimes.gif (100%) rename {Resources/public => public}/images/other/metro.gif (100%) rename {Resources/public => public}/images/other/nope.gif (100%) rename {Resources/public => public}/images/other/panda.gif (100%) rename {Resources/public => public}/images/other/pingou.gif (100%) rename {Resources/public => public}/images/other/plane.gif (100%) rename {Resources/public => public}/images/other/plouf.gif (100%) rename {Resources/public => public}/images/other/pool.gif (100%) rename {Resources/public => public}/images/other/truck-bridge.gif (100%) rename {Resources/public => public}/images/other/try-fail-repeat.gif (100%) rename {Resources/public => public}/images/other/tupper.gif (100%) rename {Resources/public => public}/images/other/you-blew-it.gif (100%) rename {EventListener => src/EventListener}/ReplaceImageListener.php (100%) create mode 100644 src/GifExceptionBundle.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 025091f..bd52d63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Replaced the `gifexception:optimize` command and `bin/optimizer.php` by the `castor optimize` task * Upgraded the test suite to PHPUnit 11 * Fixed a `TypeError` when the exception code is not an integer +* Moved to the standard bundle directory structure (`src/`, `public/`) and `AbstractBundle`, the `GifExceptionExtension` class has been removed ## 1.11.1 (2026-05-26) diff --git a/DependencyInjection/GifExceptionExtension.php b/DependencyInjection/GifExceptionExtension.php deleted file mode 100644 index dc5442c..0000000 --- a/DependencyInjection/GifExceptionExtension.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Joli\GifExceptionBundle\DependencyInjection; - -use Symfony\Component\Config\FileLocator; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; - -class GifExceptionExtension extends Extension -{ - public function load(array $configs, ContainerBuilder $container): void - { - if (!$container->getParameter('kernel.debug')) { - return; - } - - $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__) . '/Resources/config')); - $loader->load('services.php'); - - $gifs = []; - - $pattern = __DIR__ . '/../Resources/public/images/*/*.gif'; - $images = glob($pattern); - - if ($images) { - foreach ($images as $path) { - $gifs[basename(\dirname($path))][] = basename($path); - } - } - - $container->getDefinition('gif_exception.listener.replace_image')->replaceArgument(0, $gifs); - } -} diff --git a/GifExceptionBundle.php b/GifExceptionBundle.php deleted file mode 100644 index 7f3f096..0000000 --- a/GifExceptionBundle.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Joli\GifExceptionBundle; - -use Symfony\Component\HttpKernel\Bundle\Bundle; - -class GifExceptionBundle extends Bundle -{ -} diff --git a/README.md b/README.md index 80885ad..7133a99 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The GhostBuster of your exception page! It displays a GIF instead of Symfony's ghost on exception page and it's compatible with Symfony versions from 6.4 to 8.2. Here is what it looks like : -![Demo Symfony 4.4](Resources/doc/images/demo.gif) +![Demo](docs/demo.gif) *Be aware that we can not be held responsible for any loss of productivity during development.* @@ -29,7 +29,7 @@ Now enjoy your exceptions \o/ ## Add some more gifs! We need you to improve the included GIFs! Do not hesitate to open PRs to add -more gifs in [Resources/public/images/](Resources/public/images/), it will be very welcomed! :wink: +more gifs in [public/images/](public/images/), it will be very welcomed! :wink: ## Optimizing gifs diff --git a/Resources/config/services.php b/Resources/config/services.php deleted file mode 100644 index 4a37be0..0000000 --- a/Resources/config/services.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Joli\GifExceptionBundle\DependencyInjection\Loader\Configurator; - -use Joli\GifExceptionBundle\EventListener\ReplaceImageListener; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - -use function Symfony\Component\DependencyInjection\Loader\Configurator\abstract_arg; -use function Symfony\Component\DependencyInjection\Loader\Configurator\param; -use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - -return static function (ContainerConfigurator $container) { - $container->services() - ->set('gif_exception.listener.replace_image', ReplaceImageListener::class) - ->args([ - abstract_arg('gif paths'), - param('kernel.error_controller'), - service('assets.packages')->nullOnInvalid(), - ]) - ->tag('kernel.event_subscriber') - ; -}; diff --git a/castor.php b/castor.php index daa8a90..9b5d581 100644 --- a/castor.php +++ b/castor.php @@ -39,7 +39,7 @@ function phpstan(): void } #[AsTask(description: 'Optimize and resize gifs with gifsicle')] -function optimize(string $dir = 'Resources/public/images', int $width = 145, string $level = '-O3'): void +function optimize(string $dir = 'public/images', int $width = 145, string $level = '-O3'): void { foreach (glob("{$dir}/*/*.gif") ?: [] as $gif) { $before = filesize($gif); diff --git a/composer.json b/composer.json index 43caa91..b223f15 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,8 @@ "license": "MIT", "autoload": { "psr-4": { - "Joli\\GifExceptionBundle\\": "" - }, - "exclude-from-classmap": [ - "/tests/" - ] + "Joli\\GifExceptionBundle\\": "src/" + } }, "autoload-dev": { "psr-4": { diff --git a/Resources/doc/images/demo.gif b/docs/demo.gif similarity index 100% rename from Resources/doc/images/demo.gif rename to docs/demo.gif diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 986d8ed..056de27 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,12 +23,8 @@ - . + src - - tests - vendor - diff --git a/Resources/public/images/403/cat-lizzard.gif b/public/images/403/cat-lizzard.gif similarity index 100% rename from Resources/public/images/403/cat-lizzard.gif rename to public/images/403/cat-lizzard.gif diff --git a/Resources/public/images/403/cat-puppy.gif b/public/images/403/cat-puppy.gif similarity index 100% rename from Resources/public/images/403/cat-puppy.gif rename to public/images/403/cat-puppy.gif diff --git a/Resources/public/images/403/dog-cat-stairs.gif b/public/images/403/dog-cat-stairs.gif similarity index 100% rename from Resources/public/images/403/dog-cat-stairs.gif rename to public/images/403/dog-cat-stairs.gif diff --git a/Resources/public/images/403/gandalf.gif b/public/images/403/gandalf.gif similarity index 100% rename from Resources/public/images/403/gandalf.gif rename to public/images/403/gandalf.gif diff --git a/Resources/public/images/403/goat-motorbike.gif b/public/images/403/goat-motorbike.gif similarity index 100% rename from Resources/public/images/403/goat-motorbike.gif rename to public/images/403/goat-motorbike.gif diff --git a/Resources/public/images/403/men-in-black-flash.gif b/public/images/403/men-in-black-flash.gif similarity index 100% rename from Resources/public/images/403/men-in-black-flash.gif rename to public/images/403/men-in-black-flash.gif diff --git a/Resources/public/images/403/netbook-horse.gif b/public/images/403/netbook-horse.gif similarity index 100% rename from Resources/public/images/403/netbook-horse.gif rename to public/images/403/netbook-horse.gif diff --git a/Resources/public/images/403/raccoon-denied.gif b/public/images/403/raccoon-denied.gif similarity index 100% rename from Resources/public/images/403/raccoon-denied.gif rename to public/images/403/raccoon-denied.gif diff --git a/Resources/public/images/404/confused-travolta.gif b/public/images/404/confused-travolta.gif similarity index 100% rename from Resources/public/images/404/confused-travolta.gif rename to public/images/404/confused-travolta.gif diff --git a/Resources/public/images/404/looping-bird.gif b/public/images/404/looping-bird.gif similarity index 100% rename from Resources/public/images/404/looping-bird.gif rename to public/images/404/looping-bird.gif diff --git a/Resources/public/images/404/missing-stapler.gif b/public/images/404/missing-stapler.gif similarity index 100% rename from Resources/public/images/404/missing-stapler.gif rename to public/images/404/missing-stapler.gif diff --git a/Resources/public/images/404/not-the-droids.gif b/public/images/404/not-the-droids.gif similarity index 100% rename from Resources/public/images/404/not-the-droids.gif rename to public/images/404/not-the-droids.gif diff --git a/Resources/public/images/404/nothing-to-see-here.gif b/public/images/404/nothing-to-see-here.gif similarity index 100% rename from Resources/public/images/404/nothing-to-see-here.gif rename to public/images/404/nothing-to-see-here.gif diff --git a/Resources/public/images/404/raccoon-lost-candy.gif b/public/images/404/raccoon-lost-candy.gif similarity index 100% rename from Resources/public/images/404/raccoon-lost-candy.gif rename to public/images/404/raccoon-lost-candy.gif diff --git a/Resources/public/images/503/raccoon-screaming.gif b/public/images/503/raccoon-screaming.gif similarity index 100% rename from Resources/public/images/503/raccoon-screaming.gif rename to public/images/503/raccoon-screaming.gif diff --git a/Resources/public/images/503/swivel-chair.gif b/public/images/503/swivel-chair.gif similarity index 100% rename from Resources/public/images/503/swivel-chair.gif rename to public/images/503/swivel-chair.gif diff --git a/Resources/public/images/503/talk-to-the-hand.gif b/public/images/503/talk-to-the-hand.gif similarity index 100% rename from Resources/public/images/503/talk-to-the-hand.gif rename to public/images/503/talk-to-the-hand.gif diff --git a/Resources/public/images/other/athletic.gif b/public/images/other/athletic.gif similarity index 100% rename from Resources/public/images/other/athletic.gif rename to public/images/other/athletic.gif diff --git a/Resources/public/images/other/bottle.gif b/public/images/other/bottle.gif similarity index 100% rename from Resources/public/images/other/bottle.gif rename to public/images/other/bottle.gif diff --git a/Resources/public/images/other/captain-america.gif b/public/images/other/captain-america.gif similarity index 100% rename from Resources/public/images/other/captain-america.gif rename to public/images/other/captain-america.gif diff --git a/Resources/public/images/other/car-accident.gif b/public/images/other/car-accident.gif similarity index 100% rename from Resources/public/images/other/car-accident.gif rename to public/images/other/car-accident.gif diff --git a/Resources/public/images/other/carousel-fail-o.gif b/public/images/other/carousel-fail-o.gif similarity index 100% rename from Resources/public/images/other/carousel-fail-o.gif rename to public/images/other/carousel-fail-o.gif diff --git a/Resources/public/images/other/castle.gif b/public/images/other/castle.gif similarity index 100% rename from Resources/public/images/other/castle.gif rename to public/images/other/castle.gif diff --git a/Resources/public/images/other/dunk.gif b/public/images/other/dunk.gif similarity index 100% rename from Resources/public/images/other/dunk.gif rename to public/images/other/dunk.gif diff --git a/Resources/public/images/other/facepalm.gif b/public/images/other/facepalm.gif similarity index 100% rename from Resources/public/images/other/facepalm.gif rename to public/images/other/facepalm.gif diff --git a/Resources/public/images/other/garbage.gif b/public/images/other/garbage.gif similarity index 100% rename from Resources/public/images/other/garbage.gif rename to public/images/other/garbage.gif diff --git a/Resources/public/images/other/hitme.gif b/public/images/other/hitme.gif similarity index 100% rename from Resources/public/images/other/hitme.gif rename to public/images/other/hitme.gif diff --git a/Resources/public/images/other/kangaroo.gif b/public/images/other/kangaroo.gif similarity index 100% rename from Resources/public/images/other/kangaroo.gif rename to public/images/other/kangaroo.gif diff --git a/Resources/public/images/other/karate.gif b/public/images/other/karate.gif similarity index 100% rename from Resources/public/images/other/karate.gif rename to public/images/other/karate.gif diff --git a/Resources/public/images/other/life-sometimes.gif b/public/images/other/life-sometimes.gif similarity index 100% rename from Resources/public/images/other/life-sometimes.gif rename to public/images/other/life-sometimes.gif diff --git a/Resources/public/images/other/metro.gif b/public/images/other/metro.gif similarity index 100% rename from Resources/public/images/other/metro.gif rename to public/images/other/metro.gif diff --git a/Resources/public/images/other/nope.gif b/public/images/other/nope.gif similarity index 100% rename from Resources/public/images/other/nope.gif rename to public/images/other/nope.gif diff --git a/Resources/public/images/other/panda.gif b/public/images/other/panda.gif similarity index 100% rename from Resources/public/images/other/panda.gif rename to public/images/other/panda.gif diff --git a/Resources/public/images/other/pingou.gif b/public/images/other/pingou.gif similarity index 100% rename from Resources/public/images/other/pingou.gif rename to public/images/other/pingou.gif diff --git a/Resources/public/images/other/plane.gif b/public/images/other/plane.gif similarity index 100% rename from Resources/public/images/other/plane.gif rename to public/images/other/plane.gif diff --git a/Resources/public/images/other/plouf.gif b/public/images/other/plouf.gif similarity index 100% rename from Resources/public/images/other/plouf.gif rename to public/images/other/plouf.gif diff --git a/Resources/public/images/other/pool.gif b/public/images/other/pool.gif similarity index 100% rename from Resources/public/images/other/pool.gif rename to public/images/other/pool.gif diff --git a/Resources/public/images/other/truck-bridge.gif b/public/images/other/truck-bridge.gif similarity index 100% rename from Resources/public/images/other/truck-bridge.gif rename to public/images/other/truck-bridge.gif diff --git a/Resources/public/images/other/try-fail-repeat.gif b/public/images/other/try-fail-repeat.gif similarity index 100% rename from Resources/public/images/other/try-fail-repeat.gif rename to public/images/other/try-fail-repeat.gif diff --git a/Resources/public/images/other/tupper.gif b/public/images/other/tupper.gif similarity index 100% rename from Resources/public/images/other/tupper.gif rename to public/images/other/tupper.gif diff --git a/Resources/public/images/other/you-blew-it.gif b/public/images/other/you-blew-it.gif similarity index 100% rename from Resources/public/images/other/you-blew-it.gif rename to public/images/other/you-blew-it.gif diff --git a/EventListener/ReplaceImageListener.php b/src/EventListener/ReplaceImageListener.php similarity index 100% rename from EventListener/ReplaceImageListener.php rename to src/EventListener/ReplaceImageListener.php diff --git a/src/GifExceptionBundle.php b/src/GifExceptionBundle.php new file mode 100644 index 0000000..de85551 --- /dev/null +++ b/src/GifExceptionBundle.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\GifExceptionBundle; + +use Joli\GifExceptionBundle\EventListener\ReplaceImageListener; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + +use function Symfony\Component\DependencyInjection\Loader\Configurator\param; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + +class GifExceptionBundle extends AbstractBundle +{ + public function getPath(): string + { + return \dirname(__DIR__); + } + + /** + * @param array $config + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + if (!$builder->getParameter('kernel.debug')) { + return; + } + + $gifs = []; + + foreach (glob($this->getPath() . '/public/images/*/*.gif') ?: [] as $path) { + $gifs[basename(\dirname($path))][] = basename($path); + } + + $container->services() + ->set('gif_exception.listener.replace_image', ReplaceImageListener::class) + ->args([ + $gifs, + param('kernel.error_controller'), + service('assets.packages')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ; + } +} diff --git a/tests/GifsTest.php b/tests/GifsTest.php index be21ef1..3effc3d 100644 --- a/tests/GifsTest.php +++ b/tests/GifsTest.php @@ -17,7 +17,7 @@ class GifsTest extends TestCase { public function testAllGifsAreResized(): void { - $gifs = glob(__DIR__ . '/../Resources/public/images/*/*.gif'); + $gifs = glob(__DIR__ . '/../public/images/*/*.gif'); self::assertNotEmpty($gifs); From 8adeaaa52e2a23afda27cfa656c5b5454b0f8440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFck=20Piera?= Date: Sun, 11 Oct 2026 20:25:52 +0200 Subject: [PATCH 2/2] Add a Castor task to start the demo application --- CONTRIBUTING.md | 4 +++- README.md | 1 + castor.php | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f81ded0..a7cb6f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,9 +57,11 @@ If you want to manually test the error page, you can start the demo application by running the following command: ```shell -cd tests/app && bin/console assets:install && symfony serve +castor serve ``` +It requires the [Symfony CLI](https://symfony.com/download). + Then open your browser at the displayed url (something like [http://127.0.0.1:8000](http://localhost:8000/). You can now browse urls like [http://127.0.0.1:8000/error-XXX](http://localhost:8000/error-404) where XXX is the HTTP status code you want to test. diff --git a/README.md b/README.md index 7133a99..22cedcf 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ castor cs # Fix PHP coding standards castor cs --dry-run # Check PHP coding standards castor phpstan # Run static analysis castor optimize # Optimize and resize gifs +castor serve # Start the demo application ``` ## Further documentation diff --git a/castor.php b/castor.php index 9b5d581..862022e 100644 --- a/castor.php +++ b/castor.php @@ -10,7 +10,9 @@ */ use Castor\Attribute\AsTask; +use Symfony\Component\Process\ExecutableFinder; +use function Castor\context; use function Castor\io; use function Castor\run; @@ -50,3 +52,19 @@ function optimize(string $dir = 'public/images', int $width = 145, string $level io()->writeln(sprintf('%s: %d KB -> %d KB (%d%%)', $gif, $before / 1024, $after / 1024, 100 - $after * 100 / $before)); } } + +#[AsTask(description: 'Start the demo application to test the error page manually')] +function serve(): void +{ + if (null === (new ExecutableFinder())->find('symfony')) { + io()->error('The Symfony CLI is required, see https://symfony.com/download'); + + exit(1); + } + + // Run from tests/app so assets:install targets tests/app/public, not the bundle's own public dir + $context = context()->withWorkingDirectory('tests/app'); + + run('bin/console assets:install', $context); + run('symfony serve', $context->withTty()); +}