diff --git a/config/ddd.php.stub b/config/ddd.php.stub new file mode 100644 index 0000000..325d5ce --- /dev/null +++ b/config/ddd.php.stub @@ -0,0 +1,160 @@ + {{domain_path}}, + + /* + |-------------------------------------------------------------------------- + | Domain Namespace + |-------------------------------------------------------------------------- + | + | The root domain namespace. + | + */ + 'domain_namespace' => {{domain_namespace}}, + + /* + |-------------------------------------------------------------------------- + | Domain Object Namespaces + |-------------------------------------------------------------------------- + | + | This value contains the default namespaces of generated domain + | objects relative to the domain namespace of which the object + | belongs to. + | + | e.g., Domain\Invoicing\Models\* + | Domain\Invoicing\Data\* + | Domain\Invoicing\ViewModels\* + | Domain\Invoicing\ValueObjects\* + | Domain\Invoicing\Actions\* + | + */ + 'namespaces' => [ + 'model' => {{namespaces.model}}, + 'data_transfer_object' => {{namespaces.data_transfer_object}}, + 'view_model' => {{namespaces.view_model}}, + 'value_object' => {{namespaces.value_object}}, + 'action' => {{namespaces.action}}, + 'cast' => 'Casts', + 'channel' => 'Channels', + 'command' => 'Commands', + 'enum' => 'Enums', + 'event' => 'Events', + 'exception' => 'Exceptions', + 'factory' => 'Database\Factories', + 'job' => 'Jobs', + 'listener' => 'Listeners', + 'mail' => 'Mail', + 'notification' => 'Notifications', + 'observer' => 'Observers', + 'policy' => 'Policies', + 'provider' => 'Providers', + 'resource' => 'Resources', + 'rule' => 'Rules', + 'scope' => 'Scopes', + ], + + /* + |-------------------------------------------------------------------------- + | Base Model + |-------------------------------------------------------------------------- + | + | The base class which generated domain models should extend. By default, + | generated domain models will extend `Domain\Shared\Models\BaseModel`, + | which will be created if it doesn't already exist. + | + */ + 'base_model' => {{base_model}}, + + /* + |-------------------------------------------------------------------------- + | Base DTO + |-------------------------------------------------------------------------- + | + | The base class which generated data transfer objects should extend. By + | default, generated DTOs will extend `Spatie\LaravelData\Data` from + | Spatie's Laravel-data package, a highly recommended data object + | package to work with. + | + */ + 'base_dto' => {{base_dto}}, + + /* + |-------------------------------------------------------------------------- + | Base ViewModel + |-------------------------------------------------------------------------- + | + | The base class which generated view models should extend. By default, + | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`, + | which will be created if it doesn't already exist. + | + */ + 'base_view_model' => {{base_view_model}}, + + /* + |-------------------------------------------------------------------------- + | Base Action + |-------------------------------------------------------------------------- + | + | The base class which generated action objects should extend. By default, + | generated actions are based on the `lorisleiva/laravel-actions` package + | and do not extend anything. + | + */ + 'base_action' => {{base_action}}, + + /* + |-------------------------------------------------------------------------- + | Autoloading + |-------------------------------------------------------------------------- + | + | Configure whether domain providers, commands, policies, and factories + | should be auto-discovered and registered. + | + */ + 'autoload' => [ + /** + * When enabled, any class within the domain layer extending `Illuminate\Support\ServiceProvider` + * will be auto-registered as a service provider + */ + 'providers' => true, + + /** + * When enabled, any class within the domain layer extending `Illuminate\Console\Command` + * will be auto-registered as a command when running in console. + */ + 'commands' => true, + + /** + * When enabled, the package will register a custom policy discovery callback to resolve policy names + * for domain models, and fallback to Laravel's default for all other cases. + */ + 'policies' => true, + + /** + * When enabled, the package will register a custom factory discovery callback to resolve factory names + * for domain models, and fallback to Laravel's default for all other cases. + */ + 'factories' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Caching + |-------------------------------------------------------------------------- + | + | The folder where the domain cache files will be stored. Used for domain + | autoloading. + | + */ + 'cache_directory' => 'bootstrap/cache/ddd', +]; diff --git a/src/Commands/UpgradeCommand.php b/src/Commands/UpgradeCommand.php new file mode 100644 index 0000000..e696605 --- /dev/null +++ b/src/Commands/UpgradeCommand.php @@ -0,0 +1,61 @@ +components->warn('Config file was not published. Nothing to upgrade!'); + + return; + } + + $replacements = [ + 'domain_path' => 'paths.domain', + 'domain_namespace' => 'domain_namespace', + 'namespaces.model' => 'namespaces.models', + 'namespaces.data_transfer_object' => 'namespaces.data_transfer_objects', + 'namespaces.view_model' => 'namespaces.view_models', + 'namespaces.value_object' => 'namespaces.value_objects', + 'namespaces.action' => 'namespaces.actions', + 'base_model' => 'base_model', + 'base_dto' => 'base_dto', + 'base_view_model' => 'base_view_model', + 'base_action' => 'base_action', + ]; + + $oldConfig = require config_path('ddd.php'); + $oldConfig = Arr::dot($oldConfig); + + // Grab a flesh copy of the new config + $newConfigContent = file_get_contents(__DIR__.'/../../config/ddd.php.stub'); + + foreach ($replacements as $dotPath => $legacyKey) { + $value = match (true) { + array_key_exists($dotPath, $oldConfig) => $oldConfig[$dotPath], + array_key_exists($legacyKey, $oldConfig) => $oldConfig[$legacyKey], + default => config("ddd.{$dotPath}"), + }; + + $newConfigContent = str_replace( + '{{'.$dotPath.'}}', + var_export($value, true), + $newConfigContent + ); + } + + // Write the new config to the config file + file_put_contents(config_path('ddd.php'), $newConfigContent); + + $this->components->info('Configuration upgraded successfully.'); + } +} diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 4994536..5246ecf 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -22,6 +22,7 @@ public function configurePackage(Package $package): void ->hasConfigFile() ->hasCommands([ Commands\InstallCommand::class, + Commands\UpgradeCommand::class, Commands\CacheCommand::class, Commands\CacheClearCommand::class, Commands\DomainListCommand::class, diff --git a/tests/InstallTest.php b/tests/Command/InstallTest.php similarity index 97% rename from tests/InstallTest.php rename to tests/Command/InstallTest.php index 5d58ab9..df49882 100644 --- a/tests/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -18,7 +18,7 @@ $command->execute(); expect(file_exists($path))->toBeTrue(); - expect(file_get_contents($path))->toEqual(file_get_contents(__DIR__.'/../config/ddd.php')); + expect(file_get_contents($path))->toEqual(file_get_contents(__DIR__.'/../../config/ddd.php')); unlink($path); }); diff --git a/tests/Command/UpgradeTest.php b/tests/Command/UpgradeTest.php new file mode 100644 index 0000000..9d649e6 --- /dev/null +++ b/tests/Command/UpgradeTest.php @@ -0,0 +1,44 @@ +toBeTrue(); + + $this->artisan('ddd:upgrade') + ->expectsOutputToContain('Configuration upgraded successfully.') + ->execute(); + + Artisan::call('config:clear'); + + $expectedValues = Arr::dot($expectedValues); + + $configAsArray = require config_path('ddd.php'); + + foreach ($expectedValues as $path => $value) { + expect(data_get($configAsArray, $path)) + ->toEqual($value, "Config {$path} does not match expected value."); + } +})->with('configUpgrades'); + +it('skips upgrade if config file was not published', function () { + $path = config_path('ddd.php'); + + if (file_exists($path)) { + unlink($path); + } + + expect(file_exists($path))->toBeFalse(); + + $this->artisan('ddd:upgrade') + ->expectsOutputToContain('Config file was not published. Nothing to upgrade!') + ->execute(); + + expect(file_exists($path))->toBeFalse(); +}); diff --git a/tests/Datasets/Config.php b/tests/Datasets/Config.php new file mode 100644 index 0000000..eac108b --- /dev/null +++ b/tests/Datasets/Config.php @@ -0,0 +1,48 @@ + [ + __DIR__.'/resources/config.0.10.0.php', + + // Expected net result + [ + 'domain_path' => 'src/CustomDomainFolder', + 'domain_namespace' => 'CustomDomainNamespace', + 'namespaces' => [ + 'model' => 'CustomModels', + 'data_transfer_object' => 'CustomData', + 'view_model' => 'CustomViewModels', + 'value_object' => 'CustomValueObjects', + 'action' => 'CustomActions', + 'cast' => 'Casts', + 'channel' => 'Channels', + 'command' => 'Commands', + 'enum' => 'Enums', + 'event' => 'Events', + 'exception' => 'Exceptions', + 'factory' => 'Database\Factories', + 'job' => 'Jobs', + 'listener' => 'Listeners', + 'mail' => 'Mail', + 'notification' => 'Notifications', + 'observer' => 'Observers', + 'policy' => 'Policies', + 'provider' => 'Providers', + 'resource' => 'Resources', + 'rule' => 'Rules', + 'scope' => 'Scopes', + ], + 'base_model' => 'Domain\Shared\Models\CustomBaseModel', + 'base_dto' => 'Spatie\LaravelData\Data', + 'base_view_model' => 'Domain\Shared\ViewModels\CustomViewModel', + 'base_action' => null, + 'autoload' => [ + 'providers' => true, + 'commands' => true, + 'policies' => true, + 'factories' => true, + ], + 'cache_directory' => 'bootstrap/cache/ddd', + ], + ], +]); diff --git a/tests/Datasets/resources/config.0.10.0.php b/tests/Datasets/resources/config.0.10.0.php new file mode 100644 index 0000000..59e9940 --- /dev/null +++ b/tests/Datasets/resources/config.0.10.0.php @@ -0,0 +1,96 @@ + 'src/CustomDomainFolder', + + /* + |-------------------------------------------------------------------------- + | Domain Namespace + |-------------------------------------------------------------------------- + | + | The root domain namespace. + | + */ + 'domain_namespace' => 'CustomDomainNamespace', + + /* + |-------------------------------------------------------------------------- + | Domain Object Namespaces + |-------------------------------------------------------------------------- + | + | This value contains the default namespaces of generated domain + | objects relative to the domain namespace of which the object + | belongs to. + | + | e.g., Domain/Invoicing/Models/* + | Domain/Invoicing/Data/* + | Domain/Invoicing/ViewModels/* + | Domain/Invoicing/ValueObjects/* + | Domain/Invoicing/Actions/* + | + */ + 'namespaces' => [ + 'models' => 'CustomModels', + 'data_transfer_objects' => 'CustomData', + 'view_models' => 'CustomViewModels', + 'value_objects' => 'CustomValueObjects', + 'actions' => 'CustomActions', + ], + + /* + |-------------------------------------------------------------------------- + | Base Model + |-------------------------------------------------------------------------- + | + | The base class which generated domain models should extend. By default, + | generated domain models will extend `Domain\Shared\Models\BaseModel`, + | which will be created if it doesn't already exist. + | + */ + 'base_model' => 'Domain\Shared\Models\CustomBaseModel', + + /* + |-------------------------------------------------------------------------- + | Base DTO + |-------------------------------------------------------------------------- + | + | The base class which generated data transfer objects should extend. By + | default, generated DTOs will extend `Spatie\LaravelData\Data` from + | Spatie's Laravel-data package, a highly recommended data object + | package to work with. + | + */ + 'base_dto' => 'Spatie\LaravelData\Data', + + /* + |-------------------------------------------------------------------------- + | Base ViewModel + |-------------------------------------------------------------------------- + | + | The base class which generated view models should extend. By default, + | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`, + | which will be created if it doesn't already exist. + | + */ + 'base_view_model' => 'Domain\Shared\ViewModels\CustomViewModel', + + /* + |-------------------------------------------------------------------------- + | Base Action + |-------------------------------------------------------------------------- + | + | The base class which generated action objects should extend. By default, + | generated actions are based on the `lorisleiva/laravel-actions` package + | and do not extend anything. + | + */ + 'base_action' => null, +];