├── LICENSE.md
├── README.md
├── composer.json
├── config
└── context.php
├── phpcs.xml.dist
├── phpmd.xml.dist
└── src
├── ArtisanDomainContextServiceProvider.php
├── Commands
├── Concerns
│ ├── CustomOptionsFilters.php
│ └── InteractsWithChoices.php
├── Database
│ ├── Factories
│ │ └── FactoryMakeCommand.php
│ ├── Migrations
│ │ ├── Concerns
│ │ │ ├── MigrationOptions.php
│ │ │ ├── MigrationPaths.php
│ │ │ └── RunInMultiDatabases.php
│ │ ├── FreshCommand.php
│ │ ├── MigrateCommand.php
│ │ ├── MigrateMakeCommand.php
│ │ ├── RefreshCommand.php
│ │ ├── ResetCommand.php
│ │ ├── RollbackCommand.php
│ │ └── StatusCommand.php
│ └── Seeds
│ │ ├── SeedCommand.php
│ │ ├── SeederMakeCommand.php
│ │ └── stubs
│ │ └── seeder.stub
├── Foundation
│ ├── CastMakeCommand.php
│ ├── ChannelMakeCommand.php
│ ├── Concerns
│ │ ├── BuildClass.php
│ │ └── CommonMakeOptions.php
│ ├── ConsoleMakeCommand.php
│ ├── EventMakeCommand.php
│ ├── ExceptionMakeCommand.php
│ ├── JobMakeCommand.php
│ ├── ListenerMakeCommand.php
│ ├── MailMakeCommand.php
│ ├── ModelMakeCommand.php
│ ├── NotificationMakeCommand.php
│ ├── ObserverMakeCommand.php
│ ├── PolicyMakeCommand.php
│ ├── ProviderMakeCommand.php
│ ├── RequestMakeCommand.php
│ ├── ResourceMakeCommand.php
│ └── RuleMakeCommand.php
└── Routing
│ └── MiddlewareMakeCommand.php
├── Concerns
└── ArtisanConsoleTrait.php
├── Console
└── Application.php
├── Contracts
└── Inputable.php
├── Exceptions
├── ClassNotFoundException.php
└── ReaderException.php
├── Inputs
├── Concerns
│ └── ProxyForwardsCalls.php
├── Context.php
├── Host.php
└── Tenant.php
├── Providers
├── ArtisanServiceProvider.php
└── MigrationServiceProvider.php
└── Tools
└── ClassIterator.php
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 Allyson Silva
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Artisan Domain Contexts
2 |
3 |
4 |
5 |
6 |
7 | [![PHP Version][ico-php]][link-php]
8 | [![Laravel Version][ico-laravel]][link-laravel]
9 | [![CI Status][ico-actions]][link-actions]
10 | [](https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/phpcs.yml)
11 | [](https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/phpmd.yml)
12 | [](https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/phpstan.yml)
13 | [![Coverage Status][ico-codecov]][link-codecov]
14 | [![Code Quality/Consistency][ico-code-quality]][link-code-quality]
15 | [![Latest Version][ico-version]][link-packagist]
16 | [![Total Downloads][ico-downloads]][link-downloads]
17 | [](LICENSE.md)
18 |
19 | ## Table of Contents
20 |
21 | - [Overview](#overview)
22 | - [Concepts](#concepts)
23 | - [What is "Domain"?](#what-is-domain)
24 | - [What is "Contexts"?](#what-is-contexts)
25 | - [Questions](#questions)
26 | - [Should I use this package in the default Laravel framework?](#should-i-use-this-package-in-the-default-laravel-framework)
27 | - [When should I use this package?](#when-should-i-use-this-package)
28 | - [🚀 Installation](#--installation)
29 | - [Requirements](#requirements)
30 | - [Laravel version Compatibility](#laravel-version-compatibility)
31 | - [Install the Package](#install-the-package)
32 | - [Publish the Config](#publish-the-config)
33 | - [🔧 Configuration](#--configuration)
34 | - [📖 Usage](#--usage)
35 | - [Understanding the `--context` option](#understanding-the---context-option)
36 | - [Example](#example)
37 | - [Understanding the `--context-namespace` option](#understanding-the---context-namespace-option)
38 | - [Understanding the `--all-contexts` option](#understanding-the---all-contexts-option)
39 | - [Understanding the `--only-default` option](#understanding-the---only-default-option)
40 | - [Understanding the `--multi-databases` option](#understanding-the---multi-databases-option)
41 | - [List of commands using contexts](#list-of-commands-using-contexts)
42 | - [🏗 `make` commands](#-make-commands)
43 | - [Examples](#examples)
44 | - [`migrate` commands](#migrate-commands)
45 | - [Understanding the behavior of `migrate:fresh` and `migrate:refresh`](#understanding-the-behavior-of-migratefresh-and-migraterefresh)
46 | - [📹 Demo `migrate:fresh`](#--demo-migratefresh)
47 | - [📹 Demo `migrate:refresh`](#--demo-migraterefresh)
48 | - [Understanding the behavior of `migrate:reset`, `migrate:rollback`, `migrate:status` and `migrate`](#understanding-the-behavior-of-migratereset-migraterollback-migratestatus-and-migrate)
49 | - [📹 Demo `migrate:reset`](#--demo-migratereset)
50 | - [📹 Demo `migrate:rollback`](#--demo-migraterollback)
51 | - [`db:seed` command](#dbseed-command)
52 | - [📹 Demo `db:seed`](#--demo-dbseed)
53 | - [🧪 Testing](#--testing)
54 | - [📝 Changelog](#--changelog)
55 | - [🤝 Contributing](#--contributing)
56 | - [🔒 Security](#--security)
57 | - [🏆 Credits](#--credits)
58 | - [License](#license)
59 |
60 | ## Overview
61 |
62 | > This package provides the ability to use **artisan commands** in different **domain contexts**. It allows to work interactively in the migration commands and seeders, choosing which class should be executed.
63 |
64 |
65 |
66 |
67 |
68 | ### Concepts
69 |
70 | #### What is "Domain"?
71 |
72 | - The term "**Domain**" has nothing to do with URL or host (www.example.com), but with the *business*, **the specific sphere of activity or knowledge**. In other words, the "**Domain**" is the company's *business* itself.
73 | - "**Domain**" is the first (main 😏) word of DDD (*Domain-Driven Design*), which is the company's business.
74 |
75 | See the [article](https://stitcher.io/blog/organise-by-domain) for a better understanding!
76 |
77 | #### What is "Contexts"?
78 |
79 | - If the "**Domain**" is the business of the company, the "**Context**" are the parts of that business, that is, **the groups of related things, different parts of the business logic**. For example, in an online store business, we could have the contexts of: Orders, Customers, Products and more. The "**Domain**" is the business of the online store, and the contexts are the parts of that same business.
80 |
81 | See the [article](https://martinfowler.com/bliki/BoundedContext.html) for better understanding!
82 |
83 | ### Questions
84 |
85 | #### Should I use this package in the default Laravel framework?
86 |
87 | No, because the files handled by `artisan` are already in their default folders.
88 |
89 | #### When should I use this package?
90 |
91 | - When the folder structure is not Laravel's default.
92 | - When needed, for example, execute/manipulate `artisan` commands in different contexts/folders of the application.
93 |
94 | ## 🚀 Installation
95 |
96 | ### Requirements
97 |
98 | The package has been developed and tested to work with the following minimum requirements:
99 |
100 | - *PHP 8.0*
101 | - *Laravel 8.70*
102 |
103 | ### Laravel version Compatibility
104 |
105 | | Laravel | PHP | Package | Maintained |
106 | |:-------:|:---:|:------------:|:--------------:|
107 | | 9.x | 8.0 | **^2.0** | ✅ |
108 | | 8.70 | 8.0 | **^1.0** | ✅ |
109 |
110 | ### Install the Package
111 |
112 | You can install the package via Composer:
113 |
114 | ```bash
115 | composer require allysonsilva/laravel-artisan-domain-contexts
116 | ```
117 |
118 | ### Publish the Config
119 |
120 | You can then publish the package's config file by using the following command:
121 |
122 | ```bash
123 | php artisan vendor:publish --tag="context-config"
124 | ```
125 |
126 | ## 🔧 Configuration
127 |
128 | 1. Create a folder, inside the `app` folder with the same name as the config of `config('context.folders.domain')`
129 |
130 | 2. Add trait to `app/Console/Kernel.php` file with usage options in all Laravel commands:
131 | ```diff
132 | use Illuminate\Console\Scheduling\Schedule;
133 | use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
134 | +use Allyson\ArtisanDomainContext\Concerns\ArtisanConsoleTrait;
135 |
136 | class Kernel extends ConsoleKernel
137 | {
138 | + use ArtisanConsoleTrait;
139 | ```
140 |
141 | 3. Inside the domain folder (`config('context.folders.domain')`), is where you can find Laravel components (such as migrations, seeders, models, jobs, etc). The names of the folders, where the classes are, are in the config of `config('context.folders.components')`.
142 |
143 | ## 📖 Usage
144 |
145 | To use **commands by context**, the following options have been added to Laravel commands:
146 |
147 | - **`--context`**
148 | - **`--context-namespace`**
149 | - **`--all-contexts`**
150 | - **`--only-default`**
151 | - **`--multi-databases`**
152 |
153 | **Some options are only available in a certain command, but the `--context` option is present in all commands in the list below!**
154 |
155 | The list of standard laravel commands that were handled by adding these options can be seen [table below](#list-of-commands-using-contexts).
156 |
157 | ### Understanding the `--context` option
158 |
159 | **This option is present in all commands listed below!**
160 |
161 | When this option is passed in the command, then the component/class/resource is manipulated or created, according to the resource type setting in `config('context.folders.components')`.
162 |
163 | To change the path/name of the folder where the class should be manipulated or created, see the config in `config('context.folders.components')`.
164 |
165 | #### Example
166 |
167 | So, for example, to create a middleware in a given context, in this case, in the user context, the command can be: `php artisan make:middleware --context=User YourMiddleware`.
168 |
169 | A middleware class was created in `app/Domain/User/Http/Middlewares/YourMiddleware.php`.
170 |
171 | If the config of `config('context.folders.components.middlewares')` has the value of `Http/AnotherFolder` instead of `Http/Middlewares` (default), and the previous command is executed, then the class would be created in `app/Domain/User/Http/AnotherFolder/YourMiddleware.php`.
172 |
173 | ### Understanding the `--context-namespace` option
174 |
175 | This option will only be used in the `make` commands, with that, see and [explanation about it](#make-commands).
176 |
177 | ### Understanding the `--all-contexts` option
178 |
179 | When this option is passed in the command, then it will be executed non-interactively, that is, it will execute the context-specific filtered classes.
180 |
181 | This option is not present in the `make` commands only in the migration and db:seed commands. See the [table below](#list-of-commands-using-contexts).
182 |
183 | It has the same behavior as the `--force` option.
184 |
185 | ### Understanding the `--only-default` option
186 |
187 | By default, *migrations* commands are executed in all contexts when no options are passed. To run migrations commands in Laravel's default folder (`database/migrations`) use this option.
188 |
189 | This option is only in the migration and `db:seed` commands. It is not present in the `make` commands.
190 |
191 | ### Understanding the `--multi-databases` option
192 |
193 | *This option is only present in migration commands!*
194 |
195 | By default the migration commands are run on the database configured in the `DB_DATABASE` *env*, so you can only use one database in the command. With this option, you can use multiple databases for the same command via the config `config('context.migrations.databases')`
196 |
197 | When this option is passed, then the command will be executed on different databases according to the config of `config('context.migrations.databases')`.
198 |
199 | The config of `config('context.migrations.databases')` refers to the name of the database that the operation will be performed on.
200 |
201 | ### List of commands using contexts
202 |
203 |
204 |
205 | Commands |
206 | Additional Command Options |
207 |
208 |
209 | --context |
210 | --context-namespace |
211 | --all-contexts |
212 | --only-default |
213 | --multi-databases |
214 |
215 |
216 | make:cast |
217 | ✔️ |
218 | ✔️ |
219 | |
220 | |
221 | |
222 |
223 |
224 | make:channel |
225 | ✔️ |
226 | ✔️ |
227 | |
228 | |
229 | |
230 |
231 |
232 | make:command |
233 | ✔️ |
234 | ✔️ |
235 | |
236 | |
237 | |
238 |
239 |
240 | make:event |
241 | ✔️ |
242 | ✔️ |
243 | |
244 | |
245 | |
246 |
247 |
248 | make:exception |
249 | ✔️ |
250 | ✔️ |
251 | |
252 | |
253 | |
254 |
255 |
256 | make:factory |
257 | ✔️ |
258 | ✔️ |
259 | |
260 | |
261 | |
262 |
263 |
264 | make:factory |
265 | ✔️ |
266 | ✔️ |
267 | |
268 | |
269 | |
270 |
271 |
272 | make:job |
273 | ✔️ |
274 | ✔️ |
275 | |
276 | |
277 | |
278 |
279 |
280 | make:listener |
281 | ✔️ |
282 | ✔️ |
283 | |
284 | |
285 | |
286 |
287 |
288 | make:mail |
289 | ✔️ |
290 | ✔️ |
291 | |
292 | |
293 | |
294 |
295 |
296 | make:middleware |
297 | ✔️ |
298 | ✔️ |
299 | |
300 | |
301 | |
302 |
303 |
304 | make:migration |
305 | ✔️ |
306 | ✔️ |
307 | |
308 | |
309 | |
310 |
311 |
312 | make:model |
313 | ✔️ |
314 | ✔️ |
315 | |
316 | |
317 | |
318 |
319 |
320 | make:notification |
321 | ✔️ |
322 | ✔️ |
323 | |
324 | |
325 | |
326 |
327 |
328 | make:observer |
329 | ✔️ |
330 | ✔️ |
331 | |
332 | |
333 | |
334 |
335 |
336 | make:policy |
337 | ✔️ |
338 | ✔️ |
339 | |
340 | |
341 | |
342 |
343 |
344 | make:provider |
345 | ✔️ |
346 | ✔️ |
347 | |
348 | |
349 | |
350 |
351 |
352 | make:request |
353 | ✔️ |
354 | ✔️ |
355 | |
356 | |
357 | |
358 |
359 |
360 | make:resource |
361 | ✔️ |
362 | ✔️ |
363 | |
364 | |
365 | |
366 |
367 |
368 | make:rule |
369 | ✔️ |
370 | ✔️ |
371 | |
372 | |
373 | |
374 |
375 |
376 | make:seeder |
377 | ✔️ |
378 | ✔️ |
379 | |
380 | |
381 | |
382 |
383 |
384 | migrate:fresh |
385 | ✔️ |
386 | |
387 | |
388 | ✔️ |
389 | |
390 |
391 |
392 | migrate:refresh |
393 | ✔️ |
394 | |
395 | |
396 | ✔️ |
397 | ✔️ |
398 |
399 |
400 | migrate:reset |
401 | ✔️ |
402 | |
403 | ✔️ |
404 | ✔️ |
405 | ✔️ |
406 |
407 |
408 | migrate:rollback |
409 | ✔️ |
410 | |
411 | ✔️ |
412 | ✔️ |
413 | ✔️ |
414 |
415 |
416 | migrate:status |
417 | ✔️ |
418 | |
419 | ✔️ |
420 | ✔️ |
421 | ✔️ |
422 |
423 |
424 | migrate |
425 | ✔️ |
426 | |
427 | ✔️ |
428 | ✔️ |
429 | ✔️ |
430 |
431 |
432 | db:seed |
433 | ✔️ |
434 | |
435 | ✔️ |
436 | ✔️ |
437 | |
438 |
439 |
440 |
441 | ### 🏗 `make` commands
442 |
443 | > Make commands, create files in a given context or Laravel's default folder when no context is specified.
444 |
445 | All the `make` commands that are listed in the table above, have 2 options in their command, which are:
446 |
447 | - `--context`: *Name of the folder where the class will be created*, if not passed in the command, then the class will be created in the *Laravel's default folder*.
448 |
449 | - `--context-namespace`: Custom namespace that will be used in place of the class's normal namespace.
450 |
451 | To change the name of the component folder in which the class is created, see the following config `config('context.folders.components')`.
452 |
453 | #### Examples
454 |
455 | The example commands below will use the following folder organization:
456 |
457 | ```
458 | app/
459 | ├── Domain
460 | │ ├── Foo
461 | │ ├── Post
462 | └── └── User
463 | ```
464 |
465 | **How to create a [MIGRATION] in a specific context?**
466 |
467 | Use the following command below as an example, passing the `--context` option with the name of the **context** in *which the migration will be created*.
468 |
469 | The migration files will be saved in the *config* folder `config('context.folders.components.migrations')` in the *context folder*, according to the `--context` option of the command.
470 |
471 | ```bash
472 | php artisan make:migration --context=Post create_posts_table
473 | ```
474 |
475 | *A new migration has been created at*: `app/Domain/Post/Database/Migrations/2022_xx_xx_xxxxxx_create_posts_table.php`
476 |
477 | The file path parts are:
478 |
479 | - **`Domain`**: Value configured according to `config('context.folders.domain')`.
480 | - **`Post`**: Value of the `--context` option.
481 | - **`Database/Migrations`**: Value configured according to `config('context.folders.components.migrations')`.
482 |
483 | **How to create a [JOB] in a specific context?**
484 |
485 | In the same way as explained earlier in the case of migration, *the `--context` option is also used to create a new job in a specific context*.
486 |
487 | ```bash
488 | php artisan make:job --context=Foo MyJob
489 | ```
490 |
491 | *A new job has been created at*: `app/Domain/Foo/Jobs/MyJob.php`
492 |
493 | The file path parts are:
494 |
495 | - **`Domain`**: Value configured according to `config('context.folders.domain')`.
496 | - **`Foo`**: Value of the `--context` option.
497 | - **`Jobs`**: Value configured according to `config('context.folders.components.jobs')`.
498 |
499 | **How to create a class/component with a custom namespace?**
500 |
501 | *Use the `--context-namespace` option to customize the class namespace prefix.*
502 |
503 | If you want to create a model with a specific namespace, Use the following command below as an example.
504 |
505 | ```bash
506 | php artisan make:model --context=Post --context-namespace=PostDomain Post
507 | ```
508 |
509 | The previous command will *create a model in the path*: `app/Domain/Post/Models/Post.php`
510 |
511 | With the following content:
512 |
513 | ```diff
514 | The migration commands are executed interactively, and you can even select each individual migration, or all migrations in a given context to perform the operation.
542 |
543 | The following are the options that may be on commands:
544 |
545 | - `--context`: Context where migrations should be performed/handled.
546 |
547 | - `--all-contexts`: The command must be run on migrations from all contexts.
548 |
549 | - `--only-default`: If this option is passed, then only migrations from the *default Laravel folder* (`database/migrations`) will be used for the command.
550 |
551 | - `--multi-databases`: Will run the command on all config databases `config('context.migrations.databases')`.
552 |
553 | To see the migration commands in action, let's use the folder organization below as an example and see the expected results:
554 |
555 | ```
556 | app
557 | └── Domain
558 | ├── Foo
559 | │ └── Database
560 | │ ├── Migrations
561 | │ │ ├── 2022_02_30_000000_create_baz_table.php
562 | │ │ └── 2022_02_30_000000_create_foo_table.php
563 | │ └── Seeders
564 | │ ├── BazTableSeeder.php
565 | │ └── FooTableSeeder.php
566 | ├── Post
567 | │ └── Database
568 | │ ├── Migrations
569 | │ │ ├── 2022_02_30_000000_create_posts_1_table.php
570 | │ │ ├── 2022_02_30_000000_create_posts_2_table.php
571 | │ │ └── 2022_02_30_000000_create_posts_3_table.php
572 | │ └── Seeders
573 | │ ├── PostsTableSeeder1.php
574 | │ ├── PostsTableSeeder2.php
575 | │ └── PostsTableSeeder3.php
576 | └── User
577 | └── Database
578 | ├── Migrations
579 | │ ├── 2022_02_30_000000_create_users_1_table.php
580 | │ ├── 2022_02_30_000000_create_users_2_table.php
581 | │ └── 2022_02_30_000000_create_users_3_table.php
582 | └── Seeders
583 | ├── UsersTableSeeder1.php
584 | ├── UsersTableSeeder2.php
585 | └── UsersTableSeeder3.php
586 | ```
587 |
588 | #### Understanding the behavior of `migrate:fresh` and `migrate:refresh`
589 |
590 | - Both have the same set of options: `--context` and `--only-default`
591 | - `migrate:refresh` has one more option which is `--multi-databases`
592 |
593 | **Both work in the same way / Summary**:
594 |
595 | - *When no options are passed to the command, the default is to perform migrations from all contexts. For this reason it does not have the `--all-contexts` option. Well, it's the same behavior as if you had the option.*
596 | - *Cannot select/choose migration individually.*
597 | - *Run all migrations from given context, from all contexts or from Laravel's default folder.*
598 |
599 | See the questions and answers below for better understanding:
600 |
601 | - How to run the command in a specific context?
602 | ```bash
603 | php artisan migrate: --context=YOUR_CONTEXT
604 | ```
605 |
606 | - How to run command in all contexts?
607 | ```bash
608 | php artisan migrate:
609 | ```
610 |
611 | - How to run command only in default Laravel migration folder?
612 | ```bash
613 | php artisan migrate: --only-default
614 | ```
615 |
616 | - How can I run the command on multiple databases? (*only refresh*)
617 | ```bash
618 | # In all config databases `config('context.migrations.databases')`
619 | php artisan migrate:refresh --multi-databases
620 | # Or on multiple databases of a specific context:
621 | php artisan migrate:refresh --context=User --multi-databases
622 | ```
623 |
624 | #### 📹 Demo `migrate:fresh`
625 |
626 | See the demo below for better understanding:
627 |
628 |
629 |
630 |
631 |
632 | #### 📹 Demo `migrate:refresh`
633 |
634 | See the demo below for better understanding:
635 |
636 |
637 |
638 |
639 |
640 | #### Understanding the behavior of `migrate:reset`, `migrate:rollback`, `migrate:status` and `migrate`
641 |
642 | - All 4 commands have the following options:
643 | - `--context`
644 | - `--all-contexts`
645 | - `--only-default`
646 | - `--multi-databases`
647 |
648 | **Summary of all commands**:
649 |
650 | - *By default, a list will always appear with the migrations to choose from. Whether migrations from all contexts, or migrations from a specific context using the `--context` option, there will always be a list of migrations to be chosen and used in the commands. **To run non-interactive, use the `--force` option.***
651 | - *When no options are passed to the command, the default is to list migrations from all contexts, using artisan's [`choice`](https://laravel.com/docs/artisan#multiple-choice-questions) method.*
652 | - *To run the command on migrations of all contexts, use the `--all-contexts` option.*
653 |
654 | See the questions and answers below for better understanding:
655 |
656 | *The `$command` variable, can be one of the items `['migrate:reset', 'migrate:rollback', 'migrate:status', 'migrate']`.*
657 |
658 | - How to run the command in a specific context?
659 | ```bash
660 | # A list of migrations present in the context will appear to be chosen for execution!
661 | php artisan $command --context=YOUR_CONTEXT
662 | # To execute in a "forced" way, that is, all migrations from a given context, use the `--force` option!
663 | php artisan $command --context=YOUR_CONTEXT --force
664 | ```
665 |
666 | - How to run command in all contexts?
667 | ```bash
668 | # List of migrations to choose which will be performed
669 | php artisan $command
670 | # Run migrations from all contexts "forced"
671 | php artisan $command --all-contexts
672 | ```
673 |
674 | - How to run command only in default Laravel migration folder?
675 | ```bash
676 | php artisan $command --only-default
677 | ```
678 |
679 | - How can I run the command on multiple databases?
680 | ```bash
681 | # In all config databases `config('context.migrations.databases')`
682 | php artisan $command --multi-databases
683 | # Or on multiple databases of a specific context:
684 | php artisan $command --context=User --multi-databases
685 | # Or force execution of the command
686 | php artisan $command --context=User --multi-databases --force
687 | ```
688 |
689 | #### 📹 Demo `migrate:reset`
690 |
691 | See the demo below for better understanding:
692 |
693 |
694 |
695 |
696 |
697 | #### 📹 Demo `migrate:rollback`
698 |
699 | See the demo below for better understanding:
700 |
701 |
702 |
703 |
704 |
705 | ### `db:seed` command
706 |
707 | Three options are found in the command:
708 |
709 | - `--context`
710 | - `--all-contexts`
711 | - `--only-default`
712 |
713 | See the questions and answers below for better understanding:
714 |
715 | - How to run the command in a specific context?
716 | ```bash
717 | # A list of seeders present in the context will appear to be chosen for execution!
718 | php artisan db:seed --context=YOUR_CONTEXT
719 | # To execute in a "forced" way, that is, all seeders from a given context, use the `--force` option!
720 | php artisan db:seed --context=YOUR_CONTEXT --force
721 | ```
722 |
723 | - How to run command in all contexts?
724 | ```bash
725 | # List of seeders to choose which will be performed
726 | php artisan db:seed
727 | # Run seeders from all contexts "forced"
728 | php artisan db:seed --all-contexts
729 | ```
730 |
731 | - How to run command only in default Laravel migration folder?
732 | ```bash
733 | php artisan db:seed --only-default
734 | ```
735 |
736 | #### 📹 Demo `db:seed`
737 |
738 | See the demo below for better understanding:
739 |
740 |
741 |
742 |
743 |
744 | ## 🧪 Testing
745 |
746 | ``` bash
747 | composer test:unit
748 | ```
749 |
750 | ## 📝 Changelog
751 |
752 | Please see [CHANGELOG](CHANGELOG.md) for more information about the changes on this package.
753 |
754 | ## 🤝 Contributing
755 |
756 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
757 |
758 | ## 🔒 Security
759 |
760 | If you discover any security related issues, please email github@allyson.dev instead of using the issue tracker.
761 |
762 | ## 🏆 Credits
763 |
764 | - [Allyson Silva](https://github.com/allysonsilva)
765 | - [All Contributors](../../contributors)
766 |
767 | ## License
768 |
769 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
770 |
771 | [ico-php]: https://img.shields.io/packagist/php-v/allysonsilva/laravel-artisan-domain-contexts?color=%234F5B93&logo=php
772 | [ico-laravel]: https://img.shields.io/static/v1?label=laravel&message=%E2%89%A59.0&color=ff2d20&logo=laravel
773 | [ico-actions]: https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/ci.yml/badge.svg
774 | [ico-codecov]: https://codecov.io/gh/allysonsilva/laravel-artisan-domain-contexts/branch/main/graph/badge.svg?token=MXQO0HIBFM
775 | [ico-code-quality]: https://app.codacy.com/project/badge/Grade/5f2b3a98dbee48aa8e14ba4a55681f09
776 | [ico-version]: https://img.shields.io/packagist/v/allysonsilva/laravel-artisan-domain-contexts.svg?label=stable
777 | [ico-downloads]: https://img.shields.io/packagist/dt/allysonsilva/laravel-artisan-domain-contexts.svg
778 |
779 | [link-php]: https://www.php.net
780 | [link-laravel]: https://laravel.com
781 | [link-actions]: https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/ci.yml
782 | [link-codecov]: https://codecov.io/gh/allysonsilva/laravel-artisan-domain-contexts
783 | [link-code-quality]: https://www.codacy.com/gh/allysonsilva/laravel-artisan-domain-contexts/dashboard
784 | [link-packagist]: https://packagist.org/packages/allysonsilva/laravel-artisan-domain-contexts
785 | [link-downloads]: https://packagist.org/packages/allysonsilva/laravel-artisan-domain-contexts
786 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "allysonsilva/laravel-artisan-domain-contexts",
3 | "description": "A laravel package for using artisan commands in domain contexts",
4 | "license": "MIT",
5 | "keywords": [
6 | "allyson",
7 | "artisan",
8 | "console",
9 | "domain-contexts",
10 | "bounded-contexts",
11 | "ddd",
12 | "php",
13 | "cli",
14 | "laravel"
15 | ],
16 | "authors": [
17 | {
18 | "name": "Allyson Silva",
19 | "email": "dev@allyson.dev",
20 | "homepage": "https://allyson.dev",
21 | "role": "Developer"
22 | }
23 | ],
24 | "homepage": "https://github.com/allysonsilva/laravel-artisan-domain-contexts",
25 | "require": {
26 | "php": ">=8.0",
27 | "laravel/framework": "^9.32",
28 | "nikic/php-parser": "^4.13"
29 | },
30 | "require-dev": {
31 | "ergebnis/phpstan-rules": "^1.0",
32 | "nunomaduro/larastan": "^2.0",
33 | "orchestra/testbench": "^7.0",
34 | "phpmd/phpmd": "^2.11",
35 | "phpstan/phpstan": "^1.0",
36 | "phpstan/phpstan-strict-rules": "^1.1",
37 | "phpunit/phpunit": "^9.0",
38 | "slevomat/coding-standard": "^7.0",
39 | "squizlabs/php_codesniffer": "^3.6",
40 | "symplify/phpstan-rules": "10.0.21"
41 | },
42 | "minimum-stability": "dev",
43 | "prefer-stable": true,
44 | "autoload": {
45 | "psr-4": {
46 | "Allyson\\ArtisanDomainContext\\": "src"
47 | }
48 | },
49 | "autoload-dev": {
50 | "psr-4": {
51 | "App\\": "tests/LaravelApp/app/",
52 | "Database\\Factories\\": "tests/LaravelApp/database/factories/",
53 | "Database\\Seeders\\": "tests/LaravelApp/database/seeders/",
54 | "Allyson\\ArtisanDomainContext\\Tests\\": "tests"
55 | }
56 | },
57 | "config": {
58 | "allow-plugins": {
59 | "dealerdirect/phpcodesniffer-composer-installer": true,
60 | "ergebnis/composer-normalize": true
61 | },
62 | "optimize-autoloader": true,
63 | "sort-packages": true
64 | },
65 | "extra": {
66 | "laravel": {
67 | "providers": [
68 | "Allyson\\ArtisanDomainContext\\ArtisanDomainContextServiceProvider"
69 | ]
70 | }
71 | },
72 | "scripts": {
73 | "test": [
74 | "@test:analyse",
75 | "@test:standard",
76 | "@test:mess",
77 | "@test:unit"
78 | ],
79 | "test:analyse": "php -d memory_limit=1G vendor/bin/phpstan analyse --error-format=table --ansi --memory-limit=1G",
80 | "test:mess": "phpmd src ansi phpmd.xml.dist --suffixes php",
81 | "test:standard": "phpcs --report=full --report-width=auto",
82 | "test:unit": "php -d memory_limit=1G ./vendor/bin/phpunit --group Migration,Seeder,Making,Input,Options,Tools --colors=always"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/config/context.php:
--------------------------------------------------------------------------------
1 | [
8 | Context::class,
9 | ],
10 |
11 | 'folders' => [
12 | 'domain' => 'Domain',
13 | 'pattern' => '/{,*/,*/*/,*/*/*/,*/*/*/*/}/',
14 | 'components' => [
15 | 'migrations' => 'Database/Migrations',
16 | 'seeders' => 'Database/Seeders',
17 | 'factories' => 'Database/Factories',
18 | 'middlewares' => 'Http/Middlewares',
19 | 'requests' => 'Http/Requests',
20 | 'resources' => 'Http/Resources',
21 | 'casts' => 'Casts',
22 | 'channel' => 'Broadcasting',
23 | 'console' => 'Console/Commands',
24 | 'events' => 'Events',
25 | 'exceptions' => 'Exceptions',
26 | 'jobs' => 'Jobs',
27 | 'listeners' => 'Listeners',
28 | 'mail' => 'Mail',
29 | 'models' => 'Models',
30 | 'notifications' => 'Notifications',
31 | 'observers' => 'Observers',
32 | 'policies' => 'Policies',
33 | 'providers' => 'Providers',
34 | 'rules' => 'Rules',
35 | ],
36 | ],
37 | ];
38 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | The encoding standard used in this package.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | src
26 |
27 |
28 | */database/migrations/*
29 | *.(css|js|json|html|yml|yaml)
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | warning
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | warning
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 | warning
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 | Variable "%s" not allowed in double quoted string; use sprintf() or concatenation instead
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 | 5
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 | Only 1 @return annotation is allowed in a function comment
572 |
573 |
574 |
575 | Function has no return statement, but annotation @return is present
576 |
577 |
578 |
579 | @param annotation for parameter "%s" missing
580 |
581 |
582 |
583 |
584 | 0
585 |
586 |
587 |
588 | 0
589 |
590 |
591 |
592 | 0
593 |
594 |
595 |
--------------------------------------------------------------------------------
/phpmd.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | APP Mess Detector
9 |
10 |
11 | */**/console.php
12 | */database/migrations/*
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 3
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/ArtisanDomainContextServiceProvider.php:
--------------------------------------------------------------------------------
1 | >
16 | *
17 | * @phpstan-ignore-next-line
18 | */
19 | protected $providers = [
20 | ArtisanServiceProvider::class,
21 | MigrationServiceProvider::class,
22 | ];
23 |
24 | /**
25 | * Register any application services.
26 | *
27 | * @return void
28 | */
29 | public function register(): void
30 | {
31 | parent::register();
32 |
33 | if (! app()->configurationIsCached()) {
34 | $this->mergeConfigFrom(__DIR__ . '/../config/context.php', 'context');
35 | }
36 | }
37 |
38 | /**
39 | * Bootstrap services.
40 | *
41 | * @return void
42 | */
43 | public function boot(): void
44 | {
45 | if ($this->app->runningInConsole()) {
46 | $this->publishes([
47 | __DIR__ . '/../config/context.php' => config_path('context.php'),
48 | ], 'context-config');
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Commands/Concerns/CustomOptionsFilters.php:
--------------------------------------------------------------------------------
1 | getOnlyDefaultOption(), $this->getAllContextsOption()];
19 | }
20 |
21 | /**
22 | * Retrieves `--only-default` option data.
23 | *
24 | * @return \Symfony\Component\Console\Input\InputOption
25 | */
26 | protected function getOnlyDefaultOption(): InputOption
27 | {
28 | $description = "Load component from Laravel's default folder";
29 |
30 | return new InputOption(name: 'only-default', mode: InputOption::VALUE_NONE, description: $description);
31 | }
32 |
33 | /**
34 | * Retrieves `--all-contexts` option data.
35 | *
36 | * @return \Symfony\Component\Console\Input\InputOption
37 | */
38 | protected function getAllContextsOption(): InputOption
39 | {
40 | $description = 'Run, filter, show all classes and files selected from all contexts';
41 |
42 | return new InputOption(name: 'all-contexts', mode: InputOption::VALUE_NONE, description: $description);
43 | }
44 |
45 | /**
46 | * Checks if the `--only-default` option was passed in the command.
47 | *
48 | * @return bool
49 | */
50 | protected function hasOnlyDefaultOption(): bool
51 | {
52 | $optionName = 'only-default';
53 |
54 | return $this->hasOption($optionName) && boolval($this->option($optionName));
55 | }
56 |
57 | /**
58 | * Checks if the `--all-contexts` option was passed in the command.
59 | *
60 | * @return bool
61 | */
62 | protected function hasAllContextsOption(): bool
63 | {
64 | $optionName = 'all-contexts';
65 |
66 | return $this->hasOption($optionName) && boolval($this->option($optionName));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Commands/Concerns/InteractsWithChoices.php:
--------------------------------------------------------------------------------
1 | getContextDir())->replaceMatches('#/+#', '/');
33 |
34 | $finder = new Finder();
35 | $finder->files()->in($contextComponentPath);
36 |
37 | $this->decorateFinder($finder);
38 |
39 | return $finder;
40 | }
41 |
42 | /**
43 | * Compose/decorate the finder object.
44 | *
45 | * @param \Symfony\Component\Finder\Finder $finder
46 | *
47 | * @return void
48 | */
49 | protected function decorateFinder(Finder $finder): void
50 | {
51 | $finder->name('/\.php$/i')
52 | ->size('< 100K')
53 | ->sortByName(true);
54 | }
55 |
56 | /**
57 | * Recovers the folder where the files or classes are located.
58 | *
59 | * @return string
60 | */
61 | protected function getContextDir(): string
62 | {
63 | return app_path() . '/' .
64 | config('context.folders.domain') . '/' .
65 | strval($this->option('context')) . '/' .
66 | config('context.folders.pattern') .
67 | $this->getContextComponentFolder();
68 | }
69 |
70 | /**
71 | * Retrieve files from given context or all contexts.
72 | *
73 | * @return \Generator
74 | */
75 | protected function getFilesFromContext(): Generator
76 | {
77 | /** @var \Symfony\Component\Finder\Finder $files */
78 | $files = $this->handleFinder();
79 |
80 | /** @var \Symfony\Component\Finder\SplFileInfo $splFileInfo */
81 | foreach ($files as $splFileInfo) {
82 | $pathPrefixToBeRemoved = base_path() . '/';
83 | $absolutePathFile = strval($splFileInfo->getRealPath());
84 | $relativePathFile = str_replace($pathPrefixToBeRemoved, '', $absolutePathFile);
85 |
86 | yield $relativePathFile;
87 | }
88 |
89 | yield from [];
90 | }
91 |
92 | /**
93 | * Retrieves all classes for a given context via the `--context` option,
94 | * or all classes for all contexts if the option is not passed.
95 | *
96 | * @return \Illuminate\Support\Collection<\ReflectionClass>
97 | */
98 | protected function getClassesFromContext(): Collection
99 | {
100 | /** @var \Allyson\ArtisanDomainContext\Tools\ClassIterator $classIterator */
101 | $classIterator = new ClassIterator($this->handleFinder());
102 |
103 | /** @phpstan-var array */
104 | $classMap = iterator_to_array($classIterator);
105 |
106 | /** @phpstan-ignore-next-line */
107 | if (method_exists($this, 'handleClassIterator')) {
108 | $classMap = iterator_to_array($this->handleClassIterator($classIterator));
109 | }
110 |
111 | /** @var \ReflectionClass[] */
112 | $classes = array_values($classMap);
113 |
114 | return collect($classes);
115 | }
116 |
117 | /**
118 | * Transform the files reflection classes into an array
119 | * containing the full class name (with namespace).
120 | *
121 | * @param \Illuminate\Support\Collection<\ReflectionClass> $classes
122 | *
123 | * @return array
124 | */
125 | protected function transformReflectionClassIntoArrayOfClassNames(Collection $classes): array
126 | {
127 | return $classes->sortBy(fn (ReflectionClass $class) => $class->getShortName())
128 | ->map(fn (ReflectionClass $class) => $class->getName())
129 | ->values()
130 | ->all();
131 | }
132 |
133 | /**
134 | * List and choose which classes/files to run.
135 | *
136 | * @param array|\Generator $choices
137 | *
138 | * @return string[]|string
139 | */
140 | protected function choose(array|Generator $choices): string|array
141 | {
142 | if ($choices instanceof Generator) {
143 | $choices = iterator_to_array($choices);
144 | }
145 |
146 | if ($this->hasForceOrAllContextsOption()) {
147 | return $choices;
148 | }
149 |
150 | array_unshift($choices, 'ALL');
151 |
152 | $chosen = Arr::wrap($this->choice(
153 | 'Which class/file would you like to run?',
154 | $choices,
155 | multiple: true,
156 | ));
157 |
158 | if (in_array('ALL', $chosen, true)) {
159 | array_shift($choices);
160 | $chosen = $choices;
161 | }
162 |
163 | return array_values($chosen);
164 | }
165 |
166 | /**
167 | * @param array $choices
168 | *
169 | * @return array
170 | */
171 | protected function addAbsolutePath(array $choices): array
172 | {
173 | $basePath = base_path();
174 |
175 | /** @var array */
176 | $choicesWithAbsolutePath = [];
177 |
178 | foreach ($choices as $choice) {
179 | if (! str_starts_with($choice, $basePath)) {
180 | $choicesWithAbsolutePath[] = base_path($choice);
181 | }
182 | }
183 |
184 | return $choicesWithAbsolutePath;
185 | }
186 |
187 | /**
188 | * Checks if all choices should be selected.
189 | * Laravel's `choice` command will not be applied!
190 | *
191 | * @return bool
192 | */
193 | protected function hasForceOrAllContextsOption(): bool
194 | {
195 | $optionName = 'force';
196 |
197 | return ($this->hasOption($optionName) && boolval($this->option($optionName))) || $this->hasAllContextsOption();
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/Commands/Database/Factories/FactoryMakeCommand.php:
--------------------------------------------------------------------------------
1 | getCustomContextNamespace();
37 |
38 | if ($namespace === trim($this->rootNamespace(), '\\')) {
39 | $namespace = 'Database\\Factories';
40 | }
41 |
42 | $replace = [
43 | '{{ factoryNamespace }}' => $namespace,
44 | '{{ factory }}' => $factory,
45 | '{{factory}}' => $factory,
46 | ];
47 |
48 | $searchByValue = array_keys($replace);
49 | $substituteValue = array_values($replace);
50 | $stringToSearchValue = GeneratorCommand::buildClass($name);
51 |
52 | return str_replace($searchByValue, $substituteValue, $stringToSearchValue);
53 | }
54 |
55 | /**
56 | * Get the destination class path.
57 | *
58 | * @param string $name
59 | *
60 | * @return string
61 | */
62 | protected function getPath($name): string
63 | {
64 | if (! empty($this->contextOption())) {
65 | /** @phpstan-ignore-next-line */
66 | $name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory');
67 |
68 | return $this->laravel->path() . '/' . str_replace('\\', '/', $name) . '.php';
69 | }
70 |
71 | return parent::getPath($name);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/Concerns/MigrationOptions.php:
--------------------------------------------------------------------------------
1 | isWithAllContextsOption()) {
19 | array_push($options, $this->getAllContextsOption());
20 | }
21 |
22 | if ($this->isWithMultiDatabasesOption()) {
23 | array_push($options, $this->getMultiDatabasesOption());
24 | }
25 |
26 | array_push($options, $this->getOnlyDefaultOption());
27 |
28 | return $options;
29 | }
30 |
31 | /**
32 | * Get the console command options.
33 | *
34 | * @return array
35 | *
36 | * @phpstan-return InputOptionsArray
37 | */
38 | protected function getOptions(): array
39 | {
40 | return $this->getMigrationOptions();
41 | }
42 |
43 | /**
44 | * Retrieves `--multi-databases` option data.
45 | *
46 | * @return array
47 | *
48 | * @phpstan-return InputOptionSignature
49 | */
50 | protected function getMultiDatabasesOption(): array
51 | {
52 | return ['multi-databases', null, InputOption::VALUE_NONE, 'Run migrations in all available databases', null];
53 | }
54 |
55 | /**
56 | * Tells whether `--all-contexts` should be in the command options.
57 | *
58 | * @return bool
59 | */
60 | private function isWithAllContextsOption(): bool
61 | {
62 | return true;
63 | }
64 |
65 | /**
66 | * Tells whether `--multi-databases` should be in the command options.
67 | *
68 | * @return bool
69 | */
70 | private function isWithMultiDatabasesOption(): bool
71 | {
72 | return true;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/Concerns/MigrationPaths.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | protected function getMigrationPaths(): array
25 | {
26 | /** @phpstan-ignore-next-line */
27 | if ($this->hasOnlyDefaultOption()) {
28 | return parent::getMigrationPaths();
29 | }
30 |
31 | if (false === $this->hasPathOption()) {
32 | $migrationClasses = $this->getFilesFromContext();
33 |
34 | $chosen = Arr::wrap($this->choose($migrationClasses));
35 |
36 | return $this->addAbsolutePath($chosen);
37 | }
38 |
39 | return parent::getMigrationPaths();
40 | }
41 |
42 | /**
43 | * Get migration path (either specified by '--path' option or default location).
44 | *
45 | * @return string
46 | */
47 | protected function getMigrationPath(): string
48 | {
49 | if (! empty($contextOption = $this->option('context'))) {
50 | /** @phpstan-ignore-next-line */
51 | return $this->laravel->path() . '/' .
52 | config('context.folders.domain') . '/' .
53 | $contextOption . '/' .
54 | $this->getContextComponentFolder();
55 | }
56 |
57 | return parent::getMigrationPath();
58 | }
59 |
60 | /**
61 | * Checks if the `--path` option was passed in the command.
62 | *
63 | * @return bool
64 | */
65 | protected function hasPathOption(): bool
66 | {
67 | $optionName = 'path';
68 |
69 | return $this->hasOption($optionName) && ! empty($this->option($optionName));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/Concerns/RunInMultiDatabases.php:
--------------------------------------------------------------------------------
1 | $database]);
23 |
24 | DB::purge();
25 | }
26 |
27 | /**
28 | * Run migrations in given databases.
29 | *
30 | * @param string[] $databases
31 | *
32 | * @return void
33 | */
34 | protected function runInDatabases(array $databases): void
35 | {
36 | $defaultDatabase = config('database.connections.mysql.database');
37 |
38 | foreach ($databases as $database) {
39 | $this->connectUsingDatabase($database);
40 |
41 | $this->line('');
42 | $this->comment('Database: ' . $database);
43 | $this->line('');
44 |
45 | parent::handle();
46 | }
47 |
48 | if (empty($databases)) {
49 | $this->line('');
50 | $this->comment("No database found in `config('context.migrations.databases')`");
51 | }
52 |
53 | config(['database.connections.mysql.database' => $defaultDatabase]);
54 | }
55 |
56 | /**
57 | * Execute the console command.
58 | *
59 | * @return int
60 | */
61 | public function multiDatabasesHandle(): int
62 | {
63 | $optionName = 'multi-databases';
64 |
65 | if ($this->hasOption($optionName) && boolval($this->option($optionName))) {
66 | $this->runInDatabases(Arr::wrap(config('context.migrations.databases')));
67 |
68 | return 0;
69 | }
70 |
71 | /** @phpstan-ignore-next-line */
72 | return (int) parent::handle();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/FreshCommand.php:
--------------------------------------------------------------------------------
1 | $arguments
25 | */
26 | public function call($command, array $arguments = []): int
27 | {
28 | if ($command === 'migrate') {
29 | $newArguments = array_filter([
30 | '--only-default' => $this->option('only-default'),
31 | '--context' => $this->option('context'),
32 | ]);
33 |
34 | $arguments = array_merge($arguments, $newArguments);
35 | }
36 |
37 | return parent::call($command, $arguments);
38 | }
39 |
40 | /**
41 | * Run the database seeder command.
42 | *
43 | * @param string $database
44 | *
45 | * @return void
46 | */
47 | protected function runSeeder($database): void
48 | {
49 | $noEmptyValues = array_filter([
50 | '--database' => $database,
51 | '--class' => $this->option('seeder') ?? 'Database\\Seeders\\DatabaseSeeder',
52 | '--only-default' => $this->option('only-default'),
53 | '--context' => $this->option('context'),
54 | '--force' => true,
55 | ]);
56 |
57 | $this->call('db:seed', $noEmptyValues);
58 | }
59 |
60 | /**
61 | * Tells whether `--all-contexts` should be in the command options.
62 | *
63 | * @return bool
64 | */
65 | private function isWithAllContextsOption(): bool
66 | {
67 | return false;
68 | }
69 |
70 | /**
71 | * Tells whether `--multi-databases` should be in the command options.
72 | *
73 | * @return bool
74 | */
75 | private function isWithMultiDatabasesOption(): bool
76 | {
77 | return false;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/MigrateCommand.php:
--------------------------------------------------------------------------------
1 | getMigrationOptions() as $options) {
31 | if ($options instanceof InputOption) {
32 | $this->getDefinition()->addOption($options); /** @phpstan-ignore-line */
33 | } else { /** @phpstan-ignore-line */
34 | /** @phpstan-var InputOptionSignature */
35 | $optionData = array_values($options);
36 | $this->addOption(...$optionData);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/MigrateMakeCommand.php:
--------------------------------------------------------------------------------
1 | $arguments
25 | */
26 | public function call($command, array $arguments = []): int
27 | {
28 | if (in_array($command, ['migrate', 'migrate:rollback', 'migrate:reset'], true)) {
29 | $newArguments = array_filter([
30 | '--only-default' => $this->option('only-default'),
31 | '--multi-databases' => $this->option('multi-databases'),
32 | '--context' => $this->option('context'),
33 | ]);
34 |
35 | $arguments = array_merge($arguments, $newArguments);
36 | }
37 |
38 | return parent::call($command, $arguments);
39 | }
40 |
41 | /**
42 | * Run the database seeder command.
43 | *
44 | * @param string $database
45 | *
46 | * @return void
47 | */
48 | protected function runSeeder($database): void
49 | {
50 | $noEmptyValues = array_filter([
51 | '--database' => $database,
52 | '--class' => $this->option('seeder') ?? 'Database\\Seeders\\DatabaseSeeder',
53 | '--only-default' => $this->option('only-default'),
54 | '--context' => $this->option('context'),
55 | '--force' => true,
56 | ]);
57 |
58 | $this->call('db:seed', $noEmptyValues);
59 | }
60 |
61 | /**
62 | * Tells whether `--all-contexts` should be in the command options.
63 | *
64 | * @return bool
65 | */
66 | private function isWithAllContextsOption(): bool
67 | {
68 | return false;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Commands/Database/Migrations/ResetCommand.php:
--------------------------------------------------------------------------------
1 | getMigrationOptions();
29 |
30 | $forceOption = ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production', null];
31 |
32 | array_push($options, $forceOption);
33 |
34 | return $options;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Commands/Database/Seeds/SeedCommand.php:
--------------------------------------------------------------------------------
1 | $classIterator
29 | *
30 | * @return \Allyson\ArtisanDomainContext\Tools\ClassIterator
31 | */
32 | protected function handleClassIterator(ClassIterator $classIterator): ClassIterator
33 | {
34 | return $classIterator->type(Seeder::class);
35 | }
36 |
37 | /**
38 | * Handles the `Finder` class for filtering files.
39 | *
40 | * @param \Symfony\Component\Finder\Finder $finder
41 | *
42 | * @return void
43 | */
44 | // protected function decorateFinder(Finder $finder): void
45 | // {
46 | // $finder->name('/Seeder\.php$/i')->sortByName(true);
47 | // }
48 |
49 | /**
50 | * Get a seeder instance from the container.
51 | *
52 | * @return \Illuminate\Database\Seeder|object
53 | */
54 | protected function getSeeder() /** @phpstan-ignore-line */
55 | {
56 | if ($this->hasOnlyDefaultOption()) {
57 | return parent::getSeeder();
58 | }
59 |
60 | /** @var \Illuminate\Support\Collection<\ReflectionClass> */
61 | $seedersClasses = $this->getClassesFromContext();
62 |
63 | /** @var array */
64 | $seedersClasses = $this->transformReflectionClassIntoArrayOfClassNames($seedersClasses);
65 |
66 | /** @var array> */
67 | $chosen = $this->choose($seedersClasses);
68 |
69 | $handlerSeedersClasses = $this->dummyClass();
70 |
71 | /** @phpstan-ignore-next-line */
72 | $handlerSeedersClasses->setSeedersClasses($chosen);
73 |
74 | return $handlerSeedersClasses;
75 | }
76 |
77 | /**
78 | * Get the console command options.
79 | *
80 | * @return array
81 | *
82 | * @phpstan-return InputOptionsArray
83 | */
84 | protected function getOptions(): array
85 | {
86 | $options = parent::getOptions();
87 |
88 | array_push($options, ...$this->withAllFiltersOption());
89 |
90 | return $options;
91 | }
92 |
93 | /**
94 | * @phpstan-ignore-next-line
95 | *
96 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
97 | */
98 | private function dummyClass(): object
99 | {
100 | return new class ($this) {
101 | /**
102 | * @var array>
103 | */
104 | private array $seedersClasses;
105 |
106 | public function __construct(private SeedCommand $command)
107 | {
108 | }
109 |
110 | /**
111 | * @param string[] $seedersClasses
112 | *
113 | * @phpstan-param array> $seedersClasses
114 | */
115 | public function setSeedersClasses(array $seedersClasses): void
116 | {
117 | $this->seedersClasses = $seedersClasses;
118 | }
119 |
120 | public function __invoke(): void
121 | {
122 | /** @var string&class-string $seederClass */
123 | foreach ($this->seedersClasses as $seederClass) {
124 | if (! class_exists($seederClass)) {
125 | throw new ClassNotFoundException($seederClass);
126 | }
127 |
128 | /** @phpstan-ignore-next-line */
129 | $this->command
130 | ->getLaravel()
131 | ->make($seederClass)
132 | ->setContainer($this->command->getLaravel())
133 | ->setCommand($this->command)
134 | ->__invoke();
135 | }
136 | }
137 | };
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Commands/Database/Seeds/SeederMakeCommand.php:
--------------------------------------------------------------------------------
1 | getCustomContextNamespace();
44 |
45 | if ($namespace === trim($this->rootNamespace(), '\\')) {
46 | $namespace = 'Database\\Seeders';
47 | }
48 |
49 | $replace = [
50 | '{{ seederNamespace }}' => $namespace,
51 | ];
52 |
53 | $searchByValue = array_keys($replace);
54 | $substituteValue = array_values($replace);
55 | $stringToSearchValue = GeneratorCommand::buildClass($name);
56 |
57 | return str_replace($searchByValue, $substituteValue, $stringToSearchValue);
58 | }
59 |
60 | /**
61 | * Get the destination class path.
62 | *
63 | * @param string $name
64 | *
65 | * @return string
66 | */
67 | protected function getPath($name): string
68 | {
69 | if (! empty($this->contextOption())) {
70 | return GeneratorCommand::getPath($name);
71 | }
72 |
73 | $name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '');
74 |
75 | return $this->laravel->databasePath() . '/seeders/' . str_replace('\\', '/', $name) . '.php';
76 | }
77 |
78 | /**
79 | * Parse the class name and format according to the root namespace.
80 | *
81 | * @param string $name
82 | *
83 | * @return string
84 | */
85 | protected function qualifyClass($name): string
86 | {
87 | return GeneratorCommand::qualifyClass($name);
88 | }
89 |
90 | /**
91 | * Get the root namespace for the class.
92 | *
93 | * @return string
94 | */
95 | protected function rootNamespace()
96 | {
97 | return $this->laravel->getNamespace();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Commands/Database/Seeds/stubs/seeder.stub:
--------------------------------------------------------------------------------
1 | rootNamespace(), '\\');
27 |
28 | if (! empty($contextOption = $this->contextOption())) {
29 | $context = $rootNamespace . '\\' .
30 | config('context.folders.domain') . '\\' .
31 | $contextOption . '\\';
32 |
33 | if (! empty($componentFolder)) {
34 | return $context . $componentFolder;
35 | }
36 |
37 | return $context . $this->getComponentFolderNamespace();
38 | }
39 |
40 | return parent::getDefaultNamespace($rootNamespace);
41 | }
42 |
43 | /**
44 | * Returns the custom namespace of the class.
45 | *
46 | * @return string
47 | */
48 | protected function getCustomContextNamespace(): string
49 | {
50 | $contextNamespaceOption = $this->contextNamespaceOption();
51 |
52 | if (! empty($contextNamespaceOption)) {
53 | return trim($contextNamespaceOption, '\\') . '\\' . $this->getComponentFolderNamespace();
54 | }
55 |
56 | return $this->getContextNamespace();
57 | }
58 |
59 | /**
60 | * Returns only the namespace part of the component.
61 | *
62 | * @return string
63 | */
64 | protected function getComponentFolderNamespace(): string
65 | {
66 | return str_replace('/', '\\', $this->getContextComponentFolderNamespace());
67 | }
68 |
69 | /**
70 | * Get the default namespace for the class.
71 | *
72 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
73 | *
74 | * @param string $rootNamespace
75 | *
76 | * @return string
77 | */
78 | protected function getDefaultNamespace($rootNamespace): string // phpcs:ignore
79 | {
80 | return $this->getContextNamespace();
81 | }
82 |
83 | /**
84 | * Get the full namespace for a given class, without the class name.
85 | *
86 | * @param string $fullNamespace
87 | *
88 | * @return string
89 | */
90 | protected function getNamespace($fullNamespace): string
91 | {
92 | $contextNamespaceOption = $this->contextNamespaceOption();
93 |
94 | if (! empty($contextNamespaceOption)) {
95 | return trim($contextNamespaceOption, '\\') . '\\' . $this->getComponentFolderNamespace();
96 | }
97 |
98 | return parent::getNamespace($fullNamespace);
99 | }
100 |
101 | /**
102 | * Checks if the `--context` option exists and retrieves its value if this option is passed in the command.
103 | *
104 | * @return string|bool
105 | */
106 | protected function contextOption(): string|bool
107 | {
108 | $optionName = 'context';
109 |
110 | if ($this->hasOption($optionName) && ! empty($contextOption = $this->option($optionName))) {
111 | return trim($contextOption);
112 | }
113 |
114 | return false;
115 | }
116 |
117 | /**
118 | * Checks if the `--context-namespace` option exists and
119 | * retrieves its value if this option is passed in the command.
120 | *
121 | * @return string|bool
122 | */
123 | protected function contextNamespaceOption(): string|bool
124 | {
125 | $optionName = 'context-namespace';
126 |
127 | if ($this->hasOption($optionName) && ! empty($contextNamespaceOption = $this->option($optionName))) {
128 | return trim($contextNamespaceOption);
129 | }
130 |
131 | return false;
132 | }
133 |
134 | /**
135 | * Replace the class name for the given stub.
136 | *
137 | * @param string $stub
138 | * @param string $name
139 | *
140 | * @return string
141 | */
142 | protected function replaceClass($stub, $name): string
143 | {
144 | $partsFullClassName = explode('\\', $name);
145 | $className = end($partsFullClassName);
146 |
147 | return str_replace(['DummyClass', '{{ class }}', '{{class}}'], $className, $stub);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/Commands/Foundation/Concerns/CommonMakeOptions.php:
--------------------------------------------------------------------------------
1 | getContextNamespaceOption());
21 |
22 | return $options;
23 | }
24 |
25 | /**
26 | * Retrieves `--context-namespace` option data.
27 | *
28 | * @return \Symfony\Component\Console\Input\InputOption
29 | */
30 | protected function getContextNamespaceOption(): InputOption
31 | {
32 | $description = 'Namespace alias used in context classes';
33 |
34 | return new InputOption(name: 'context-namespace', mode: InputOption::VALUE_OPTIONAL, description: $description);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Commands/Foundation/ConsoleMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceDummyClass($stub, $name);
35 |
36 | /** @phpstan-ignore-next-line */
37 | return str_replace(['dummy:command', '{{ command }}'], $this->option('command'), $stub);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Commands/Foundation/EventMakeCommand.php:
--------------------------------------------------------------------------------
1 | $arguments
21 | */
22 | public function call($command, array $arguments = []): int
23 | {
24 | $contextArguments = array_filter([
25 | '--context-namespace' => $this->option('context-namespace'),
26 | '--context' => $this->option('context'),
27 | ]);
28 |
29 | $arguments = array_merge($arguments, $contextArguments);
30 |
31 | return parent::call($command, $arguments);
32 | }
33 |
34 | /**
35 | * Retrieves the name of the folder that is used in the namespace.
36 | *
37 | * @return string
38 | */
39 | protected function getContextComponentFolderNamespace(): string
40 | {
41 | return config('context.folders.components.models');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Commands/Foundation/NotificationMakeCommand.php:
--------------------------------------------------------------------------------
1 | rootNamespace();
36 |
37 | if (str_starts_with($model, $rootNamespace)) {
38 | return $model;
39 | }
40 |
41 | if (! empty($this->contextOption())) {
42 | $modelsComponentFolder = strval(config('context.folders.components.models'));
43 |
44 | return $this->getContextNamespace($modelsComponentFolder) . "\\{$model}";
45 | }
46 |
47 | return is_dir(app_path('Models'))
48 | ? $rootNamespace . 'Models\\' . $model
49 | : $rootNamespace . $model;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Commands/Foundation/ProviderMakeCommand.php:
--------------------------------------------------------------------------------
1 | artisan)) {
17 | $consoleApplication = (new ArtisanApplication($this->app, $this->events, $this->app->version()));
18 |
19 | $this->artisan = $consoleApplication->resolveCommands($this->commands)
20 | ->setContainerCommandLoader();
21 |
22 | return $this->artisan;
23 | }
24 |
25 | return $this->artisan;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Console/Application.php:
--------------------------------------------------------------------------------
1 | execute();
23 | }
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Contracts/Inputable.php:
--------------------------------------------------------------------------------
1 | classname = $classname;
19 | }
20 |
21 | public function getClassname(): string
22 | {
23 | return $this->classname;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Exceptions/ReaderException.php:
--------------------------------------------------------------------------------
1 | $arguments
27 | */
28 | public function __call(string $name, array $arguments)
29 | {
30 | return $this->forwardCallTo($this->definition, $name, $arguments);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Inputs/Context.php:
--------------------------------------------------------------------------------
1 | definition->addOption($this->withOption());
21 | }
22 |
23 | /**
24 | * Retrieves command option data.
25 | *
26 | * @param int $behavior
27 | *
28 | * @return \Symfony\Component\Console\Input\InputOption
29 | */
30 | public function withOption(int $behavior = InputOption::VALUE_OPTIONAL): InputOption
31 | {
32 | $message = 'The context the command should run under';
33 |
34 | return new InputOption('context', null, $behavior, $message);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Inputs/Host.php:
--------------------------------------------------------------------------------
1 | definition->addOption($this->withOption());
21 | }
22 |
23 | /**
24 | * Retrieves command option data.
25 | *
26 | * @param int $behavior
27 | *
28 | * @return \Symfony\Component\Console\Input\InputOption
29 | */
30 | public function withOption(int $behavior = InputOption::VALUE_OPTIONAL): InputOption
31 | {
32 | $message = 'The Host/Domain the command should run under';
33 |
34 | return new InputOption('host', null, $behavior, $message);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Inputs/Tenant.php:
--------------------------------------------------------------------------------
1 | definition->addOption($this->withOption());
21 | }
22 |
23 | /**
24 | * Retrieves command option data.
25 | *
26 | * @param int $behavior
27 | *
28 | * @return \Symfony\Component\Console\Input\InputOption
29 | */
30 | public function withOption(int $behavior = InputOption::VALUE_OPTIONAL): InputOption
31 | {
32 | $message = 'The tenant the command should be run for';
33 |
34 | return new InputOption('tenant', null, $behavior, $message);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Providers/ArtisanServiceProvider.php:
--------------------------------------------------------------------------------
1 |
40 | */
41 | protected $commands = [
42 | 'Seed' => 'command.seed',
43 | 'CastMake' => 'command.cast.make',
44 | 'ChannelMake' => 'command.channel.make',
45 | 'ConsoleMake' => 'command.console.make',
46 | 'EventMake' => 'command.event.make',
47 | 'ExceptionMake' => 'command.exception.make',
48 | 'FactoryMake' => 'command.factory.make',
49 | 'JobMake' => 'command.job.make',
50 | 'ListenerMake' => 'command.listener.make',
51 | 'MailMake' => 'command.mail.make',
52 | 'MiddlewareMake' => 'command.middleware.make',
53 | 'ModelMake' => 'command.model.make',
54 | 'NotificationMake' => 'command.notification.make',
55 | 'ObserverMake' => 'command.observer.make',
56 | 'PolicyMake' => 'command.policy.make',
57 | 'ProviderMake' => 'command.provider.make',
58 | 'RequestMake' => 'command.request.make',
59 | 'ResourceMake' => 'command.resource.make',
60 | 'RuleMake' => 'command.rule.make',
61 | 'SeederMake' => 'command.seeder.make',
62 | ];
63 |
64 | /**
65 | * Register the service provider.
66 | *
67 | * @return void
68 | */
69 | public function register(): void
70 | {
71 | $this->registerCommands($this->commands);
72 | }
73 |
74 | /**
75 | * Get the services provided by the provider.
76 | *
77 | * @codeCoverageIgnore
78 | *
79 | * @return string[]
80 | */
81 | public function provides(): array
82 | {
83 | return array_values($this->commands);
84 | }
85 |
86 | /**
87 | * Register the given commands.
88 | *
89 | * @param array $commands
90 | *
91 | * @return void
92 | *
93 | * @phpstan-param array $commands
94 | */
95 | protected function registerCommands(array $commands): void
96 | {
97 | foreach ($commands as $command => $abstract) {
98 | /** @phpstan-ignore-next-line */
99 | $this->{"register{$command}Command"}($abstract);
100 | }
101 |
102 | $commands = array_values($commands);
103 |
104 | $this->commands($commands);
105 |
106 | // if ($this->app->runningInConsole()) {
107 | // $closure = function (\Illuminate\Foundation\Console\ModelMakeCommand $command) {
108 | // return new App\ModelMakeCommand($this->app['files']);
109 | // };
110 |
111 | // $this->app->extend('command.model.make', $closure);
112 | // }
113 | }
114 |
115 | /**
116 | * Register the command.
117 | *
118 | * @param string $abstract
119 | *
120 | * @return void
121 | */
122 | protected function registerSeedCommand(string $abstract): void
123 | {
124 | $this->app->singleton($abstract, fn ($app) => new SeedCommand($app['db']));
125 |
126 | $this->app->extend($abstract, function (SeedCommand $command) {
127 | return $command->addOption('size', 's', InputOption::VALUE_REQUIRED, 'The size of the seeding', 10);
128 | });
129 | }
130 |
131 | /**
132 | * Register the command.
133 | *
134 | * @param string $abstract
135 | *
136 | * @return void
137 | */
138 | protected function registerCastMakeCommand(string $abstract): void
139 | {
140 | $this->app->singleton($abstract, fn ($app) => new CastMakeCommand($app['files']));
141 | }
142 |
143 | /**
144 | * Register the command.
145 | *
146 | * @param string $abstract
147 | *
148 | * @return void
149 | */
150 | protected function registerChannelMakeCommand(string $abstract): void
151 | {
152 | $this->app->singleton($abstract, fn ($app) => new ChannelMakeCommand($app['files']));
153 | }
154 |
155 | /**
156 | * Register the command.
157 | *
158 | * @param string $abstract
159 | *
160 | * @return void
161 | */
162 | protected function registerConsoleMakeCommand(string $abstract): void
163 | {
164 | $this->app->singleton($abstract, fn ($app) => new ConsoleMakeCommand($app['files']));
165 | }
166 |
167 | /**
168 | * Register the command.
169 | *
170 | * @param string $abstract
171 | *
172 | * @return void
173 | */
174 | protected function registerEventMakeCommand(string $abstract): void
175 | {
176 | $this->app->singleton($abstract, fn ($app) => new EventMakeCommand($app['files']));
177 | }
178 |
179 | /**
180 | * Register the command.
181 | *
182 | * @param string $abstract
183 | *
184 | * @return void
185 | */
186 | protected function registerExceptionMakeCommand(string $abstract): void
187 | {
188 | $this->app->singleton($abstract, fn ($app) => new ExceptionMakeCommand($app['files']));
189 | }
190 |
191 | /**
192 | * Register the command.
193 | *
194 | * @param string $abstract
195 | *
196 | * @return void
197 | */
198 | protected function registerFactoryMakeCommand(string $abstract): void
199 | {
200 | $this->app->singleton($abstract, fn ($app) => new FactoryMakeCommand($app['files']));
201 | }
202 |
203 | /**
204 | * Register the command.
205 | *
206 | * @param string $abstract
207 | *
208 | * @return void
209 | */
210 | protected function registerJobMakeCommand(string $abstract): void
211 | {
212 | $this->app->singleton($abstract, fn ($app) => new JobMakeCommand($app['files']));
213 | }
214 |
215 | /**
216 | * Register the command.
217 | *
218 | * @param string $abstract
219 | *
220 | * @return void
221 | */
222 | protected function registerListenerMakeCommand(string $abstract): void
223 | {
224 | $this->app->singleton($abstract, fn ($app) => new ListenerMakeCommand($app['files']));
225 | }
226 |
227 | /**
228 | * Register the command.
229 | *
230 | * @param string $abstract
231 | *
232 | * @return void
233 | */
234 | protected function registerMailMakeCommand(string $abstract): void
235 | {
236 | $this->app->singleton($abstract, fn ($app) => new MailMakeCommand($app['files']));
237 | }
238 |
239 | /**
240 | * Register the command.
241 | *
242 | * @param string $abstract
243 | *
244 | * @return void
245 | */
246 | protected function registerMiddlewareMakeCommand(string $abstract): void
247 | {
248 | $this->app->singleton($abstract, fn ($app) => new MiddlewareMakeCommand($app['files']));
249 | }
250 |
251 | /**
252 | * Register the command.
253 | *
254 | * @param string $abstract
255 | *
256 | * @return void
257 | */
258 | protected function registerModelMakeCommand(string $abstract): void
259 | {
260 | $this->app->singleton($abstract, fn ($app) => new ModelMakeCommand($app['files']));
261 | }
262 |
263 | /**
264 | * Register the command.
265 | *
266 | * @param string $abstract
267 | *
268 | * @return void
269 | */
270 | protected function registerNotificationMakeCommand(string $abstract): void
271 | {
272 | $this->app->singleton($abstract, fn ($app) => new NotificationMakeCommand($app['files']));
273 | }
274 |
275 | /**
276 | * Register the command.
277 | *
278 | * @param string $abstract
279 | *
280 | * @return void
281 | */
282 | protected function registerObserverMakeCommand(string $abstract): void
283 | {
284 | $this->app->singleton($abstract, fn ($app) => new ObserverMakeCommand($app['files']));
285 | }
286 |
287 | /**
288 | * Register the command.
289 | *
290 | * @param string $abstract
291 | *
292 | * @return void
293 | */
294 | protected function registerPolicyMakeCommand(string $abstract): void
295 | {
296 | $this->app->singleton($abstract, fn ($app) => new PolicyMakeCommand($app['files']));
297 | }
298 |
299 | /**
300 | * Register the command.
301 | *
302 | * @param string $abstract
303 | *
304 | * @return void
305 | */
306 | protected function registerProviderMakeCommand(string $abstract): void
307 | {
308 | $this->app->singleton($abstract, fn ($app) => new ProviderMakeCommand($app['files']));
309 | }
310 |
311 | /**
312 | * Register the command.
313 | *
314 | * @param string $abstract
315 | *
316 | * @return void
317 | */
318 | protected function registerRequestMakeCommand(string $abstract): void
319 | {
320 | $this->app->singleton($abstract, fn ($app) => new RequestMakeCommand($app['files']));
321 | }
322 |
323 | /**
324 | * Register the command.
325 | *
326 | * @param string $abstract
327 | *
328 | * @return void
329 | */
330 | protected function registerResourceMakeCommand(string $abstract): void
331 | {
332 | $this->app->singleton($abstract, fn ($app) => new ResourceMakeCommand($app['files']));
333 | }
334 |
335 | /**
336 | * Register the command.
337 | *
338 | * @param string $abstract
339 | *
340 | * @return void
341 | */
342 | protected function registerRuleMakeCommand(string $abstract): void
343 | {
344 | $this->app->singleton($abstract, fn ($app) => new RuleMakeCommand($app['files']));
345 | }
346 |
347 | /**
348 | * Register the command.
349 | *
350 | * @param string $abstract
351 | *
352 | * @return void
353 | */
354 | protected function registerSeederMakeCommand(string $abstract): void
355 | {
356 | $this->app->singleton($abstract, fn ($app) => new SeederMakeCommand($app['files']));
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/Providers/MigrationServiceProvider.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | protected $commands = [
24 | 'Migrate' => 'command.migrate',
25 | 'MigrateFresh' => 'command.migrate.fresh',
26 | 'MigrateRefresh' => 'command.migrate.refresh',
27 | 'MigrateReset' => 'command.migrate.reset',
28 | 'MigrateRollback' => 'command.migrate.rollback',
29 | 'MigrateStatus' => 'command.migrate.status',
30 | 'MigrateMake' => 'command.migrate.make',
31 | ];
32 |
33 | /**
34 | * Register the service provider.
35 | *
36 | * @return void
37 | */
38 | public function register(): void
39 | {
40 | $this->registerCommands($this->commands);
41 | }
42 |
43 | /**
44 | * Get the services provided by the provider.
45 | *
46 | * @codeCoverageIgnore
47 | *
48 | * @return string[]
49 | */
50 | public function provides(): array
51 | {
52 | return array_values($this->commands);
53 | }
54 |
55 | /**
56 | * Register the given commands.
57 | *
58 | * @param array $commands
59 | *
60 | * @return void
61 | *
62 | * @phpstan-param array $commands
63 | */
64 | protected function registerCommands(array $commands): void
65 | {
66 | foreach ($commands as $command => $abstract) {
67 | /** @phpstan-ignore-next-line */
68 | $this->{"register{$command}Command"}($abstract);
69 | }
70 |
71 | $commands = array_values($commands);
72 |
73 | $this->commands($commands);
74 | }
75 |
76 | /**
77 | * Register the command.
78 | *
79 | * @param string $abstract
80 | *
81 | * @return void
82 | */
83 | protected function registerMigrateCommand(string $abstract): void
84 | {
85 | $this->app->singleton($abstract, fn ($app) => new MigrateCommand($app['migrator'], $app[Dispatcher::class]));
86 | }
87 |
88 | /**
89 | * Register the command.
90 | *
91 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
92 | *
93 | * @param string $abstract
94 | *
95 | * @return void
96 | */
97 | protected function registerMigrateFreshCommand(string $abstract): void
98 | {
99 | $this->app->singleton($abstract, fn () => new FreshCommand());
100 | }
101 |
102 | /**
103 | * Register the command.
104 | *
105 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
106 | *
107 | * @param string $abstract
108 | *
109 | * @return void
110 | */
111 | protected function registerMigrateRefreshCommand(string $abstract): void
112 | {
113 | $this->app->singleton($abstract, fn () => new RefreshCommand());
114 | }
115 |
116 | /**
117 | * Register the command.
118 | *
119 | * @param string $abstract
120 | *
121 | * @return void
122 | */
123 | protected function registerMigrateResetCommand(string $abstract): void
124 | {
125 | $this->app->singleton($abstract, fn ($app) => new ResetCommand($app['migrator']));
126 | }
127 |
128 | /**
129 | * Register the command.
130 | *
131 | * @param string $abstract
132 | *
133 | * @return void
134 | */
135 | protected function registerMigrateRollbackCommand(string $abstract): void
136 | {
137 | $this->app->singleton($abstract, fn ($app) => new RollbackCommand($app['migrator']));
138 | }
139 |
140 | /**
141 | * Register the command.
142 | *
143 | * @param string $abstract
144 | *
145 | * @return void
146 | */
147 | protected function registerMigrateStatusCommand(string $abstract): void
148 | {
149 | $this->app->singleton($abstract, fn ($app) => new StatusCommand($app['migrator']));
150 | }
151 |
152 | /**
153 | * Register the command.
154 | *
155 | * @param string $abstract
156 | *
157 | * @return void
158 | */
159 | protected function registerMigrateMakeCommand(string $abstract): void
160 | {
161 | $closure = fn ($app) => new MigrateMakeCommand($app['migration.creator'], $app['composer']);
162 |
163 | $this->app->singleton($abstract, $closure);
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Tools/ClassIterator.php:
--------------------------------------------------------------------------------
1 |
28 | */
29 | private array $classes = [];
30 |
31 | private ?SplFileInfo $currentFile;
32 |
33 | public function __construct(private Finder $finder)
34 | {
35 | $this->customAutoloader();
36 |
37 | $this->handleFiles($this->finder);
38 | }
39 |
40 | /**
41 | * Get an iterator for the items.
42 | *
43 | * @return \ArrayIterator
44 | */
45 | public function getIterator(): Traversable
46 | {
47 | return new ArrayIterator($this->classes);
48 | }
49 |
50 | /**
51 | * Filters classes according to their type.
52 | * - Implements certain interface or is a child class.
53 | *
54 | * @param string $objectType
55 | *
56 | * @phpstan-return static<$this>
57 | */
58 | public function type(string $objectType): static
59 | {
60 | /** @var \ReflectionClass $reflectedClass */
61 | foreach ($this->classes as $qualifiedClassName => $reflectedClass) {
62 | if (! $reflectedClass->isSubclassOf($objectType)) {
63 | $this->removeClass($qualifiedClassName);
64 | }
65 | }
66 |
67 | return $this;
68 | }
69 |
70 | /**
71 | * Filters classes by name.
72 | *
73 | * @param string $pattern
74 | *
75 | * @phpstan-return static<$this>
76 | */
77 | public function name(string $pattern): static
78 | {
79 | /** @phpstan-var array */
80 | $qualifiedClassNames = array_keys($this->classes);
81 |
82 | foreach ($qualifiedClassNames as $className) {
83 | $subject = $className;
84 | $partsOfClassName = explode('\\', $className);
85 | $subject = array_pop($partsOfClassName);
86 |
87 | if (empty(Str::match($pattern, $subject))) {
88 | $this->removeClass($className);
89 | }
90 | }
91 |
92 | return $this;
93 | }
94 |
95 | /**
96 | * Filters classes by fully qualified namespace.
97 | *
98 | * @param string $namespace
99 | *
100 | * @phpstan-return static<$this>
101 | */
102 | public function inNamespace(string $namespace): static
103 | {
104 | /** @var \ReflectionClass $reflectedClass */
105 | foreach ($this->classes as $qualifiedClassName => $reflectedClass) {
106 | $pattern = '/^' . preg_quote($namespace) . '/';
107 |
108 | if (empty(Str::match($pattern, $reflectedClass->getNamespaceName()))) {
109 | $this->removeClass($qualifiedClassName);
110 | }
111 | }
112 |
113 | return $this;
114 | }
115 |
116 | /**
117 | * Register given function as __autoload() implementation.
118 | *
119 | * @return void
120 | */
121 | private function customAutoloader(): void
122 | {
123 | // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
124 | spl_autoload_register(function ($className) {
125 | if (! empty($this->currentFile)) {
126 | require_once $this->currentFile->getRealPath();
127 | }
128 | });
129 | }
130 |
131 | /**
132 | * Manipulate files to retrieve class/type information.
133 | *
134 | * @param \Symfony\Component\Finder\Finder $finder
135 | *
136 | * @return void
137 | *
138 | * @throws \Allyson\ArtisanDomainContext\Exceptions\ReaderException
139 | */
140 | private function handleFiles(Finder $finder): void
141 | {
142 | $parserFactory = new ParserFactory();
143 |
144 | /** @var \Symfony\Component\Finder\SplFileInfo $splFileInfo */
145 | foreach ($finder as $splFileInfo) {
146 | $this->currentFile = $splFileInfo;
147 |
148 | /** @var \PhpParser\Parser\Multiple */
149 | $parser = $parserFactory->create(ParserFactory::PREFER_PHP7);
150 |
151 | try {
152 | /** @var \PhpParser\Node\Stmt[] */
153 | $ast = $parser->parse($splFileInfo->getContents());
154 |
155 | $this->parseFile($ast);
156 | } catch (PhpParserError $error) { // @codeCoverageIgnore
157 | throw new ReaderException("Parse error: {$error->getMessage()}"); // @codeCoverageIgnore
158 | } finally {
159 | $this->currentFile = null;
160 | }
161 | }
162 | }
163 |
164 | /**
165 | * @param \PhpParser\Node\Stmt[] $ast
166 | *
167 | * @return void
168 | */
169 | private function parseFile(array $ast): void
170 | {
171 | /** @var \PhpParser\Node\Stmt $nodeStmt */
172 | foreach ($ast as $nodeStmt) {
173 | if ($nodeStmt instanceof Namespace_) {
174 | /** @var \PhpParser\Node\Name */
175 | $nodeStmtName = $nodeStmt->name;
176 |
177 | $classNamespace = $nodeStmtName->toString();
178 |
179 | $this->handleClasses($nodeStmt->stmts, $classNamespace);
180 | }
181 | }
182 | }
183 |
184 | /**
185 | * @param \PhpParser\Node\Stmt[] $stmts
186 | * @param string $classNamespace
187 | *
188 | * @return void
189 | */
190 | private function handleClasses(array $stmts, string $classNamespace): void
191 | {
192 | foreach ($stmts as $nodeClass) {
193 | $allowedNodes = $nodeClass instanceof Class_ ||
194 | $nodeClass instanceof Interface_ ||
195 | $nodeClass instanceof Trait_;
196 |
197 | if ($allowedNodes) {
198 | /** @var \PhpParser\Node\Identifier */
199 | $stmtClass = $nodeClass->name;
200 |
201 | // Remove leading backslashes - So that it is not a fully qualified class!
202 | /** @phpstan-var class-string */
203 | $qualifiedClassName = ltrim("{$classNamespace}\\{$stmtClass->name}", '\\');
204 |
205 | $class = new ReflectionClass($qualifiedClassName);
206 |
207 | $this->classes[$qualifiedClassName] = $class;
208 | }
209 | }
210 | }
211 |
212 | /**
213 | * Remove class from class list.
214 | *
215 | * @param string $className
216 | *
217 | * @return void
218 | */
219 | private function removeClass(string $className): void
220 | {
221 | unset($this->classes[$className]);
222 | }
223 | }
224 |
--------------------------------------------------------------------------------