diff --git a/config/cycle.php b/config/cycle.php index dc12c89b..ad306524 100644 --- a/config/cycle.php +++ b/config/cycle.php @@ -324,4 +324,25 @@ 'entityBehavior' => [ 'register' => env('CYCLE_REGISTER_ENTITY_BEHAVIOUR', true), ], + + 'integrations' => [ + /* + * Enables migration generation for Laravel Queues + */ + 'queue' => [ + 'enabled' => env('CYCLE_ADAPTER_QUEUE_INTEGRATION', true), + ], + /* + * Enables migration generation for Laravel Sessions + */ + 'session' => [ + 'enabled' => env('CYCLE_ADAPTER_SESSION_INTEGRATION', true), + ], + /* + * Enables migration generation for Laravel Cache + */ + 'cache' => [ + 'enabled' => env('CYCLE_ADAPTER_CACHE_INTEGRATION', true), + ], + ], ]; diff --git a/docs/pages/services/_meta.json b/docs/pages/services/_meta.json index f890979f..351d8080 100644 --- a/docs/pages/services/_meta.json +++ b/docs/pages/services/_meta.json @@ -4,5 +4,8 @@ "testing": "Testing", "validation": "Validation", "pagination": "Pagination", + "sessions": "Sessions", + "queue": "Queue", + "cache": "Cache", "laravel-telescope": "Laravel Telescope" } diff --git a/docs/pages/services/cache.mdx b/docs/pages/services/cache.mdx new file mode 100644 index 00000000..d423f065 --- /dev/null +++ b/docs/pages/services/cache.mdx @@ -0,0 +1 @@ +# Cache diff --git a/docs/pages/services/laravel-telescope.mdx b/docs/pages/services/laravel-telescope.mdx index 35e45c98..526194f0 100644 --- a/docs/pages/services/laravel-telescope.mdx +++ b/docs/pages/services/laravel-telescope.mdx @@ -21,7 +21,19 @@ Install, telescope as usual, via Composer package manager: composer require laravel/telescope ``` -### Step 2: Publish Telescope Assets +### Step 2: Add .env Configuration + +Add the following configuration to your `.env` file: + +```dotenv filename=".env" +... + +DB_USE_TELESCOPE_LOGGER=true + +... +``` + +### Step 3: Publish Telescope Assets After installing Telescope, publish its assets and migrations using the `telescope:install` Artisan command. @@ -29,25 +41,10 @@ After installing Telescope, publish its assets and migrations using the `telesco php artisan telescope:install ``` -### Step 3: Run Telescope Migrations trough Cycle-ORM-Adapter +### Step 4: Run Telescope Migrations trough Cycle-ORM-Adapter After installing Telescope, you should also run the migrate command in order to create the tables needed to store Telescope's data, but as you are using Cycle ORM, you should avoid using default `php artisan migrate` command, and instead, do the following steps: -Edit `config/cycle.php` file and add the following code to the `tokenizer.directories` array: - -```php {7} filename="config/cycle.php" -return [ - // ... - 'tokenizer' => [ - // ... - 'directories' => [ - app_path(), - __DIR__ . '/../vendor/wayofdev/laravel-cycle-orm-adapter/src/Bridge/Telescope/Entities', - ], - ], -], -``` - Run the following command to create the Telescope tables: ```bash @@ -56,8 +53,7 @@ php artisan cycle:migrate:init php artisan cycle:orm:migrate --split --run ``` - -### Step 4: Add the CycleORM Query Watcher +### Step 5: Add the CycleORM Query Watcher Next, edit your `config/telescope.php` configuration file and add the following lines to the `watchers` array, right after the default`Watchers\QueryWatcher::class` line: @@ -85,18 +81,6 @@ return [ ]; ``` -### Step 5: Add .env Configuration - -Add the following configuration to your `.env` file: - -```dotenv filename=".env" -... - -DB_USE_TELESCOPE_LOGGER=true - -... -``` - ### Step 6: Access Laravel Telescope Finally, you may access the Telescope dashboard via the `/telescope` route. Of course, don't forget to start your Laravel application: diff --git a/docs/pages/services/pagination.mdx b/docs/pages/services/pagination.mdx index 2d787769..88a8d2a2 100644 --- a/docs/pages/services/pagination.mdx +++ b/docs/pages/services/pagination.mdx @@ -8,12 +8,68 @@ Pagination is provided by external package [wayofdev/laravel-paginator](https:// composer require wayofdev/laravel-paginator ``` -## Usage +## Usage with Repositories
### Step 1: Define `paginate()` method in your Repository -@todo +Create a `paginate()` method in your abstract repository class that will return a `CyclePaginator` instance. + +```php filename="app/Infrastructure/Persistence/Cycle/Repository.php" + $select + */ + public function __construct( + protected Select $select, + protected EntityManagerInterface $entityManager + ) { + parent::__construct($select); + } + + // ... + + public function paginate(int $perPage = 20, int $page = 1, string $pageName = 'page'): CyclePaginator + { + return $this->paginateQuery( + $this->select(), + $perPage, + $page, + $pageName, + ); + } + + protected function paginateQuery(Select $query, int $perPage = 20, int $page = 1, string $pageName = 'page'): CyclePaginator + { + return new CyclePaginator( + (new SpiralPaginator($perPage))->withPage($page)->paginate($query), + $this->createCollection($query->fetchAll()), + $pageName, + ); + } + + protected function createCollection(iterable $items): Collection + { + return new Collection($items); + } +} +```
diff --git a/docs/pages/services/queue.mdx b/docs/pages/services/queue.mdx new file mode 100644 index 00000000..d095a2b0 --- /dev/null +++ b/docs/pages/services/queue.mdx @@ -0,0 +1 @@ +# Queue diff --git a/docs/pages/services/sessions.mdx b/docs/pages/services/sessions.mdx new file mode 100644 index 00000000..f185a0e7 --- /dev/null +++ b/docs/pages/services/sessions.mdx @@ -0,0 +1 @@ +# Sessions diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b053275f..d1596566 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,6 +13,15 @@ + + + + + + + + + diff --git a/src/Bridge/Cache/Entities/Cache.php b/src/Bridge/Cache/Entities/Cache.php new file mode 100644 index 00000000..8ea7a13f --- /dev/null +++ b/src/Bridge/Cache/Entities/Cache.php @@ -0,0 +1,21 @@ +app->get(Repository::class); + + $config->set('cycle.tokenizer.directories', array_merge( + $config->get('cycle.tokenizer.directories', []), + [__DIR__ . '/../Entities'] + )); + } +} diff --git a/src/Bridge/Laravel/Providers/CycleServiceProvider.php b/src/Bridge/Laravel/Providers/CycleServiceProvider.php index 5043b35c..3060f502 100644 --- a/src/Bridge/Laravel/Providers/CycleServiceProvider.php +++ b/src/Bridge/Laravel/Providers/CycleServiceProvider.php @@ -10,9 +10,13 @@ use Illuminate\Support\ServiceProvider; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; +use WayOfDev\Cycle\Bridge\Cache\Providers\CacheServiceProvider; use WayOfDev\Cycle\Bridge\Laravel\Console\Commands\Database; use WayOfDev\Cycle\Bridge\Laravel\Console\Commands\Migrations; use WayOfDev\Cycle\Bridge\Laravel\Console\Commands\ORM; +use WayOfDev\Cycle\Bridge\Queue\Providers\QueueServiceProvider; +use WayOfDev\Cycle\Bridge\Session\Providers\SessionServiceProvider; +use WayOfDev\Cycle\Bridge\Telescope\Providers\TelescopeServiceProvider; final class CycleServiceProvider extends ServiceProvider { @@ -65,6 +69,24 @@ public function register(): void foreach ($registrators as $registrator) { (new $registrator())($this->app); } + + $this->registerIntegrations(); + } + + private function registerIntegrations(): void + { + $services = [ + 'cycle.integrations.session.enabled' => SessionServiceProvider::class, + 'cycle.integrations.cache.enabled' => CacheServiceProvider::class, + 'cycle.integrations.queue.enabled' => QueueServiceProvider::class, + 'cycle.database.logger.use_telescope' => TelescopeServiceProvider::class, + ]; + + foreach ($services as $configKey => $providerClass) { + if (config($configKey) === true) { + $this->app->register($providerClass); + } + } } private function registerConsoleCommands(): void diff --git a/src/Bridge/Queue/Entities/FailedJob.php b/src/Bridge/Queue/Entities/FailedJob.php new file mode 100644 index 00000000..de1e169b --- /dev/null +++ b/src/Bridge/Queue/Entities/FailedJob.php @@ -0,0 +1,36 @@ +app->get(Repository::class); + + $config->set('cycle.tokenizer.directories', array_merge( + $config->get('cycle.tokenizer.directories', []), + [__DIR__ . '/../Entities'] + )); + } +} diff --git a/src/Bridge/Session/Entities/Session.php b/src/Bridge/Session/Entities/Session.php new file mode 100644 index 00000000..901b65f5 --- /dev/null +++ b/src/Bridge/Session/Entities/Session.php @@ -0,0 +1,35 @@ +app->get(Repository::class); + + $config->set('cycle.tokenizer.directories', array_merge( + $config->get('cycle.tokenizer.directories', []), + [__DIR__ . '/../Entities'] + )); + } +} diff --git a/src/Bridge/Telescope/Providers/TelescopeServiceProvider.php b/src/Bridge/Telescope/Providers/TelescopeServiceProvider.php new file mode 100644 index 00000000..55577305 --- /dev/null +++ b/src/Bridge/Telescope/Providers/TelescopeServiceProvider.php @@ -0,0 +1,30 @@ +app->get(Repository::class); + + $config->set('cycle.tokenizer.directories', array_merge( + $config->get('cycle.tokenizer.directories', []), + [__DIR__ . '/../Entities'] + )); + } +} diff --git a/tests/src/Bridge/Cache/Providers/CacheServiceProviderTest.php b/tests/src/Bridge/Cache/Providers/CacheServiceProviderTest.php new file mode 100644 index 00000000..1b1cb098 --- /dev/null +++ b/tests/src/Bridge/Cache/Providers/CacheServiceProviderTest.php @@ -0,0 +1,42 @@ +artisanCall('cache:clear'); + $this->refreshApplication(); + + $this::assertFalse($this->app->providerIsLoaded(CacheServiceProvider::class)); + + $this->assertConsoleCommandOutputDoesNotContainStrings('cycle:orm:render', ['--no-color' => true], [ + '[cache] :: default.cache', + '[cacheLock] :: default.cache_locks', + 'Entity: WayOfDev\Cycle\Bridge\Cache\Entities\Cache', + 'Entity: WayOfDev\Cycle\Bridge\Cache\Entities\CacheLock', + ]); + } + + #[Test] + public function it_generates_migration_when_enabled(): void + { + $this->app->register(CacheServiceProvider::class); + + $this::assertTrue($this->app->providerIsLoaded(CacheServiceProvider::class)); + $this->assertConsoleCommandOutputContainsStrings('cycle:orm:render', ['--no-color' => true], [ + '[cache] :: default.cache', + '[cacheLock] :: default.cache_locks', + 'Entity: WayOfDev\Cycle\Bridge\Cache\Entities\Cache', + 'Entity: WayOfDev\Cycle\Bridge\Cache\Entities\CacheLock', + ]); + } +} diff --git a/tests/src/Bridge/Laravel/Providers/CycleServiceProviderTest.php b/tests/src/Bridge/Laravel/Providers/CycleServiceProviderTest.php index 1c167657..4ae512d9 100644 --- a/tests/src/Bridge/Laravel/Providers/CycleServiceProviderTest.php +++ b/tests/src/Bridge/Laravel/Providers/CycleServiceProviderTest.php @@ -5,38 +5,35 @@ namespace WayOfDev\Tests\Bridge\Laravel\Providers; use Cycle\Database\Config\DatabaseConfig; +use Cycle\ORM\EntityManager; use Cycle\ORM\EntityManagerInterface; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; +use PHPUnit\Framework\Attributes\Test; use WayOfDev\Tests\TestCase; class CycleServiceProviderTest extends TestCase { - /** - * @test - * - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ + #[Test] public function it_gets_database_config_from_container(): void { /** @var DatabaseConfig $config */ - $config = $this->app->get(DatabaseConfig::class); + $config = $this->app->make(DatabaseConfig::class); self::assertArrayHasKey('default', $config->toArray()); self::assertArrayHasKey('databases', $config->toArray()); self::assertArrayHasKey('drivers', $config->toArray()); } - /** - * @test - * - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ + #[Test] public function it_gets_entity_manager_instance_from_container(): void { - $manager = $this->app->get(EntityManagerInterface::class); + /** @var EntityManager|null $manager */ + $manager = $this->app->make(EntityManagerInterface::class); self::assertInstanceOf(EntityManagerInterface::class, $manager); } + + #[Test] + public function it_registers_configurations_correctly(): void + { + $this::assertNotNull(config('cycle')); + } } diff --git a/tests/src/Bridge/Queue/Providers/QueueServiceProviderTest.php b/tests/src/Bridge/Queue/Providers/QueueServiceProviderTest.php new file mode 100644 index 00000000..6478a472 --- /dev/null +++ b/tests/src/Bridge/Queue/Providers/QueueServiceProviderTest.php @@ -0,0 +1,45 @@ +refreshApplication(); + + $this::assertFalse($this->app->providerIsLoaded(QueueServiceProvider::class)); + + $this->assertConsoleCommandOutputDoesNotContainStrings('cycle:orm:render', ['--no-color' => true], [ + '[job] :: default.jobs', + '[failedJob] :: default.failed_jobs', + '[jobBatch] :: default.job_batches', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\Job', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\JobBatch', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\FailedJob', + ]); + } + + #[Test] + public function it_generates_migration_when_enabled(): void + { + $this->app->register(QueueServiceProvider::class); + + $this::assertTrue($this->app->providerIsLoaded(QueueServiceProvider::class)); + $this->assertConsoleCommandOutputContainsStrings('cycle:orm:render', ['--no-color' => true], [ + '[job] :: default.jobs', + '[failedJob] :: default.failed_jobs', + '[jobBatch] :: default.job_batches', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\Job', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\JobBatch', + 'Entity: WayOfDev\Cycle\Bridge\Queue\Entities\FailedJob', + ]); + } +} diff --git a/tests/src/Bridge/Session/Providers/SessionServiceProviderTest.php b/tests/src/Bridge/Session/Providers/SessionServiceProviderTest.php new file mode 100644 index 00000000..26d2563b --- /dev/null +++ b/tests/src/Bridge/Session/Providers/SessionServiceProviderTest.php @@ -0,0 +1,37 @@ +refreshApplication(); + + $this::assertFalse($this->app->providerIsLoaded(SessionServiceProvider::class)); + + $this->assertConsoleCommandOutputDoesNotContainStrings('cycle:orm:render', ['--no-color' => true], [ + '[session] :: default.sessions', + 'Entity: WayOfDev\Cycle\Bridge\Session\Entities\Session', + ]); + } + + #[Test] + public function it_generates_migration_when_enabled(): void + { + $this->app->register(SessionServiceProvider::class); + + $this::assertTrue($this->app->providerIsLoaded(SessionServiceProvider::class)); + $this->assertConsoleCommandOutputContainsStrings('cycle:orm:render', ['--no-color' => true], [ + '[session] :: default.sessions', + 'Entity: WayOfDev\Cycle\Bridge\Session\Entities\Session', + ]); + } +} diff --git a/tests/src/Bridge/Telescope/Providers/TelescopeServiceProviderTest.php b/tests/src/Bridge/Telescope/Providers/TelescopeServiceProviderTest.php new file mode 100644 index 00000000..3842446e --- /dev/null +++ b/tests/src/Bridge/Telescope/Providers/TelescopeServiceProviderTest.php @@ -0,0 +1,45 @@ +refreshApplication(); + + $this::assertFalse($this->app->providerIsLoaded(TelescopeServiceProvider::class)); + + $this->assertConsoleCommandOutputDoesNotContainStrings('cycle:orm:render', ['--no-color' => true], [ + '[telescopeEntry] :: default.telescope_entries', + '[telescopeEntryTag] :: default.telescope_entries_tags', + '[telescopeMonitoring] :: default.telescope_monitoring', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\Telescope', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\TelescopeEntryTag', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\TelescopeMonitoring', + ]); + } + + #[Test] + public function it_generates_migration_when_enabled(): void + { + $this->app->register(TelescopeServiceProvider::class); + + $this::assertTrue($this->app->providerIsLoaded(TelescopeServiceProvider::class)); + $this->assertConsoleCommandOutputContainsStrings('cycle:orm:render', ['--no-color' => true], [ + '[telescopeEntry] :: default.telescope_entries', + '[telescopeEntryTag] :: default.telescope_entries_tags', + '[telescopeMonitoring] :: default.telescope_monitoring', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\Telescope', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\TelescopeEntryTag', + 'Entity: WayOfDev\Cycle\Bridge\Telescope\Entities\TelescopeMonitoring', + ]); + } +} diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php index cbd16abe..3fe042f6 100644 --- a/tests/src/TestCase.php +++ b/tests/src/TestCase.php @@ -53,7 +53,6 @@ protected function setUp(): void 'cycle.tokenizer.directories' => array_merge( config('cycle.tokenizer.directories'), [__DIR__ . '/../app/Entities'], - // [__DIR__ . '/../../src/Bridge/Telescope/Entities'], ), 'cycle.migrations.directory' => $this->migrationsPath, ]); @@ -73,28 +72,45 @@ public function artisanCall(string $command, array $parameters = []) return $this->app[Kernel::class]->call($command, $parameters); } - protected function assertConsoleCommandOutputContainsStrings( + protected function assertConsoleCommandOutput( string $command, - array $args = [], - array|string $strings = [] + array $args, + $strings, + callable $assertionCallback ): void { $this->artisanCall($command, $args); $output = Artisan::output(); foreach ((array) $strings as $string) { - $this::assertStringContainsString( - $string, - $output, - sprintf( - 'Console command [%s] with args [%s] does not contain string [%s]', - $command, - json_encode($args), - $string - ) - ); + $assertionCallback($string, $output, sprintf( + 'Console command [%s] with args [%s] output assertion failed for string [%s]', + $command, + json_encode($args), + $string + )); } } + protected function assertConsoleCommandOutputContainsStrings( + string $command, + array $args = [], + $strings = [] + ): void { + $this->assertConsoleCommandOutput($command, $args, $strings, function ($string, $output, $message): void { + $this::assertStringContainsString($string, $output, $message); + }); + } + + protected function assertConsoleCommandOutputDoesNotContainStrings( + string $command, + array $args = [], + $strings = [] + ): void { + $this->assertConsoleCommandOutput($command, $args, $strings, function ($string, $output, $message): void { + $this::assertStringNotContainsString($string, $output, $message); + }); + } + protected function getPackageProviders($app): array { return [