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/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/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..22cedcf 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 :
-
+
*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
@@ -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/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..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;
@@ -39,7 +41,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);
@@ -50,3 +52,19 @@ function optimize(string $dir = 'Resources/public/images', int $width = 145, str
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());
+}
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);