diff --git a/CHANGELOG.md b/CHANGELOG.md index c402834..6f971b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,14 @@ Les versions `0.1.x`/`0.2.0` (juin 2026) correspondent à la phase de prototypag - `Support\ServiceIntegration` — classe de base pour représenter un service externe comme une unique classe liée au conteneur : lit son propre bloc `config('services.')`, expose un `HttpClient` pré-configuré (`base_uri` + jeton `Bearer`) via `http()`, et vérifie ses propres webhooks via `verifyWebhook()` — qui lit, comme le middleware `webhook`, `webhook_header`/`webhook_timestamped`/`webhook_tolerance` depuis la config (un appel sans argument suffit ; les paramètres ne servent qu'à surcharger ponctuellement). Ne remplace pas le mécanisme de binding existant (`#[Module(providers: [...])]`) — comble l'absence de convention sur *la forme* d'un service externe une fois lié. - `config/services.php` (skeleton) documente désormais cette convention (`base_uri`, `secret`, `webhook_secret`, ...) dans son exemple commenté. +### Changed + +- **`fakerphp/faker` et `psy/psysh` déplacés vers `require-dev`** — tous deux ne sont utilisés qu'en développement (`Database\Factory::fake()`/`db:seed` pour le premier, la commande REPL `tinker` pour le second), jamais au boot ni dans une requête HTTP. Une installation `composer install --no-dev` (prod) n'embarque donc plus ni l'un ni l'autre. `tinker` continue de se dégrader proprement si `psy/psysh` manque (`Console\Kernel::registerFrameworkCommands()` enveloppe déjà chaque commande dans un `try/catch`) ; `db:seed` dispose maintenant de son propre `try/catch` dédié (voir ci-dessous) puisque, contrairement à `tinker`, l'échec ne peut survenir qu'à l'exécution du seeder, pas à l'enregistrement de la commande. Une application qui a besoin de l'un ou l'autre en local/CI doit désormais les redéclarer dans son propre `require-dev` — c'est fait pour le skeleton. + +### Fixed + +- **`db:seed` plantait avec une trace brute et incompréhensible si un seeder utilisait `Database\Factory::fake()` sans que `fakerphp/faker` soit installé** (cas qui devient réel maintenant que ce package est en `require-dev`, voir ci-dessus) — `DbSeedCommand` capture désormais cette erreur spécifiquement et affiche un message actionnable (`composer require --dev fakerphp/faker`) au lieu de laisser remonter l'exception ; toute autre erreur de seeder continue de s'afficher normalement. + ### Documentation - `docs/middleware.md` référençait tous les middlewares sauf le nouveau `webhook` — ajouté au tableau. `docs/authentication.md` ne montrait pas la clé `redirects` dans son exemple `config/auth.php` alors qu'elle est bien lue par `Authenticate` — complété. diff --git a/composer.json b/composer.json index c202e3e..42c8930 100644 --- a/composer.json +++ b/composer.json @@ -27,11 +27,9 @@ "require": { "php": ">=8.2", "doctrine/dbal": "^4.4", - "fakerphp/faker": "^1.23", "firebase/php-jwt": "^7.0", "league/flysystem": "^3.29", "monolog/monolog": "^3.10", - "psy/psysh": "^0.12.23", "symfony/cache": "^7.0", "symfony/console": "^7.0", "symfony/http-client": "^7.0", @@ -44,15 +42,19 @@ "vlucas/phpdotenv": "^5.6" }, "require-dev": { + "fakerphp/faker": "^1.23", "league/flysystem-aws-s3-v3": "^3.29", "pestphp/pest": "^3.8", "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.0", + "psy/psysh": "^0.12.23" }, "suggest": { "ext-apcu": "Faster in-memory cache and rate limiting", + "fakerphp/faker": "Required by Model::factory()/db:seed when a seeder generates fake data — dev/CI only, not needed in production", "league/flysystem-aws-s3-v3": "S3 filesystem disk support", - "predis/predis": "Redis cache and queue backend" + "predis/predis": "Redis cache and queue backend", + "psy/psysh": "Powers the `tinker` REPL command — dev only, not needed in production" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 06652fb..fd00a26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3bb80a2bf2b953a61ad48e1d0f79d6d2", + "content-hash": "e6d8e06fc23db8454e7e09bc043cc546", "packages": [ { "name": "doctrine/dbal", @@ -302,69 +302,6 @@ ], "time": "2025-03-06T22:45:56+00:00" }, - { - "name": "fakerphp/faker", - "version": "v1.24.1", - "source": { - "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." - }, - "type": "library", - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" - }, - "time": "2024-11-21T13:46:39+00:00" - }, { "name": "firebase/php-jwt", "version": "v7.2.1", @@ -794,63 +731,6 @@ ], "time": "2026-09-29T19:21:12+00:00" }, - { - "name": "nikic/php-parser", - "version": "v5.9.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", - "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" - }, - "time": "2026-09-13T18:51:52+00:00" - }, { "name": "phpoption/phpoption", "version": "1.10.0", @@ -1128,85 +1008,6 @@ }, "time": "2024-09-11T13:17:53+00:00" }, - { - "name": "psy/psysh", - "version": "v0.12.24", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", - "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^5.0 || ^4.0", - "php": "^8.0 || ^7.4", - "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" - }, - "conflict": { - "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "composer/class-map-generator": "^1.6" - }, - "suggest": { - "composer/class-map-generator": "Improved tab completion performance with better class discovery.", - "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." - }, - "bin": [ - "bin/psysh" - ], - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false, - "forward-command": false - }, - "branch-alias": { - "dev-main": "0.12.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Psy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info" - } - ], - "description": "An interactive shell for modern PHP.", - "homepage": "https://psysh.org", - "keywords": [ - "REPL", - "console", - "interactive", - "shell" - ], - "support": { - "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" - }, - "time": "2026-06-29T15:41:09+00:00" - }, { "name": "symfony/cache", "version": "v7.4.20", @@ -3714,6 +3515,69 @@ ], "time": "2026-01-08T08:02:38+00:00" }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, { "name": "fidry/cpu-core-counter", "version": "1.4.1", @@ -4416,6 +4280,63 @@ ], "time": "2026-08-11T10:17:44+00:00" }, + { + "name": "nikic/php-parser", + "version": "v5.9.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + }, + "time": "2026-09-13T18:51:52+00:00" + }, { "name": "nunomaduro/collision", "version": "v8.9.5", @@ -5985,6 +5906,85 @@ }, "time": "2021-10-29T13:26:27+00:00" }, + { + "name": "psy/psysh", + "version": "v0.12.24", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" + }, + "time": "2026-06-29T15:41:09+00:00" + }, { "name": "sebastian/cli-parser", "version": "3.0.2", diff --git a/src/Console/Commands/DbSeedCommand.php b/src/Console/Commands/DbSeedCommand.php index c6fbb3d..aa5e6c6 100644 --- a/src/Console/Commands/DbSeedCommand.php +++ b/src/Console/Commands/DbSeedCommand.php @@ -31,8 +31,28 @@ protected function handle(): int return self::FAILURE; } - $seeder = $this->container->make($class); - $seeder->run(); + try { + $seeder = $this->container->make($class); + $seeder->run(); + } catch (\Throwable $e) { + // fakerphp/faker is a dev-only dependency (Database\Factory::fake()) + // — unlike `tinker`'s psy/psysh, there's no earlier point to catch + // this at (the seeder itself is only resolvable/runnable here), so + // a missing-class \Error from it needs its own friendly message + // rather than surfacing as a raw stack trace. + if (str_contains($e->getMessage(), 'Faker\\')) { + $this->error( + "Seeder [{$class}] uses fake data (Database\\Factory::fake()), " . + "but fakerphp/faker isn't installed — it's a dev-only dependency. " . + "Run: composer require --dev fakerphp/faker" + ); + return self::FAILURE; + } + + $this->error("Seeder [{$class}] failed: {$e->getMessage()}"); + return self::FAILURE; + } + $this->success("Seeded [{$class}]"); return self::SUCCESS; } diff --git a/tests/Unit/DbSeedCommandTest.php b/tests/Unit/DbSeedCommandTest.php new file mode 100644 index 0000000..2a863b7 --- /dev/null +++ b/tests/Unit/DbSeedCommandTest.php @@ -0,0 +1,84 @@ +run(new ArrayInput(['--class' => $class], $cmd->getDefinition()), $output); + return $output->fetch(); +} + +class FixtureSuccessSeeder extends Seeder +{ + public function run(): void + { + // no-op + } +} + +class FixtureFakerDependentSeeder extends Seeder +{ + public function run(): void + { + // Mirrors exactly what PHP throws when fakerphp/faker isn't installed + // and Database\Factory::fake() (or a seeder using it directly) tries + // to instantiate Faker\Generator. + throw new \Error('Class "Faker\Generator" not found'); + } +} + +class FixtureBrokenSeeder extends Seeder +{ + public function run(): void + { + throw new \RuntimeException('something unrelated broke'); + } +} + +test('runs a seeder successfully and reports DONE', function () { + $output = runDbSeedCommand(FixtureSuccessSeeder::class); + + expect($output)->toContain('DONE'); + expect($output)->toContain('Seeded [' . FixtureSuccessSeeder::class . ']'); +}); + +test('reports a missing seeder class without throwing', function () { + $output = runDbSeedCommand('Marrow\Tests\Unit\NoSuchSeederAtAll'); + + expect($output)->toContain('ERROR'); + expect($output)->toContain('not found'); +}); + +test('a seeder failing because fakerphp/faker is not installed gets a friendly, actionable error instead of a raw stack trace', function () { + $output = runDbSeedCommand(FixtureFakerDependentSeeder::class); + + expect($output)->toContain('ERROR'); + expect($output)->toContain('fakerphp/faker'); + expect($output)->toContain('composer require --dev fakerphp/faker'); +}); + +test('any other seeder failure still surfaces its own message, not the Faker-specific one', function () { + $output = runDbSeedCommand(FixtureBrokenSeeder::class); + + expect($output)->toContain('ERROR'); + expect($output)->toContain('something unrelated broke'); + expect($output)->not->toContain('fakerphp/faker'); +});