diff --git a/README.md b/README.md
index f4612ef..b36c698 100644
--- a/README.md
+++ b/README.md
@@ -16,27 +16,36 @@ consumers.
> - Chunked queries to database
> - Draft mode for a process
> - Easy property mapping
-> - Generation of any XML (feeds, sitemaps, etc.)
+> - Generation of any feeds, sitemaps, etc.
## Installation
-To get the latest version of **Laravel Feeds**, simply require the project
-using [Composer](https://getcomposer.org):
+You can install the **Laravel Feeds** package via [Composer](https://getcomposer.org):
```Bash
composer require dragon-code/laravel-feeds
```
-After that, publish the configuration file by call the console command:
+You should publish
+the [migration](database/migrations/2025_09_01_231655_create_feeds_table.php)
+and the [config/feeds.php](config/feeds.php) file with:
```bash
-php artisan vendor:publish --tag=feeds
+php artisan vendor:publish --tag="feeds"
```
+> [!WARNING]
+>
+> Before running migrations, check the database connection settings in the [config/feeds.php](config/feeds.php) file.
+
+Now you can run migrations and proceed to [create feeds](https://feeds.dragon-code.pro/create-feeds.html).
+
## Basic Usage
### Create Feeds
+To create a feed class, use the `make:feed` console command:
+
```bash
php artisan make:feed User -t
```
@@ -44,10 +53,26 @@ php artisan make:feed User -t
As a result of executing the console command, the files `app/Feeds/UserFeed.php` and `app/Feeds/Items/UserFeedItem.php`
will be created.
-### Generate Feeds
+> [!TIP]
+> When creating a feed, an operation/migration will also be created to add it to the database.
+>
+> If the project uses the [Laravel Deploy Operations](https://deploy-operations.dragon-code.pro), then an operation
+> class will be created, otherwise a migration class will be created.
+>
+> This is necessary to add and manage information about feeds in the database.
+
+Check the [operation/migration](https://feeds.dragon-code.pro/create-feeds.html) file that was created for you and run
+the console command:
+
+```bash
+# For Laravel Deploy Operations
+php artisan operations
+
+# For Laravel Migrations
+php artisan migrate
+```
-To generate feeds, create the classes of feeds and its element, add links to the file `config/feeds.php`, next call the
-console command:
+To generate all active feeds, use the console command:
```bash
php artisan feed:generate
@@ -55,7 +80,7 @@ php artisan feed:generate
## Documentation
-[📚 Check out the full documentation to learn everything that Laravel Feeds has to offer.](https://feeds.dragon-code.pro)
+📚 You will find full documentation on the dedicated [documentation](https://feeds.dragon-code.pro) site.
## License
diff --git a/composer.json b/composer.json
index 95bc30b..b1444c8 100644
--- a/composer.json
+++ b/composer.json
@@ -14,6 +14,7 @@
"php": "^8.2",
"ext-dom": "*",
"ext-libxml": "*",
+ "dragonmantank/cron-expression": "^3.4",
"illuminate/database": "^11.0 || ^12.0",
"illuminate/filesystem": "^11.0 || ^12.0",
"illuminate/support": "^11.0 || ^12.0",
@@ -21,6 +22,8 @@
},
"require-dev": {
"dragon-code/codestyler": "^6.3",
+ "dragon-code/laravel-deploy-operations": "^7.1",
+ "mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0 || ^10.0",
"pestphp/pest": "^3.0 || ^4.0",
"pestphp/pest-plugin-laravel": "^3.0 || ^4.0",
@@ -36,7 +39,8 @@
"psr-4": {
"Tests\\": "tests/",
"Workbench\\App\\": "workbench/app/",
- "Workbench\\Database\\Factories\\": "workbench/database/factories/"
+ "Workbench\\Database\\Factories\\": "workbench/database/factories/",
+ "Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
},
"config": {
@@ -63,10 +67,12 @@
"@clear",
"@prepare"
],
+ "build": "@php vendor/bin/testbench workbench:build --ansi",
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
+ "migrate": "@php vendor/bin/testbench migrate:fresh --seed --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"style": "vendor/bin/pint --parallel --ansi",
- "test": "@php vendor/bin/pest --colors=always",
+ "test": "@php vendor/bin/pest --parallel --colors=always",
"test:update": "@php vendor/bin/pest --colors=always --update-snapshots"
}
}
diff --git a/config/feeds.php b/config/feeds.php
index 0a2945c..e51840f 100644
--- a/config/feeds.php
+++ b/config/feeds.php
@@ -3,11 +3,52 @@
declare(strict_types=1);
return [
- 'channels' => [
- // App\Feeds\FooFeed::class => (bool) env('FEED_FOO_ENABLED', true),
- // App\Feeds\BarFeed::class => (bool) env('FEED_BAR_ENABLED', true),
- // App\Feeds\BazFeed::class => (bool) env('FEED_BAZ_ENABLED', false),
+ /**
+ * Pretty-print the generated feed output.
+ *
+ * When enabled, the resulting XML/JSON will include indentation and
+ * human‑friendly formatting. Disable for slightly smaller payload size.
+ *
+ * By default, false
+ */
+ 'pretty' => (bool) env('FEED_PRETTY', false),
+
+ /**
+ * Database table settings used by the package (e.g., for generation logs or state).
+ */
+ 'table' => [
+ /**
+ * The database connection name to use.
+ *
+ * Should match a connection defined in config/database.php under
+ * the "connections" array.
+ */
+ 'connection' => env('DB_CONNECTION', 'sqlite'),
+
+ /**
+ * The database table name used by the package.
+ */
+ 'table' => env('FEED_TABLE', 'feeds'),
],
- 'pretty' => (bool) env('FEED_PRETTY', false),
+ /**
+ * Scheduling options for feed generation/update tasks.
+ */
+ 'schedule' => [
+ /**
+ * Time To Live (in minutes) for the schedule lock or cache.
+ *
+ * Controls how frequently a scheduled job may be executed to avoid
+ * overlapping or excessively frequent runs.
+ */
+ 'ttl' => (int) env('FEED_SCHEDULE_TTL', 1440),
+
+ /**
+ * Run scheduled jobs in the background.
+ *
+ * When true, tasks will be dispatched to run asynchronously so they do
+ * not block the current process. Set to false to run in the foreground.
+ */
+ 'background' => (bool) env('FEED_SCHEDULE_RUN_BACKGROUND', true),
+ ],
];
diff --git a/database/migrations/2025_09_01_231655_create_feeds_table.php b/database/migrations/2025_09_01_231655_create_feeds_table.php
new file mode 100644
index 0000000..3f8cbf2
--- /dev/null
+++ b/database/migrations/2025_09_01_231655_create_feeds_table.php
@@ -0,0 +1,48 @@
+schema()->create($this->table(), function (Blueprint $table) {
+ $table->id();
+
+ $table->string('class')->unique();
+ $table->string('title');
+
+ $table->string('expression');
+
+ $table->boolean('is_active');
+
+ $table->timestamp('last_activity')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ public function down(): void
+ {
+ $this->schema()->dropIfExists($this->table());
+ }
+
+ protected function schema(): Builder
+ {
+ return Schema::connection($this->connection());
+ }
+
+ protected function connection(): ?string
+ {
+ return config('feeds.table.connection');
+ }
+
+ protected function table(): string
+ {
+ return config('feeds.table.table');
+ }
+};
diff --git a/docs/images/laravel-idea.png b/docs/images/laravel-idea.png
index 863dd73..9bb05b6 100644
Binary files a/docs/images/laravel-idea.png and b/docs/images/laravel-idea.png differ
diff --git a/docs/images/laravel-idea_dark.png b/docs/images/laravel-idea_dark.png
index 1684ccd..32c607b 100644
Binary files a/docs/images/laravel-idea_dark.png and b/docs/images/laravel-idea_dark.png differ
diff --git a/docs/laravel-feeds.tree b/docs/laravel-feeds.tree
index 5ff6d18..ff8b0c3 100644
--- a/docs/laravel-feeds.tree
+++ b/docs/laravel-feeds.tree
@@ -19,8 +19,8 @@
-
-
-
+
+
+
diff --git a/docs/snippets/generation-feed-storage.php b/docs/snippets/advanced-feed-chunk.php
similarity index 60%
rename from docs/snippets/generation-feed-storage.php
rename to docs/snippets/advanced-feed-chunk.php
index 01d0bf9..7817eec 100644
--- a/docs/snippets/generation-feed-storage.php
+++ b/docs/snippets/advanced-feed-chunk.php
@@ -6,5 +6,8 @@
class UserFeed extends Feed
{
- protected string $storage = 'public';
+ public function chunkSize(): int
+ {
+ return 500;
+ }
}
diff --git a/docs/snippets/advanced-header.php b/docs/snippets/advanced-feed-header-and-footer.php
similarity index 75%
rename from docs/snippets/advanced-header.php
rename to docs/snippets/advanced-feed-header-and-footer.php
index 3341d28..c9c4c95 100644
--- a/docs/snippets/advanced-header.php
+++ b/docs/snippets/advanced-feed-header-and-footer.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace App\Feeds;
-
use DragonCode\LaravelFeed\Feeds\Feed;
class UserFeed extends Feed
@@ -12,4 +10,9 @@ public function header(): string
{
return '';
}
+
+ public function footer(): string
+ {
+ return '';
+ }
}
diff --git a/docs/snippets/advanced-head-info.php b/docs/snippets/advanced-feed-info-class.php
similarity index 71%
rename from docs/snippets/advanced-head-info.php
rename to docs/snippets/advanced-feed-info-class.php
index 3370923..3d2b065 100644
--- a/docs/snippets/advanced-head-info.php
+++ b/docs/snippets/advanced-feed-info-class.php
@@ -11,7 +11,8 @@ class UserFeedInfo extends FeedInfo
public function toArray(): array
{
return [
- // ...
+ 'name' => config('app.name'),
+ 'company' => config('app.name'),
];
}
}
diff --git a/docs/snippets/advanced-head-feed.php b/docs/snippets/advanced-feed-info.php
similarity index 75%
rename from docs/snippets/advanced-head-feed.php
rename to docs/snippets/advanced-feed-info.php
index ab7e675..24980f9 100644
--- a/docs/snippets/advanced-head-feed.php
+++ b/docs/snippets/advanced-feed-info.php
@@ -2,8 +2,7 @@
declare(strict_types=1);
-namespace App\Feeds;
-
+use App\Feeds\Info\UserFeedInfo;
use DragonCode\LaravelFeed\Feeds\Feed;
use DragonCode\LaravelFeed\Feeds\Info\FeedInfo;
@@ -11,6 +10,6 @@ class UserFeed extends Feed
{
public function info(): FeedInfo
{
- return new FeedInfo;
+ return new UserFeedInfo;
}
}
diff --git a/docs/snippets/generation-feed-filename.php b/docs/snippets/advanced-feed-storage.php
similarity index 63%
rename from docs/snippets/generation-feed-filename.php
rename to docs/snippets/advanced-feed-storage.php
index cf913c4..eae1984 100644
--- a/docs/snippets/generation-feed-filename.php
+++ b/docs/snippets/advanced-feed-storage.php
@@ -6,8 +6,10 @@
class UserFeed extends Feed
{
+ protected string $storage = 'public';
+
public function filename(): string
{
- return 'your/path/may/be/here.xml';
+ return 'some/path/will/be/here.xml';
}
}
diff --git a/docs/snippets/advanced-nested.php b/docs/snippets/advanced-nested.php
index 02b98a9..b0963a4 100644
--- a/docs/snippets/advanced-nested.php
+++ b/docs/snippets/advanced-nested.php
@@ -11,11 +11,11 @@ class UserFeedItem extends FeedItem
public function toArray(): array
{
return [
- 'name' => $this->model->name,
+ 'name' => $this->model->class,
'email' => $this->model->email,
'header' => [
- '@cdata' => '
' . $this->model->name . '
',
+ '@cdata' => '' . $this->model->class . '
',
],
'names' => [
diff --git a/docs/snippets/config-channels.ini b/docs/snippets/config-channels.ini
deleted file mode 100644
index 1513607..0000000
--- a/docs/snippets/config-channels.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-FEED_FOO_ENABLED = true
-FEED_BAR_ENABLED = false
diff --git a/docs/snippets/config-pretty-false.xml b/docs/snippets/config-pretty-false.xml
deleted file mode 100644
index 5d3495e..0000000
--- a/docs/snippets/config-pretty-false.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- [NEWS]:Some 1Some content 1Some extra data
- [NEWS]:Some 2Some content 2Some extra data
-
diff --git a/docs/snippets/config-pretty-true.xml b/docs/snippets/config-pretty-true.xml
deleted file mode 100644
index 7a42836..0000000
--- a/docs/snippets/config-pretty-true.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- [NEWS]:Some 1
- Some content 1
- Some extra data
-
-
- [NEWS]:Some 2
- Some content 2
- Some extra data
-
-
diff --git a/docs/snippets/feeds-feed.php b/docs/snippets/create-feeds-feed.php
similarity index 100%
rename from docs/snippets/feeds-feed.php
rename to docs/snippets/create-feeds-feed.php
diff --git a/docs/snippets/feeds-feed-item-result.xml b/docs/snippets/feeds-feed-item-result.xml
deleted file mode 100644
index 152f1f9..0000000
--- a/docs/snippets/feeds-feed-item-result.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- John Doe
- john.doe@example.com
-
-
-
diff --git a/docs/snippets/feeds-feed-item.php b/docs/snippets/generation-feed-item.php
similarity index 51%
rename from docs/snippets/feeds-feed-item.php
rename to docs/snippets/generation-feed-item.php
index db0176a..6423c11 100644
--- a/docs/snippets/feeds-feed-item.php
+++ b/docs/snippets/generation-feed-item.php
@@ -12,16 +12,8 @@ class UserFeedItem extends FeedItem
public function toArray(): array
{
return [
- 'name' => $this->model->name,
+ 'name' => $this->model->class,
'email' => $this->model->email,
-
- 'header' => [
- '@attributes' => [
- 'my-key-1' => 'my value 1',
- 'my-key-2' => 'my value 2',
- ],
- '@cdata' => '' . $this->model->name . '
',
- ],
];
}
}
diff --git a/docs/snippets/receipt-instagram-config.php b/docs/snippets/receipt-instagram-config.php
deleted file mode 100644
index 533ad16..0000000
--- a/docs/snippets/receipt-instagram-config.php
+++ /dev/null
@@ -1,9 +0,0 @@
- [
- App\Feeds\InstagramFeed::class => (bool) env('FEED_INSTAGRAM_ENABLED', true),
- ],
-];
diff --git a/docs/snippets/receipt-sitemap-config.php b/docs/snippets/receipt-sitemap-config.php
deleted file mode 100644
index 803e0cd..0000000
--- a/docs/snippets/receipt-sitemap-config.php
+++ /dev/null
@@ -1,9 +0,0 @@
- [
- App\Feeds\Sitemaps\ProductFeed::class => (bool) env('FEED_SITEMAP_POSTS_ENABLED', true),
- ],
-];
diff --git a/docs/snippets/receipt-yandex-config.php b/docs/snippets/receipt-yandex-config.php
deleted file mode 100644
index 4e2a66a..0000000
--- a/docs/snippets/receipt-yandex-config.php
+++ /dev/null
@@ -1,9 +0,0 @@
- [
- App\Feeds\YandexFeed::class => (bool) env('FEED_YANDEX_ENABLED', true),
- ],
-];
diff --git a/docs/snippets/schedule-setup-manual.php b/docs/snippets/schedule-setup-manual.php
new file mode 100644
index 0000000..66de617
--- /dev/null
+++ b/docs/snippets/schedule-setup-manual.php
@@ -0,0 +1,20 @@
+withoutOverlapping()
+ ->runInBackground()
+ ->daily();
+
+Schedule::command(FeedGenerateCommand::class, [222])
+ ->withoutOverlapping()
+ ->runInBackground()
+ ->hourly();
+
+Schedule::call(function () {
+ // ... other action
+})->everySecond();
diff --git a/docs/snippets/schedule-setup.php b/docs/snippets/schedule-setup.php
new file mode 100644
index 0000000..1492c95
--- /dev/null
+++ b/docs/snippets/schedule-setup.php
@@ -0,0 +1,12 @@
+commands();
+
+Schedule::call(function () {
+ // ... other action
+})->everySecond();
diff --git a/docs/topics/advanced-usage.topic b/docs/topics/advanced-usage.topic
index 55b4c62..1a0d4de 100644
--- a/docs/topics/advanced-usage.topic
+++ b/docs/topics/advanced-usage.topic
@@ -24,68 +24,118 @@
-
-
+
+
+
+ To add a root element and/or its attributes, override the root method:
+
+
+
+
+
+ To disable the addition of the main element, specify its name as empty - null or
+ "".
+
+
+
+
+
+ To add information to the beginning of the root element (if present) or without it,
+ override the
+ info method in the feed class and call the creation of an object that extend the
+ DragonCode\LaravelFeed\Feeds\Info\FeedInfo class:
+
+
+
+
+
+
+
+
+ To change the header and footer, override the header and footer methods:
+
+
+
+
-
-
- In some cases, you need to add various information to the beginning of the file.
- To do this, use the info method:
-
+
+
+
+
-
-
+
+
+
-
-
-
- Add a link to the feed class in the %config-filename% file:
-
-
-
+
+
diff --git a/docs/topics/sitemap.topic b/docs/topics/receipt-sitemap.topic
similarity index 86%
rename from docs/topics/sitemap.topic
rename to docs/topics/receipt-sitemap.topic
index 9a12fb4..54e5c07 100644
--- a/docs/topics/sitemap.topic
+++ b/docs/topics/receipt-sitemap.topic
@@ -4,7 +4,7 @@
+ title="Sitemap" id="receipt-sitemap" help-id="sitemap">
Recipe for creating a sitemap
Recipe for creating a sitemap
@@ -26,12 +26,8 @@
-
-
- Add a link to the feed class in the %config-filename% file:
-
-
-
+
+
diff --git a/docs/topics/yandex.topic b/docs/topics/receipt-yandex.topic
similarity index 82%
rename from docs/topics/yandex.topic
rename to docs/topics/receipt-yandex.topic
index 6796b1e..b540e34 100644
--- a/docs/topics/yandex.topic
+++ b/docs/topics/receipt-yandex.topic
@@ -4,7 +4,7 @@
+ title="Yandex" id="receipt-yandex" help-id="yandex">
Feed generation recipe for Yandex
Feed generation recipe for Yandex
@@ -30,12 +30,8 @@
-
-
- Add a link to the feed class in the %config-filename% file:
-
-
-
+
+
diff --git a/docs/topics/snippet-generate.topic b/docs/topics/snippet-generate.topic
index 2ba7ffc..cc80a0e 100644
--- a/docs/topics/snippet-generate.topic
+++ b/docs/topics/snippet-generate.topic
@@ -16,4 +16,19 @@
%command-generate%
+
+
+
+ Check the operation/migration
+ file that was created for you and run the console command:
+
+
+
+ # For Laravel Deploy Operations
+ php artisan operations
+
+ # For Laravel Migrations
+ php artisan migrate
+
+
diff --git a/docs/v.list b/docs/v.list
index e95359b..1db1fde 100644
--- a/docs/v.list
+++ b/docs/v.list
@@ -7,14 +7,16 @@
-
+
+
+
+
+
-
@@ -22,6 +24,4 @@
-
-
diff --git a/ide.json b/ide.json
index d57bac0..0fc213c 100644
--- a/ide.json
+++ b/ide.json
@@ -2,8 +2,8 @@
"$schema": "https://laravel-ide.com/schema/laravel-ide-v2.json",
"codeGenerations": [
{
- "id": "dragon-code.xml-feeds.main",
- "name": "Create XML Feed",
+ "id": "dragon-code.feeds.main",
+ "name": "Create Feed",
"classSuffix": "Feed",
"regex": ".+",
"files": [
@@ -25,8 +25,8 @@
]
},
{
- "id": "dragon-code.xml-feeds.item",
- "name": "Create XML Feed Item",
+ "id": "dragon-code.feeds.item",
+ "name": "Create Feed Item",
"classSuffix": "FeedItem",
"regex": ".+",
"files": [
@@ -47,8 +47,8 @@
]
},
{
- "id": "dragon-code.xml-feeds.info",
- "name": "Create XML Feed Info",
+ "id": "dragon-code.feeds.info",
+ "name": "Create Feed Info",
"classSuffix": "FeedInfo",
"regex": ".+",
"files": [
diff --git a/phpunit.xml b/phpunit.xml
index 0f2a031..7ca863e 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -27,9 +27,14 @@
+
+
+
+
+
+
-
diff --git a/src/Casts/ClassCast.php b/src/Casts/ClassCast.php
new file mode 100644
index 0000000..167ac31
--- /dev/null
+++ b/src/Casts/ClassCast.php
@@ -0,0 +1,35 @@
+isValid($value)) {
+ throw new InvalidExpressionException($value);
+ }
+
+ return Str::of($value)->squish()->trim()->toString();
+ }
+
+ protected function isValid(string $value): bool
+ {
+ return CronExpression::isValidExpression($value);
+ }
+}
diff --git a/src/Console/Commands/FeedGenerateCommand.php b/src/Console/Commands/FeedGenerateCommand.php
index 5d81bb6..9495f4f 100644
--- a/src/Console/Commands/FeedGenerateCommand.php
+++ b/src/Console/Commands/FeedGenerateCommand.php
@@ -4,7 +4,8 @@
namespace DragonCode\LaravelFeed\Console\Commands;
-use DragonCode\LaravelFeed\Helpers\FeedHelper;
+use DragonCode\LaravelFeed\Exceptions\InvalidFeedArgumentException;
+use DragonCode\LaravelFeed\Queries\FeedQuery;
use DragonCode\LaravelFeed\Services\Generator;
use Illuminate\Console\Command;
use Laravel\Prompts\Concerns\Colors;
@@ -12,38 +13,37 @@
use Symfony\Component\Console\Input\InputArgument;
use function app;
-use function config;
+use function is_numeric;
#[AsCommand('feed:generate', 'Generate XML feeds')]
class FeedGenerateCommand extends Command
{
use Colors;
- public function handle(Generator $generator, FeedHelper $helper): void
+ public function handle(Generator $generator, FeedQuery $query): void
{
- foreach ($this->feedable($helper) as $feed => $enabled) {
+ foreach ($this->feedable($query) as $feed => $enabled) {
$enabled
? $this->components->task($feed, fn () => $generator->feed(app($feed)))
: $this->components->twoColumnDetail($feed, $this->messageYellow('SKIP'));
}
}
- protected function feedable(FeedHelper $helper): array
+ protected function feedable(FeedQuery $feeds): array
{
- if ($feed = $this->resolveFeedClass($helper)) {
- return [$feed => true];
+ if (! $id = $this->argument('feed')) {
+ return $feeds->all()
+ ->pluck('is_active', 'class')
+ ->all();
}
- return config('feeds.channels');
- }
-
- protected function resolveFeedClass(FeedHelper $helper): ?string
- {
- if (! $class = $this->argument('class')) {
- return null;
+ if (! is_numeric($id)) {
+ throw new InvalidFeedArgumentException($id);
}
- return $helper->find((string) $class);
+ $feed = $feeds->find((int) $id);
+
+ return [$feed->class => true];
}
protected function messageYellow(string $message): string
@@ -58,7 +58,7 @@ protected function messageYellow(string $message): string
protected function getArguments(): array
{
return [
- ['class', InputArgument::OPTIONAL, 'The feed class for generation'],
+ ['feed', InputArgument::OPTIONAL, 'The Feed ID for generation (from the database)'],
];
}
}
diff --git a/src/Console/Commands/FeedMakeCommand.php b/src/Console/Commands/FeedMakeCommand.php
index 7173a5a..cce1bd7 100644
--- a/src/Console/Commands/FeedMakeCommand.php
+++ b/src/Console/Commands/FeedMakeCommand.php
@@ -4,11 +4,18 @@
namespace DragonCode\LaravelFeed\Console\Commands;
+use DragonCode\LaravelDeployOperations\Operation;
use DragonCode\LaravelFeed\Concerns\InteractsWithName;
+use DragonCode\LaravelFeed\Helpers\ClassExistsHelper;
+use DragonCode\LaravelFeed\Publishers\MigrationPublisher;
+use DragonCode\LaravelFeed\Publishers\OperationPublisher;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
+use function app;
+use function vsprintf;
+
#[AsCommand('make:feed', 'Create a new feed')]
class FeedMakeCommand extends GeneratorCommand
{
@@ -33,6 +40,23 @@ public function handle(): void
(bool) $this->option('force')
);
}
+
+ $this->makeOperation(
+ $this->argument('name'),
+ $this->getQualifyClass()
+ );
+ }
+
+ protected function makeOperation(string $name, string $class): void
+ {
+ $publisher = $this->hasOperations()
+ ? app(OperationPublisher::class, ['title' => $name, 'class' => $class])
+ : app(MigrationPublisher::class, ['title' => $name, 'class' => $class]);
+
+ $this->components->info(vsprintf('%s [%s] created successfully.', [
+ $publisher->name(),
+ $publisher->publish(),
+ ]));
}
protected function makeFeedItem(string $name, bool $force): void
@@ -61,6 +85,16 @@ protected function getDefaultNamespace($rootNamespace): string
return $rootNamespace . '\Feeds';
}
+ protected function getQualifyClass(): string
+ {
+ return $this->qualifyClass($this->getNameInput());
+ }
+
+ protected function hasOperations(): bool
+ {
+ return app(ClassExistsHelper::class)->exists(Operation::class);
+ }
+
protected function getOptions(): array
{
return [
diff --git a/src/Services/ConvertToXml.php b/src/Converters/ConvertToXml.php
similarity index 99%
rename from src/Services/ConvertToXml.php
rename to src/Converters/ConvertToXml.php
index c0bd13a..ed6bc74 100644
--- a/src/Services/ConvertToXml.php
+++ b/src/Converters/ConvertToXml.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace DragonCode\LaravelFeed\Services;
+namespace DragonCode\LaravelFeed\Converters;
use DOMDocument;
use DOMNode;
diff --git a/src/Enums/FeedFormatEnum.php b/src/Enums/FeedFormatEnum.php
new file mode 100644
index 0000000..e8c0673
--- /dev/null
+++ b/src/Enums/FeedFormatEnum.php
@@ -0,0 +1,10 @@
+filename ??= Str::kebab(class_basename($this)) . '.xml';
+ return $this->filename ??= Str::of(static::class)
+ ->after($this->laravel->getNamespace() . 'Feeds\\')
+ ->ltrim('\\')
+ ->replace('\\', ' ')
+ ->kebab()
+ ->append('.', $this->format->value)
+ ->toString();
}
public function path(): string
diff --git a/src/Helpers/ClassExistsHelper.php b/src/Helpers/ClassExistsHelper.php
new file mode 100644
index 0000000..007f984
--- /dev/null
+++ b/src/Helpers/ClassExistsHelper.php
@@ -0,0 +1,15 @@
+ensure($class);
- }
-
- if (class_exists($class = $this->resolve($class))) {
- return $this->ensure($class);
- }
-
- throw new FeedNotFoundException($class);
- }
-
- protected function resolve(string $class): string
- {
- return Str::of($class)
- ->replace('/', '\\')
- ->ltrim('\\')
- ->start($this->rootNamespace() . 'Feeds\\')
- ->finish('Feed')
- ->toString();
- }
-
- protected function ensure(string $class): string
- {
- if (! is_a($class, Feed::class, true)) {
- throw new UnexpectedFeedException($class);
- }
-
- return $class;
- }
-
- protected function rootNamespace(): string
- {
- return $this->laravel->getNamespace();
- }
-}
diff --git a/src/Helpers/ScheduleFeedHelper.php b/src/Helpers/ScheduleFeedHelper.php
new file mode 100644
index 0000000..dfdfad0
--- /dev/null
+++ b/src/Helpers/ScheduleFeedHelper.php
@@ -0,0 +1,46 @@
+query->active()->each(
+ fn (Feed $feed) => $this->register($feed)
+ );
+ }
+
+ protected function register(Feed $feed): void
+ {
+ $event = $this->event($feed);
+
+ if ($this->canBackground) {
+ $event->runInBackground();
+ }
+ }
+
+ protected function event(Feed $feed): Event
+ {
+ return Schedule::command(FeedGenerateCommand::class, [$feed->id])
+ ->withoutOverlapping($this->ttl)
+ ->cron($feed->expression);
+ }
+}
diff --git a/src/LaravelFeedServiceProvider.php b/src/LaravelFeedServiceProvider.php
index d0ae2b8..3eda0e6 100644
--- a/src/LaravelFeedServiceProvider.php
+++ b/src/LaravelFeedServiceProvider.php
@@ -25,6 +25,7 @@ public function boot(): void
$this->registerCommands();
$this->publishConfig();
+ $this->migrations();
}
protected function publishConfig(): void
@@ -34,6 +35,13 @@ protected function publishConfig(): void
], ['config', 'feeds']);
}
+ protected function migrations(): void
+ {
+ $this->publishesMigrations([
+ __DIR__ . '/../database/migrations' => $this->app->databasePath('migrations'),
+ ], 'feeds');
+ }
+
protected function registerCommands(): void
{
$this->commands([
diff --git a/src/Models/Feed.php b/src/Models/Feed.php
new file mode 100644
index 0000000..c4a7da9
--- /dev/null
+++ b/src/Models/Feed.php
@@ -0,0 +1,56 @@
+ '* * * * *',
+
+ 'is_active' => true,
+ ];
+
+ public function getConnectionName(): ?string
+ {
+ return config('feeds.table.connection');
+ }
+
+ public function getTable(): ?string
+ {
+ return config('feeds.table.table') ?? parent::getTable();
+ }
+
+ protected function casts(): array
+ {
+ return [
+ 'class' => ClassCast::class,
+ 'expression' => ExpressionCast::class,
+
+ 'is_active' => 'boolean',
+
+ 'last_activity' => 'datetime',
+ ];
+ }
+}
diff --git a/src/Publishers/MigrationPublisher.php b/src/Publishers/MigrationPublisher.php
new file mode 100644
index 0000000..43a16af
--- /dev/null
+++ b/src/Publishers/MigrationPublisher.php
@@ -0,0 +1,20 @@
+classBasename()
+ ->before('Publisher')
+ ->toString();
+ }
+
+ public function publish(): string
+ {
+ return $this->store(
+ $this->path(),
+ $this->replace()
+ );
+ }
+
+ protected function store(string $path, string $contents): string
+ {
+ $this->filesystem->ensureDirectoryExists(dirname($path));
+ $this->filesystem->put($path, $contents);
+
+ return $path;
+ }
+
+ protected function replace(): string
+ {
+ return str_replace(
+ ['DummyClass', 'DummyBaseClass', 'DummyTitle'],
+ [$this->class, $this->baseClass(), $this->title()],
+ $this->load()
+ );
+ }
+
+ protected function baseClass(): string
+ {
+ return class_basename($this->class);
+ }
+
+ protected function title(): string
+ {
+ return Str::of($this->title)
+ ->replace(['\\', '/'], ': ')
+ ->snake(' ')
+ ->title()
+ ->toString();
+ }
+
+ protected function path(): string
+ {
+ return vsprintf('%s/%s_%s.php', [
+ $this->basePath(),
+ $this->date(),
+ $this->filename(),
+ ]);
+ }
+
+ protected function filename(): string
+ {
+ return Str::of($this->title)
+ ->snake()
+ ->prepend('create_')
+ ->append('_feed')
+ ->toString();
+ }
+
+ protected function date(): string
+ {
+ return Carbon::now()->format('Y_m_d_His');
+ }
+
+ protected function load(): string
+ {
+ return file_get_contents($this->template());
+ }
+}
diff --git a/src/Queries/FeedQuery.php b/src/Queries/FeedQuery.php
new file mode 100644
index 0000000..eeb1ca9
--- /dev/null
+++ b/src/Queries/FeedQuery.php
@@ -0,0 +1,64 @@
+ $class,
+ 'title' => $title,
+ 'expression' => $expression,
+ 'is_active' => $isActive,
+ ]);
+ }
+
+ public function find(int $id): Feed
+ {
+ return Feed::findOr($id, callback: static fn () => throw new FeedNotFoundException($id));
+ }
+
+ public function all(): Builder
+ {
+ return Feed::query()->orderBy('id');
+ }
+
+ public function active(): Builder
+ {
+ return Feed::query()
+ ->where('is_active', true)
+ ->orderBy('id');
+ }
+
+ public function setLastActivity(string $class): void
+ {
+ Feed::query()
+ ->whereClass($class)
+ ->update(['last_activity' => now()]);
+ }
+
+ public function delete(int $id): void
+ {
+ Feed::destroy($id);
+ }
+
+ public function restore(int $id): void
+ {
+ Feed::query()
+ ->whereId($id)
+ ->restore();
+ }
+}
diff --git a/src/Services/Filesystem.php b/src/Services/Filesystem.php
index bff317d..f7bcb4d 100644
--- a/src/Services/Filesystem.php
+++ b/src/Services/Filesystem.php
@@ -4,12 +4,16 @@
namespace DragonCode\LaravelFeed\Services;
+use DragonCode\LaravelFeed\Exceptions\OpenFeedException;
+use DragonCode\LaravelFeed\Exceptions\WriteFeedException;
use Illuminate\Filesystem\Filesystem as File;
+use function blank;
use function dirname;
use function fclose;
use function fopen;
use function fwrite;
+use function is_resource;
class Filesystem
{
@@ -27,13 +31,26 @@ public function open(string $path)
$this->ensureFileDelete($path);
$this->ensureDirectory($path);
- return fopen($path, 'ab');
+ $resource = fopen($path, 'ab');
+
+ if ($resource === false) {
+ throw new OpenFeedException($path);
+ }
+
+ return $resource;
}
- public function append($resource, string $content): void
+ /**
+ * @param resource $resource
+ */
+ public function append($resource, string $content, string $path): void
{
- if (! empty($content)) {
- fwrite($resource, $content);
+ if (blank($content)) {
+ return;
+ }
+
+ if (fwrite($resource, $content) === false) {
+ throw new WriteFeedException($path);
}
}
@@ -59,6 +76,10 @@ public function release($resource, string $path): void
*/
public function close($resource): void
{
+ if (! is_resource($resource)) {
+ return;
+ }
+
fclose($resource);
}
diff --git a/src/Services/Generator.php b/src/Services/Generator.php
index 962335a..25ebea6 100644
--- a/src/Services/Generator.php
+++ b/src/Services/Generator.php
@@ -4,12 +4,15 @@
namespace DragonCode\LaravelFeed\Services;
+use DragonCode\LaravelFeed\Converters\ConvertToXml;
use DragonCode\LaravelFeed\Data\ElementData;
use DragonCode\LaravelFeed\Feeds\Feed;
+use DragonCode\LaravelFeed\Queries\FeedQuery;
use Illuminate\Database\Eloquent\Collection;
use function blank;
use function collect;
+use function get_class;
use function implode;
use function sprintf;
@@ -18,6 +21,7 @@ class Generator
public function __construct(
protected Filesystem $filesystem,
protected ConvertToXml $converter,
+ protected FeedQuery $query,
) {}
public function feed(Feed $feed): void
@@ -33,6 +37,8 @@ public function feed(Feed $feed): void
$this->performFooter($file, $feed);
$this->release($file, $path);
+
+ $this->setLastActivity($feed);
}
protected function performItem($file, Feed $feed): void
@@ -46,13 +52,13 @@ protected function performItem($file, Feed $feed): void
);
}
- $this->append($file, implode(PHP_EOL, $content));
+ $this->append($file, implode(PHP_EOL, $content), $feed->path());
});
}
protected function performHeader($file, Feed $feed): void
{
- $this->append($file, $feed->header());
+ $this->append($file, $feed->header(), $feed->path());
}
protected function performInfo($file, Feed $feed): void
@@ -63,7 +69,7 @@ protected function performInfo($file, Feed $feed): void
$value = $this->converter->convertInfo($info);
- $this->append($file, PHP_EOL . $value);
+ $this->append($file, PHP_EOL . $value, $feed->path());
}
protected function performRoot($file, Feed $feed): void
@@ -76,7 +82,7 @@ protected function performRoot($file, Feed $feed): void
? sprintf("\n<%s %s>\n", $name, $this->makeRootAttributes($feed->root()))
: sprintf("\n<%s>\n", $name);
- $this->append($file, $value);
+ $this->append($file, $value, $feed->path());
}
protected function performFooter($file, Feed $feed): void
@@ -87,7 +93,9 @@ protected function performFooter($file, Feed $feed): void
$value .= "\n$name>\n";
}
- $this->append($file, $value . $feed->footer());
+ $value .= $feed->footer();
+
+ $this->append($file, $value, $feed->path());
}
protected function makeRootAttributes(ElementData $item): string
@@ -97,9 +105,9 @@ protected function makeRootAttributes(ElementData $item): string
->implode(' ');
}
- protected function append($file, string $content): void
+ protected function append($file, string $content, string $path): void
{
- $this->filesystem->append($file, $content);
+ $this->filesystem->append($file, $content, $path);
}
protected function release($file, string $path): void
@@ -111,4 +119,11 @@ protected function openFile(string $path)
{
return $this->filesystem->open($path);
}
+
+ protected function setLastActivity(Feed $feed): void
+ {
+ $this->query->setLastActivity(
+ get_class($feed)
+ );
+ }
}
diff --git a/stubs/feed_activate_migration.stub b/stubs/feed_activate_migration.stub
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/feed_activate_operation.stub b/stubs/feed_activate_operation.stub
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/migration.stub b/stubs/migration.stub
new file mode 100644
index 0000000..2d91f28
--- /dev/null
+++ b/stubs/migration.stub
@@ -0,0 +1,20 @@
+create(
+ class : DummyBaseClass::class,
+ title : 'DummyTitle',
+ expression: '* * * * *'
+ );
+ }
+};
diff --git a/stubs/operation.stub b/stubs/operation.stub
new file mode 100644
index 0000000..eabdbab
--- /dev/null
+++ b/stubs/operation.stub
@@ -0,0 +1,23 @@
+create(
+ class : DummyBaseClass::class,
+ title : 'DummyTitle',
+ expression: '* * * * *'
+ );
+ }
+
+ public function withinTransactions(): bool
+ {
+ return false;
+ }
+};
diff --git a/testbench.yaml b/testbench.yaml
index ada5349..e0ad071 100644
--- a/testbench.yaml
+++ b/testbench.yaml
@@ -1,10 +1,13 @@
laravel: '@testbench'
providers:
- - DragonCode\LaravelFeed\LaravelFeedServiceProvider
- Workbench\App\Providers\WorkbenchServiceProvider
+ - DragonCode\LaravelFeed\LaravelFeedServiceProvider
+ - Spatie\LaravelData\LaravelDataServiceProvider
+ - DragonCode\LaravelDeployOperations\ServiceProvider
migrations:
+ - database/migrations
- workbench/database/migrations
workbench:
@@ -20,3 +23,7 @@ workbench:
- create-sqlite-db
- db-wipe
- migrate-fresh
+
+env:
+ CACHE_DRIVER: array
+ CACHE_STORE: array
diff --git a/tests/.pest/snapshots/Unit/Console/MakeTest/make_feed.snap b/tests/.pest/snapshots/Unit/Console/Make/FeedTest/make_feed.snap
similarity index 100%
rename from tests/.pest/snapshots/Unit/Console/MakeTest/make_feed.snap
rename to tests/.pest/snapshots/Unit/Console/Make/FeedTest/make_feed.snap
diff --git a/tests/.pest/snapshots/Unit/Console/MakeTest/make_with_info.snap b/tests/.pest/snapshots/Unit/Console/Make/InfoTest/make_with_info.snap
similarity index 100%
rename from tests/.pest/snapshots/Unit/Console/MakeTest/make_with_info.snap
rename to tests/.pest/snapshots/Unit/Console/Make/InfoTest/make_with_info.snap
diff --git a/tests/.pest/snapshots/Unit/Console/MakeTest/make_with_info__2.snap b/tests/.pest/snapshots/Unit/Console/Make/InfoTest/make_with_info__2.snap
similarity index 100%
rename from tests/.pest/snapshots/Unit/Console/MakeTest/make_with_info__2.snap
rename to tests/.pest/snapshots/Unit/Console/Make/InfoTest/make_with_info__2.snap
diff --git a/tests/.pest/snapshots/Unit/Console/MakeTest/make_with_item.snap b/tests/.pest/snapshots/Unit/Console/Make/ItemTest/make_with_item.snap
similarity index 100%
rename from tests/.pest/snapshots/Unit/Console/MakeTest/make_with_item.snap
rename to tests/.pest/snapshots/Unit/Console/Make/ItemTest/make_with_item.snap
diff --git a/tests/.pest/snapshots/Unit/Console/MakeTest/make_with_item__2.snap b/tests/.pest/snapshots/Unit/Console/Make/ItemTest/make_with_item__2.snap
similarity index 100%
rename from tests/.pest/snapshots/Unit/Console/MakeTest/make_with_item__2.snap
rename to tests/.pest/snapshots/Unit/Console/Make/ItemTest/make_with_item__2.snap
diff --git a/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_EmptyFeed___.snap b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_EmptyFeed___.snap
new file mode 100644
index 0000000..3eef6fb
--- /dev/null
+++ b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_EmptyFeed___.snap
@@ -0,0 +1 @@
+empty-feed.xml
\ No newline at end of file
diff --git a/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_FullFeed___.snap b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_FullFeed___.snap
new file mode 100644
index 0000000..f7bbd67
--- /dev/null
+++ b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_FullFeed___.snap
@@ -0,0 +1 @@
+nested/full.xml
\ No newline at end of file
diff --git a/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_PartialFeed___.snap b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_PartialFeed___.snap
new file mode 100644
index 0000000..f6cded8
--- /dev/null
+++ b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_PartialFeed___.snap
@@ -0,0 +1 @@
+partial-feed.xml
\ No newline at end of file
diff --git a/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_SitemapFeed___.snap b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_SitemapFeed___.snap
new file mode 100644
index 0000000..e5f01af
--- /dev/null
+++ b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_SitemapFeed___.snap
@@ -0,0 +1 @@
+sitemaps/products.xml
\ No newline at end of file
diff --git a/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_YandexFeed___.snap b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_YandexFeed___.snap
new file mode 100644
index 0000000..7e66387
--- /dev/null
+++ b/tests/.pest/snapshots/Unit/Feeds/FilenameTest/filename_with_data_set_____Workbench_App_Feeds_YandexFeed___.snap
@@ -0,0 +1 @@
+yandex.xml
\ No newline at end of file
diff --git a/tests/Expectations.php b/tests/Expectations.php
index 2ff9cef..26d9f96 100644
--- a/tests/Expectations.php
+++ b/tests/Expectations.php
@@ -2,26 +2,42 @@
declare(strict_types=1);
-expect()->extend('toMatchFeedSnapshot', function () {
- $content = file_get_contents(feedPath($this->value . 'Feed'));
+expect()->extend('toMatchFileSnapshot', function () {
+ $content = file_get_contents($this->value);
expect($content)->toMatchSnapshot();
return $this;
});
+expect()->extend('toMatchFeedSnapshot', function () {
+ $path = feedPath($this->value . 'Feed');
+
+ expect($path)->toMatchFileSnapshot();
+
+ return $this;
+});
+
expect()->extend('toMatchFeedItemSnapshot', function () {
- $content = file_get_contents(feedPath('Items/' . $this->value . 'FeedItem'));
+ $path = feedPath('Items/' . $this->value . 'FeedItem');
- expect($content)->toMatchSnapshot();
+ expect($path)->toMatchFileSnapshot();
return $this;
});
expect()->extend('toMatchFeedInfoSnapshot', function () {
- $content = file_get_contents(feedPath('Info/' . $this->value . 'FeedInfo'));
+ $path = feedPath('Info/' . $this->value . 'FeedInfo');
- expect($content)->toMatchSnapshot();
+ expect($path)->toMatchFileSnapshot();
+
+ return $this;
+});
+
+expect()->extend('toMatchGeneratedFeed', function () {
+ $path = app($this->value->class)->path();
+
+ expect($path)->toBeFile();
return $this;
});
diff --git a/tests/Feature/Console/Generation/DefaultTest.php b/tests/Feature/Console/Generation/DefaultTest.php
index 08dc4ed..24aac4f 100644
--- a/tests/Feature/Console/Generation/DefaultTest.php
+++ b/tests/Feature/Console/Generation/DefaultTest.php
@@ -3,21 +3,20 @@
declare(strict_types=1);
use DragonCode\LaravelFeed\Console\Commands\FeedGenerateCommand;
+use DragonCode\LaravelFeed\Models\Feed;
use function Pest\Laravel\artisan;
test('generate', function () {
$command = artisan(FeedGenerateCommand::class);
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => $command->expectsOutputToContain($feed));
+ getAllFeeds()->each(
+ fn (Feed $feed) => $command->expectsOutputToContain($feed->class)
+ );
$command->assertSuccessful()->run();
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => expect(app($feed)->path())->toBeReadableFile());
+ getAllFeeds()->each(
+ fn (Feed $feed) => expect($feed)->toMatchGeneratedFeed()
+ );
});
diff --git a/tests/Feature/Console/Generation/DisabledTest.php b/tests/Feature/Console/Generation/DisabledTest.php
index 9fa0476..b48bb86 100644
--- a/tests/Feature/Console/Generation/DisabledTest.php
+++ b/tests/Feature/Console/Generation/DisabledTest.php
@@ -3,30 +3,31 @@
declare(strict_types=1);
use DragonCode\LaravelFeed\Console\Commands\FeedGenerateCommand;
+use DragonCode\LaravelFeed\Models\Feed;
use Workbench\App\Feeds\SitemapFeed;
use Workbench\App\Feeds\YandexFeed;
use function Pest\Laravel\artisan;
test('generate', function () {
- config()?->set('feeds.channels.' . SitemapFeed::class, false);
- config()?->set('feeds.channels.' . YandexFeed::class, false);
+ disableFeeds([
+ SitemapFeed::class,
+ YandexFeed::class,
+ ]);
$command = artisan(FeedGenerateCommand::class);
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => $command->expectsOutputToContain($feed));
+ getAllFeeds()->each(
+ fn (Feed $feed) => $command->expectsOutputToContain($feed->class)
+ );
$command->assertSuccessful()->run();
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => match ($feed) {
+ getAllFeeds()->each(
+ fn (Feed $feed) => match ($feed->class) {
SitemapFeed::class,
- YandexFeed::class => expect(app($feed)->path())->not->toBeReadableFile(),
- default => expect(app($feed)->path())->toBeReadableFile()
- });
+ YandexFeed::class => expect($feed)->not->toMatchGeneratedFeed(),
+ default => expect($feed)->toMatchGeneratedFeed()
+ }
+ );
});
diff --git a/tests/Feature/Console/Generation/FoundTest.php b/tests/Feature/Console/Generation/FoundTest.php
new file mode 100644
index 0000000..8b65b54
--- /dev/null
+++ b/tests/Feature/Console/Generation/FoundTest.php
@@ -0,0 +1,31 @@
+ $id,
+ ]);
+
+ getAllFeeds()->each(
+ fn (Feed $feed) => $id === $feed->id
+ ? $command->expectsOutputToContain($feed->class)
+ : $command->doesntExpectOutputToContain($feed->class)
+ );
+
+ $command->assertSuccessful()->run();
+
+ getAllFeeds()->each(
+ fn (Feed $feed) => $id === $feed->id
+ ? expect($feed)->toMatchGeneratedFeed()
+ : expect($feed)->not->toMatchGeneratedFeed()
+ );
+})->with([
+ fn () => Feed::query()->latest()->first()->id,
+ fn () => Feed::query()->oldest()->first()->id,
+]);
diff --git a/tests/Feature/Console/Generation/IncorrectParameterTest.php b/tests/Feature/Console/Generation/IncorrectParameterTest.php
index fe48160..a3937ee 100644
--- a/tests/Feature/Console/Generation/IncorrectParameterTest.php
+++ b/tests/Feature/Console/Generation/IncorrectParameterTest.php
@@ -3,42 +3,21 @@
declare(strict_types=1);
use DragonCode\LaravelFeed\Console\Commands\FeedGenerateCommand;
-use DragonCode\LaravelFeed\Exceptions\FeedNotFoundException;
+use DragonCode\LaravelFeed\Exceptions\InvalidFeedArgumentException;
use function Pest\Laravel\artisan;
-test('incorrect', function (mixed $name) {
+test('incorrect', function (mixed $id) {
artisan(FeedGenerateCommand::class, [
- 'class' => $name,
+ 'feed' => $id,
])->run();
})
- ->throws(FeedNotFoundException::class)
+ ->throws(InvalidFeedArgumentException::class, 'Feed ID must be of type integer, [string] given.')
->with([
- 'foo=bar',
- 'foo+bar',
'foo bar',
- '123',
- 123,
+ '+',
+ '-',
+ '/',
+ '\\',
+ '_',
]);
-
-test('may be correct', function (mixed $name) {
- $command = artisan(FeedGenerateCommand::class, [
- 'class' => $name,
- ]);
-
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => $command->expectsOutputToContain($feed));
-
- $command->assertSuccessful()->run();
-
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => expect(app($feed)->path())->toBeReadableFile());
-})->with([
- '',
- 0,
- null,
-]);
diff --git a/tests/Feature/Console/Generation/NotFoundTest.php b/tests/Feature/Console/Generation/NotFoundTest.php
new file mode 100644
index 0000000..58bc0d7
--- /dev/null
+++ b/tests/Feature/Console/Generation/NotFoundTest.php
@@ -0,0 +1,14 @@
+ 123,
+ ])->run();
+})->throws(FeedNotFoundException::class, 'Feed [123] not found.');
diff --git a/tests/Feature/Console/Generation/SpecifiedTest.php b/tests/Feature/Console/Generation/SpecifiedTest.php
index 7649a00..205f707 100644
--- a/tests/Feature/Console/Generation/SpecifiedTest.php
+++ b/tests/Feature/Console/Generation/SpecifiedTest.php
@@ -3,32 +3,29 @@
declare(strict_types=1);
use DragonCode\LaravelFeed\Console\Commands\FeedGenerateCommand;
+use DragonCode\LaravelFeed\Models\Feed;
use Workbench\App\Feeds\SitemapFeed;
use function Pest\Laravel\artisan;
test('generate', function () {
+ $source = findFeed(SitemapFeed::class);
+
$command = artisan(FeedGenerateCommand::class, [
- 'class' => SitemapFeed::class,
+ 'feed' => $source->id,
]);
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(
- fn (string $feed) => $feed === SitemapFeed::class
- ? $command->expectsOutputToContain($feed)
- : $command->doesntExpectOutputToContain($feed)
- );
+ getAllFeeds()->each(
+ fn (Feed $feed) => $source->id === $feed->id
+ ? $command->expectsOutputToContain($feed->class)
+ : $command->doesntExpectOutputToContain($feed->class)
+ );
$command->assertSuccessful()->run();
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(
- fn (string $feed) => $feed === SitemapFeed::class
- ? expect(app($feed)->path())->toBeReadableFile()
- : expect(app($feed)->path())->not->toBeReadableFile()
- );
+ getAllFeeds()->each(
+ fn (Feed $feed) => $source->id === $feed->id
+ ? expect($feed)->toMatchGeneratedFeed()
+ : expect($feed)->not->toMatchGeneratedFeed()
+ );
});
diff --git a/tests/Feature/Console/Generation/UnknownClassTest.php b/tests/Feature/Console/Generation/UnknownClassTest.php
deleted file mode 100644
index 5951a9a..0000000
--- a/tests/Feature/Console/Generation/UnknownClassTest.php
+++ /dev/null
@@ -1,26 +0,0 @@
- $feed,
- ])->run();
-})
- ->throws(UnexpectedFeedException::class)
- ->with([
- TestCase::class,
- WorkbenchServiceProvider::class,
-
- YandexFeedItem::class,
- YandexFeedInfo::class,
- ]);
diff --git a/tests/Feature/Feeds/EmptyTest.php b/tests/Feature/Feeds/EmptyTest.php
index 8d5e406..931f9f8 100644
--- a/tests/Feature/Feeds/EmptyTest.php
+++ b/tests/Feature/Feeds/EmptyTest.php
@@ -7,5 +7,5 @@
test('export', function (bool $pretty) {
setPrettyXml($pretty);
- expectFeed(EmptyFeed::class);
+ expectFeedSnapshot(EmptyFeed::class);
})->with('boolean');
diff --git a/tests/Feature/Feeds/FullTest.php b/tests/Feature/Feeds/FullTest.php
index d3df498..204dfef 100644
--- a/tests/Feature/Feeds/FullTest.php
+++ b/tests/Feature/Feeds/FullTest.php
@@ -10,5 +10,5 @@
createNews(...NewsFakeData::toArray());
- expectFeed(FullFeed::class);
+ expectFeedSnapshot(FullFeed::class);
})->with('boolean');
diff --git a/tests/Feature/Feeds/PartialTest.php b/tests/Feature/Feeds/PartialTest.php
index 27a2b6e..aa0b0ca 100644
--- a/tests/Feature/Feeds/PartialTest.php
+++ b/tests/Feature/Feeds/PartialTest.php
@@ -14,5 +14,5 @@
createNews(...NewsFakeData::toArray());
- expectFeed(PartialFeed::class);
+ expectFeedSnapshot(PartialFeed::class);
})->with('boolean');
diff --git a/tests/Feature/Feeds/SitemapTest.php b/tests/Feature/Feeds/SitemapTest.php
index 6cc3914..15e6a82 100644
--- a/tests/Feature/Feeds/SitemapTest.php
+++ b/tests/Feature/Feeds/SitemapTest.php
@@ -7,5 +7,5 @@
test('export', function () {
createProducts();
- expectFeed(SitemapFeed::class);
+ expectFeedSnapshot(SitemapFeed::class);
});
diff --git a/tests/Feature/Feeds/YandexTest.php b/tests/Feature/Feeds/YandexTest.php
index e7b77f9..00ad370 100644
--- a/tests/Feature/Feeds/YandexTest.php
+++ b/tests/Feature/Feeds/YandexTest.php
@@ -7,5 +7,5 @@
test('export', function () {
createProducts();
- expectFeed(YandexFeed::class);
+ expectFeedSnapshot(YandexFeed::class);
});
diff --git a/tests/Feature/Queries/Create/ClassTest.php b/tests/Feature/Queries/Create/ClassTest.php
new file mode 100644
index 0000000..d4068ba
--- /dev/null
+++ b/tests/Feature/Queries/Create/ClassTest.php
@@ -0,0 +1,23 @@
+create(
+ class : EmptyFeed::class,
+ title : 'Some',
+ expression: $value
+ );
+})->throws(
+ exception: InvalidExpressionException::class,
+)->with([
+ 'foo',
+ '123',
+ 'foo 1 2 3',
+ '* * * * * *',
+ '* * *',
+]);
diff --git a/tests/Feature/Queries/Create/ExpressionTest.php b/tests/Feature/Queries/Create/ExpressionTest.php
new file mode 100644
index 0000000..7c9dc20
--- /dev/null
+++ b/tests/Feature/Queries/Create/ExpressionTest.php
@@ -0,0 +1,29 @@
+create(
+ class: 'foo',
+ title: 'Some',
+ );
+})->throws(
+ exception : UnexpectedClassException::class,
+ exceptionMessage: 'Class [foo] does not exist.'
+);
+
+test('not extending', function () {
+ app(FeedQuery::class)->create(
+ class: ProductFakeData::class,
+ title: 'Some',
+ );
+})->throws(
+ exception : UnknownFeedClassException::class,
+ exceptionMessage: sprintf('The [%s] class must extend from the %s class.', ProductFakeData::class, Feed::class)
+);
diff --git a/tests/Feature/Queries/Create/LastActivity.php b/tests/Feature/Queries/Create/LastActivity.php
new file mode 100644
index 0000000..06fbe97
--- /dev/null
+++ b/tests/Feature/Queries/Create/LastActivity.php
@@ -0,0 +1,35 @@
+ $feed->id,
+
+ 'last_activity' => null,
+ ]);
+
+ artisan(FeedGenerateCommand::class)
+ ->expectsOutputToContain($feed->class)
+ ->assertSuccessful()
+ ->run();
+
+ assertDatabaseMissing(Feed::class, [
+ 'id' => $feed->id,
+
+ 'last_activity' => null,
+ ]);
+
+ $feed->refresh();
+
+ expect($feed->last_activity)->toDateTime();
+});
diff --git a/tests/Feature/Queries/Create/SuccessTest.php b/tests/Feature/Queries/Create/SuccessTest.php
new file mode 100644
index 0000000..18fce60
--- /dev/null
+++ b/tests/Feature/Queries/Create/SuccessTest.php
@@ -0,0 +1,24 @@
+forceDelete();
+
+ $feed = app(FeedQuery::class)->create(
+ class : EmptyFeed::class,
+ title : 'Some',
+ expression: '*/15 */2 * 1 *'
+ );
+
+ expect($feed)
+ ->class->toBe(EmptyFeed::class)
+ ->title->toBe('Some')
+ ->expression->toBe('*/15 */2 * 1 *')
+ ->is_active->toBeTrue()
+ ->last_activity->toBeNull();
+});
diff --git a/tests/Helpers/cleanup.php b/tests/Helpers/cleanup.php
index e2216db..89c1d95 100644
--- a/tests/Helpers/cleanup.php
+++ b/tests/Helpers/cleanup.php
@@ -3,26 +3,20 @@
declare(strict_types=1);
use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Facades\ParallelTesting;
-function deleteFile(string $filename): void
+function deleteOperations(): void
{
- app(Filesystem::class)->delete(
- $filename
+ (new Filesystem)->deleteDirectory(
+ config('deploy-operations.path')
);
}
-function deleteFeed(string $feedName): void
+function deleteMigrations(): void
{
- $path = app_path(
- feedPath($feedName)
- );
-
- deleteFile($path);
-}
+ $token = ParallelTesting::token() ?: '0';
-function deleteFeedResult(string $feedClass): void
-{
- deleteFile(
- app($feedClass)->path()
+ (new Filesystem)->deleteDirectory(
+ database_path($token)
);
}
diff --git a/tests/Helpers/expects.php b/tests/Helpers/expects.php
index 0e9f63a..19ff160 100644
--- a/tests/Helpers/expects.php
+++ b/tests/Helpers/expects.php
@@ -6,17 +6,16 @@
use function Pest\Laravel\artisan;
-/**
- * @param class-string $feed
- */
-function expectFeed(string $feed): void
+function expectFeedSnapshot(string $class): void
{
- $instance = app($feed);
+ $feed = findFeed($class);
+
+ $instance = app($feed->class);
artisan(FeedGenerateCommand::class, [
- 'class' => $feed,
+ 'feed' => $feed->id,
])->assertSuccessful()->run();
- expect($instance->path())->toBeReadableFile();
+ expect($instance->path())->toBeFile();
expect(file_get_contents($instance->path()))->toMatchSnapshot();
}
diff --git a/tests/Helpers/feeds.php b/tests/Helpers/feeds.php
new file mode 100644
index 0000000..2ecdb1b
--- /dev/null
+++ b/tests/Helpers/feeds.php
@@ -0,0 +1,26 @@
+whereIn('class', Arr::wrap($classes))
+ ->update(['is_active' => false]);
+}
+
+function getAllFeeds(): Collection
+{
+ return Feed::get();
+}
+
+function findFeed(string $class): Feed
+{
+ return Feed::query()
+ ->whereClass($class)
+ ->firstOrFail();
+}
diff --git a/tests/Helpers/mocks.php b/tests/Helpers/mocks.php
new file mode 100644
index 0000000..2d3c8e3
--- /dev/null
+++ b/tests/Helpers/mocks.php
@@ -0,0 +1,30 @@
+forgetInstance(ClassExistsHelper::class);
+
+ app()->singleton(ClassExistsHelper::class, function () use ($installed) {
+ $mock = mock(ClassExistsHelper::class);
+ $mock->shouldReceive('exists')->andReturn($installed);
+
+ return $mock;
+ });
+}
+
+function mockPaths(): void
+{
+ $token = ParallelTesting::token() ?: '0';
+
+ $operations = config('deploy-operations.path');
+ $migrations = database_path($token);
+
+ config()?->set('deploy-operations.path', $operations . '/' . $token);
+
+ app()->useDatabasePath($migrations);
+}
diff --git a/tests/Pest.php b/tests/Pest.php
index fa9a81d..47a69f5 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -3,11 +3,8 @@
declare(strict_types=1);
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Carbon;
use Tests\TestCase;
-use Workbench\App\Feeds\FullFeed;
-use Workbench\App\Feeds\PartialFeed;
-use Workbench\App\Feeds\SitemapFeed;
-use Workbench\App\Feeds\YandexFeed;
pest()
->printer()
@@ -16,24 +13,32 @@
pest()
->extend(TestCase::class)
->use(RefreshDatabase::class)
- ->in('Feature');
+ ->in('Feature')
+ ->beforeEach(function () {
+ mockOperations();
+ mockPaths();
-pest()
- ->extend(TestCase::class)
- ->in('Unit');
+ deleteOperations();
+ deleteMigrations();
+ })
+ ->afterEach(function () {
+ deleteOperations();
+ deleteMigrations();
+ });
pest()
- ->in('Feature/Console/Generation')
+ ->extend(TestCase::class)
+ ->in('Unit')
->beforeEach(function () {
- config()?->set('feeds.channels', [
- FullFeed::class => true,
- PartialFeed::class => true,
- SitemapFeed::class => true,
- YandexFeed::class => true,
- ]);
-
- config()
- ?->collection('feeds.channels')
- ?->keys()
- ?->each(fn (string $feed) => deleteFeedResult($feed));
+ Carbon::setTestNow('2025-09-03 01:50:24');
+
+ mockOperations();
+ mockPaths();
+
+ deleteOperations();
+ deleteMigrations();
+ })
+ ->afterEach(function () {
+ deleteOperations();
+ deleteMigrations();
});
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 42bf8ab..0f835b2 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -6,8 +6,11 @@
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase as BaseTestCase;
+use Workbench\Database\Seeders\DatabaseSeeder;
class TestCase extends BaseTestCase
{
use WithWorkbench;
+
+ public string $seeder = DatabaseSeeder::class;
}
diff --git a/tests/Unit/Console/Make/FeedTest.php b/tests/Unit/Console/Make/FeedTest.php
new file mode 100644
index 0000000..785ec15
--- /dev/null
+++ b/tests/Unit/Console/Make/FeedTest.php
@@ -0,0 +1,21 @@
+ 'FooBar',
+ '--force' => true,
+ ])
+ ->expectsOutputToContain(resolvePath('app/Feeds/FooBarFeed.php] created successfully'))
+ ->doesntExpectOutputToContain(resolvePath('app/Feeds/Items'))
+ ->doesntExpectOutputToContain(resolvePath('app/Feeds/Info'))
+ ->assertSuccessful()
+ ->run();
+
+ expect('FooBar')->toMatchFeedSnapshot();
+});
diff --git a/tests/Unit/Console/Make/InfoTest.php b/tests/Unit/Console/Make/InfoTest.php
new file mode 100644
index 0000000..f7e0274
--- /dev/null
+++ b/tests/Unit/Console/Make/InfoTest.php
@@ -0,0 +1,24 @@
+ 'QweRty',
+ '--info' => true,
+ '--force' => true,
+ ])
+ ->expectsOutputToContain(resolvePath('app/Feeds/QweRtyFeed.php] created successfully'))
+ ->doesntExpectOutputToContain(resolvePath('app/Feeds/Items/QweRtyFeedItem.php] created successfully'))
+ ->expectsOutputToContain(resolvePath('app/Feeds/Info/QweRtyFeedInfo'))
+ ->assertSuccessful()
+ ->run();
+
+ expect('QweRty')
+ ->toMatchFeedSnapshot()
+ ->toMatchFeedInfoSnapshot();
+});
diff --git a/tests/Unit/Console/Make/ItemTest.php b/tests/Unit/Console/Make/ItemTest.php
new file mode 100644
index 0000000..56fa99a
--- /dev/null
+++ b/tests/Unit/Console/Make/ItemTest.php
@@ -0,0 +1,24 @@
+ 'QweRty',
+ '--item' => true,
+ '--force' => true,
+ ])
+ ->expectsOutputToContain(resolvePath('app/Feeds/QweRtyFeed.php] created successfully'))
+ ->expectsOutputToContain(resolvePath('app/Feeds/Items/QweRtyFeedItem.php] created successfully'))
+ ->doesntExpectOutputToContain(resolvePath('app/Feeds/Info'))
+ ->assertSuccessful()
+ ->run();
+
+ expect('QweRty')
+ ->toMatchFeedSnapshot()
+ ->toMatchFeedItemSnapshot();
+});
diff --git a/tests/Unit/Console/Make/MigrationTest.php b/tests/Unit/Console/Make/MigrationTest.php
new file mode 100644
index 0000000..b51c3e7
--- /dev/null
+++ b/tests/Unit/Console/Make/MigrationTest.php
@@ -0,0 +1,21 @@
+ 'FooBar',
+ '--force' => true,
+ ])
+ ->expectsOutputToContain(resolvePath('app/Feeds/FooBarFeed.php] created successfully'))
+ ->doesntExpectOutputToContain('Operation')
+ ->expectsOutputToContain('Migration')
+ ->assertSuccessful()
+ ->run();
+});
diff --git a/tests/Unit/Console/Make/OperationTest.php b/tests/Unit/Console/Make/OperationTest.php
new file mode 100644
index 0000000..5afb3e2
--- /dev/null
+++ b/tests/Unit/Console/Make/OperationTest.php
@@ -0,0 +1,19 @@
+ 'FooBar',
+ '--force' => true,
+ ])
+ ->expectsOutputToContain(resolvePath('app/Feeds/FooBarFeed.php] created successfully'))
+ ->expectsOutputToContain('Operation')
+ ->doesntExpectOutputToContain('Migration')
+ ->assertSuccessful()
+ ->run();
+});
diff --git a/tests/Unit/Console/MakeInfoTest.php b/tests/Unit/Console/MakeInfoTest.php
index 109dee2..51b4307 100644
--- a/tests/Unit/Console/MakeInfoTest.php
+++ b/tests/Unit/Console/MakeInfoTest.php
@@ -7,8 +7,6 @@
use function Pest\Laravel\artisan;
test('make feed item', function () {
- deleteFeed('Info/FooBar');
-
artisan(FeedInfoMakeCommand::class, [
'name' => 'FooBar',
'--force' => true,
diff --git a/tests/Unit/Console/MakeItemTest.php b/tests/Unit/Console/MakeItemTest.php
index 0f9e3f5..12da122 100644
--- a/tests/Unit/Console/MakeItemTest.php
+++ b/tests/Unit/Console/MakeItemTest.php
@@ -7,8 +7,6 @@
use function Pest\Laravel\artisan;
test('make feed item', function () {
- deleteFeed('Items/FooBar');
-
artisan(FeedItemMakeCommand::class, [
'name' => 'FooBar',
'--force' => true,
diff --git a/tests/Unit/Console/MakeTest.php b/tests/Unit/Console/MakeTest.php
deleted file mode 100644
index 2aaf173..0000000
--- a/tests/Unit/Console/MakeTest.php
+++ /dev/null
@@ -1,63 +0,0 @@
- 'FooBar',
- '--force' => true,
- ])
- ->expectsOutputToContain(resolvePath('app/Feeds/FooBarFeed.php] created successfully'))
- ->doesntExpectOutputToContain(resolvePath('app/Feeds/Items'))
- ->doesntExpectOutputToContain(resolvePath('app/Feeds/Info'))
- ->assertSuccessful()
- ->run();
-
- expect('FooBar')->toMatchFeedSnapshot();
-});
-
-test('make with item', function () {
- deleteFeed('QweRty');
- deleteFeed('Items/QweRty');
-
- artisan(FeedMakeCommand::class, [
- 'name' => 'QweRty',
- '--item' => true,
- '--force' => true,
- ])
- ->expectsOutputToContain(resolvePath('app/Feeds/QweRtyFeed.php] created successfully'))
- ->expectsOutputToContain(resolvePath('app/Feeds/Items/QweRtyFeedItem.php] created successfully'))
- ->doesntExpectOutputToContain(resolvePath('app/Feeds/Info'))
- ->assertSuccessful()
- ->run();
-
- expect('QweRty')
- ->toMatchFeedSnapshot()
- ->toMatchFeedItemSnapshot();
-});
-
-test('make with info', function () {
- deleteFeed('QweRty');
- deleteFeed('Items/QweRty');
-
- artisan(FeedMakeCommand::class, [
- 'name' => 'QweRty',
- '--info' => true,
- '--force' => true,
- ])
- ->expectsOutputToContain(resolvePath('app/Feeds/QweRtyFeed.php] created successfully'))
- ->doesntExpectOutputToContain(resolvePath('app/Feeds/Items/QweRtyFeedItem.php] created successfully'))
- ->expectsOutputToContain(resolvePath('app/Feeds/Info/QweRtyFeedInfo'))
- ->assertSuccessful()
- ->run();
-
- expect('QweRty')
- ->toMatchFeedSnapshot()
- ->toMatchFeedInfoSnapshot();
-});
diff --git a/tests/Unit/Feeds/FilenameTest.php b/tests/Unit/Feeds/FilenameTest.php
new file mode 100644
index 0000000..4bd2afb
--- /dev/null
+++ b/tests/Unit/Feeds/FilenameTest.php
@@ -0,0 +1,23 @@
+filename())->toMatchSnapshot();
+})->with([
+ EmptyFeed::class,
+ FullFeed::class,
+ PartialFeed::class,
+ SitemapFeed::class,
+ YandexFeed::class,
+]);
diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php
index 2682ce9..2f760f6 100644
--- a/workbench/app/Providers/WorkbenchServiceProvider.php
+++ b/workbench/app/Providers/WorkbenchServiceProvider.php
@@ -4,27 +4,23 @@
namespace Workbench\App\Providers;
-use Illuminate\Config\Repository;
-use Illuminate\Foundation\Application;
-use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
-use Illuminate\Support\Str;
+
+use function array_keys;
+use function config;
class WorkbenchServiceProvider extends ServiceProvider
{
public function boot(): void
{
- if ($this->isFreshLaravel()) {
- return;
+ foreach ($this->disks() as $disk) {
+ Storage::fake($disk);
}
-
- Repository::macro('collection', function (string $key) {
- return new Collection($this->get($key));
- });
}
- protected function isFreshLaravel(): bool
+ protected function disks(): array
{
- return Str::of(Application::VERSION)->before('.')->toString() === '12';
+ return array_keys(config('filesystems.disks'));
}
}
diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php
new file mode 100644
index 0000000..df3c3aa
--- /dev/null
+++ b/workbench/database/seeders/DatabaseSeeder.php
@@ -0,0 +1,15 @@
+call(FeedSeeder::class);
+ }
+}
diff --git a/workbench/database/seeders/FeedSeeder.php b/workbench/database/seeders/FeedSeeder.php
new file mode 100644
index 0000000..152c21e
--- /dev/null
+++ b/workbench/database/seeders/FeedSeeder.php
@@ -0,0 +1,39 @@
+feeds as $feed) {
+ $this->store($feed);
+ }
+ }
+
+ protected function store(string $name): void
+ {
+ Feed::create([
+ 'class' => $name,
+ 'title' => $name,
+ ]);
+ }
+}