diff --git a/src/Abstract/Models/BaseModel.php b/src/Abstract/Models/BaseModel.php index 39dab683d..6f37143f8 100644 --- a/src/Abstract/Models/BaseModel.php +++ b/src/Abstract/Models/BaseModel.php @@ -15,7 +15,7 @@ abstract class BaseModel extends LaravelEloquentModel implements Resource protected static function newFactory() { - $factoryName = apiato()->factoryDiscovery() + $factoryName = apiato()->factory() ->resolveFactoryName(static::class); if (is_string($factoryName)) { diff --git a/src/Foundation/Apiato.php b/src/Foundation/Apiato.php index 54da88aa6..3580920f3 100644 --- a/src/Foundation/Apiato.php +++ b/src/Foundation/Apiato.php @@ -3,8 +3,9 @@ namespace Apiato\Foundation; use Apiato\Foundation\Configuration\ApplicationBuilder; -use Apiato\Foundation\Configuration\FactoryDiscovery; +use Apiato\Foundation\Configuration\Factory; use Apiato\Foundation\Configuration\Localization; +use Apiato\Foundation\Configuration\Repository; use Apiato\Foundation\Configuration\Routing; use Apiato\Foundation\Configuration\Seeding; use Apiato\Foundation\Configuration\View; @@ -33,7 +34,8 @@ final class Apiato private Localization $localization; private View $view; private Seeding $seeding; - private FactoryDiscovery $factoryDiscovery; + private Factory $factory; + private Repository $repository; private function __construct( private readonly string $basePath, @@ -111,10 +113,21 @@ public function withRouting(callable|null $callback = null): self public function withFactories(callable|null $callback = null): self { - $this->factoryDiscovery ??= new FactoryDiscovery(); + $this->factory ??= new Factory(); if (!is_null($callback)) { - $callback($this->factoryDiscovery); + $callback($this->factory); + } + + return $this; + } + + public function withRepositories(callable|null $callback = null): self + { + $this->repository ??= new Repository(); + + if (!is_null($callback)) { + $callback($this->repository); } return $this; @@ -203,7 +216,7 @@ public function providers(): array } /* - * Get the configuration files to be loaded. + * Get the config files. * * @return string[] */ @@ -215,7 +228,7 @@ public function configs(): array } /* - * Get the helper files to be loaded. + * Get the helper files. * * @return string[] */ @@ -226,51 +239,89 @@ public function helpers(): array )->toArray(); } + /** + * Get the migration paths. + */ public function migrations(): array { return $this->migrationPaths; } + /** + * Get the event paths. + */ public function events(): array { return $this->eventDiscoveryPaths; } + /** + * Get the command paths. + */ public function commands(): array { return $this->commandPaths; } + /** + * Register the API routes. + */ public function registerApiRoutes(): void { $this->routing->registerApiRoutes(); } + /** + * Get Web routes. + */ public function webRoutes(): array { return $this->routing->webRoutes(); } + /** + * Get the seeding configuration. + */ public function seeding(): Seeding { return $this->seeding; } + /** + * Get the routing configuration. + */ public function routing(): Routing { return $this->routing; } - public function factoryDiscovery(): FactoryDiscovery + /** + * Get the factory configuration. + */ + public function factory(): Factory + { + return $this->factory; + } + + /** + * Get the repository configuration. + */ + public function repository(): Repository { - return $this->factoryDiscovery; + return $this->repository; } + /** + * Get the localization configuration. + */ public function localization(): Localization { return $this->localization; } + /** + * Get the view configuration. + */ public function view(): View { return $this->view; diff --git a/src/Foundation/Configuration/ApplicationBuilder.php b/src/Foundation/Configuration/ApplicationBuilder.php index 7f445887e..17ad0ffa6 100644 --- a/src/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Foundation/Configuration/ApplicationBuilder.php @@ -59,7 +59,15 @@ private function withDefaults(string $basePath): void )->loadWebRoutesFrom( ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/WEB/Routes')), ); - })->withFactories(); + })->withFactories() + ->withRepositories(); + } + + public function withRepositories(callable|null $callback = null): self + { + $this->apiato->withRepositories($callback); + + return $this; } public function withFactories(callable|null $callback = null): self diff --git a/src/Foundation/Configuration/Factory.php b/src/Foundation/Configuration/Factory.php new file mode 100644 index 000000000..27752d867 --- /dev/null +++ b/src/Foundation/Configuration/Factory.php @@ -0,0 +1,53 @@ +resolveFactoryNameUsing( + static function (string $modelName): string|null { + $factoryName = Str::of($modelName)->beforeLast('Models\\') + ->append('Data\\Factories\\' . class_basename($modelName) . 'Factory') + ->value(); + + if (class_exists($factoryName)) { + return $factoryName; + } + + return null; + }, + ); + } + + /** + * @param \Closure(class-string): (class-string|null) $callback + */ + public function resolveFactoryNameUsing(\Closure $callback): self + { + self::$nameResolver = $callback; + + return $this; + } + + /** + * @param class-string $modelName + * + * @return class-string|null + */ + public function resolveFactoryName(string $modelName): string|null + { + return app()->call(self::$nameResolver, ['modelName' => $modelName]); + } +} diff --git a/src/Foundation/Configuration/FactoryDiscovery.php b/src/Foundation/Configuration/FactoryDiscovery.php deleted file mode 100644 index cba1cdf6c..000000000 --- a/src/Foundation/Configuration/FactoryDiscovery.php +++ /dev/null @@ -1,55 +0,0 @@ -resolveFactoryNameUsing( - static function (string $modelName): string { - $factoryNamespace = Str::of($modelName)->beforeLast('Models\\') - ->append('Data\\Factories\\'); - - return $factoryNamespace - ->append(class_basename($modelName) . 'Factory') - ->value(); - }, - ); - } - - /** - * @template TModel of Model - * @template TFactory of Factory - * - * @param \Closure(class-string): class-string $callback - */ - public function resolveFactoryNameUsing(\Closure $callback): self - { - self::$nameResolver = $callback; - - return $this; - } - - public function resolveFactoryName(string $modelName): string|null - { - $factoryName = app()->call(self::$nameResolver, ['modelName' => $modelName]); - - if ($this->isValidFactory($factoryName)) { - return $factoryName; - } - - return null; - } - - private function isValidFactory(string $factoryName): bool - { - return class_exists($factoryName) && is_a($factoryName, Factory::class, true); - } -} diff --git a/src/Foundation/Configuration/Repository.php b/src/Foundation/Configuration/Repository.php new file mode 100644 index 000000000..ca0b6e1ee --- /dev/null +++ b/src/Foundation/Configuration/Repository.php @@ -0,0 +1,57 @@ +resolveModelNameUsing( + static function (string $repositoryName): string { + $modelName = Str::of($repositoryName)->beforeLast('Data\\Repositories\\') + ->append('Models\\') + ->append( + Str::of(class_basename($repositoryName)) + ->beforeLast('Repository') + ->title()->value(), + )->value(); + + if (class_exists($modelName)) { + return $modelName; + } + + throw new \RuntimeException("Model not found for repository: {$repositoryName}"); + }, + ); + } + + /** + * @param \Closure(class-string): (class-string) $callback + */ + public function resolveModelNameUsing(\Closure $callback): self + { + self::$nameResolver = $callback; + + return $this; + } + + /** + * @param class-string $repositoryName + * + * @return class-string + */ + public function resolveModelName(string $repositoryName): string + { + return app()->call(self::$nameResolver, ['repositoryName' => $repositoryName]); + } +} diff --git a/tests/Unit/Abstract/Repositories/RepositoryTest.php b/tests/Unit/Abstract/Repositories/RepositoryTest.php index 6110b2c28..43a2481ae 100644 --- a/tests/Unit/Abstract/Repositories/RepositoryTest.php +++ b/tests/Unit/Abstract/Repositories/RepositoryTest.php @@ -8,6 +8,7 @@ use Pest\Expectation; use Workbench\App\Containers\Identity\User\Data\Repositories\UserRepository; use Workbench\App\Containers\Identity\User\Models\User; +use Workbench\App\Containers\MySection\Book\Data\Repositories\BookRepository; use Workbench\App\Containers\MySection\Book\Models\Book; describe(class_basename(Repository::class), function (): void { @@ -119,6 +120,12 @@ public function shouldEagerLoadIncludes(): bool }); }); + it('discover its model', function (): void { + $repository = new BookRepository(app()); + + expect($repository->model())->toBe(Book::class); + }); + it('can cache', function (): void { config()->set('repository.cache.enabled', true); config()->set('repository.cache.minutes', 1); diff --git a/tests/Unit/Foundation/Configuration/FactoryDiscoveryTest.php b/tests/Unit/Foundation/Configuration/FactoryDiscoveryTest.php deleted file mode 100644 index cfdf5dbe5..000000000 --- a/tests/Unit/Foundation/Configuration/FactoryDiscoveryTest.php +++ /dev/null @@ -1,32 +0,0 @@ -resolveFactoryName(Book::class)) - ->toBe(BookFactory::class); - }); - - it('can set custom factory name resolver', function (): void { - $configuration = new FactoryDiscovery(); - - $configuration->resolveFactoryNameUsing(static fn (string $modelName): string => BookFactory::class); - - expect($configuration->resolveFactoryName('test')) - ->toBe(BookFactory::class); - }); - - it('returns null if cannot resolve factory', function (): void { - $configuration = new FactoryDiscovery(); - - $configuration->resolveFactoryNameUsing(static fn (string $modelName): string => $modelName); - - expect($configuration->resolveFactoryName('test')) - ->toBeNull(); - }); -})->covers(FactoryDiscovery::class); diff --git a/tests/Unit/Foundation/Configuration/FactoryTest.php b/tests/Unit/Foundation/Configuration/FactoryTest.php new file mode 100644 index 000000000..59cc64d5f --- /dev/null +++ b/tests/Unit/Foundation/Configuration/FactoryTest.php @@ -0,0 +1,26 @@ +resolveFactoryName(Book::class)) + ->toBe(BookFactory::class) + ->and($configuration->resolveFactoryName('test')) + ->toBeNull(); + + }); + + it('can set custom factory name resolver', function (): void { + $configuration = new Factory(); + + $configuration->resolveFactoryNameUsing(static fn (string $modelName): string => BookFactory::class); + + expect($configuration->resolveFactoryName('test')) + ->toBe(BookFactory::class); + }); +})->covers(Factory::class); diff --git a/tests/Unit/Foundation/Configuration/RepositoryTest.php b/tests/Unit/Foundation/Configuration/RepositoryTest.php new file mode 100644 index 000000000..558577acc --- /dev/null +++ b/tests/Unit/Foundation/Configuration/RepositoryTest.php @@ -0,0 +1,26 @@ +resolveModelName(BookRepository::class)) + ->toBe(Book::class) + ->and(static fn () => $configuration->resolveModelName('test')) + ->toThrow(\RuntimeException::class); + + }); + + it('can set custom model name resolver', function (): void { + $configuration = new Repository(); + + $configuration->resolveModelNameUsing(static fn (string $repositoryName): string => Book::class); + + expect($configuration->resolveModelName('test')) + ->toBe(Book::class); + }); +})->covers(Repository::class);