├── 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 | Social Card of Laravel Artisan Domain Contexts 5 |

6 | 7 | [![PHP Version][ico-php]][link-php] 8 | [![Laravel Version][ico-laravel]][link-laravel] 9 | [![CI Status][ico-actions]][link-actions] 10 | [![PHPCS - GitHub Workflow Status](https://img.shields.io/github/workflow/status/allysonsilva/laravel-artisan-domain-contexts/PHP%20CodeSniffer%20-%20Coding%20Standards?label=PHPCS&logo=github)](https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/phpcs.yml) 11 | [![PHPMD - GitHub Workflow Status](https://img.shields.io/github/workflow/status/allysonsilva/laravel-artisan-domain-contexts/PHPMD%20-%20PHP%20Mess%20Detector?label=PHPMD&logo=github)](https://github.com/allysonsilva/laravel-artisan-domain-contexts/actions/workflows/phpmd.yml) 12 | [![PHPStan - GitHub Workflow Status](https://img.shields.io/github/workflow/status/allysonsilva/laravel-artisan-domain-contexts/PHPStan%20-%20Code%20Static%20Analysis?label=PHPStan&logo=github)](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 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg)](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 | 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 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 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 |
CommandsAdditional Command Options
--context--context-namespace--all-contexts--only-default--multi-databases
make:cast✔️✔️
make:channel✔️✔️
make:command✔️✔️
make:event✔️✔️
make:exception✔️✔️
make:factory✔️✔️
make:factory✔️✔️
make:job✔️✔️
make:listener✔️✔️
make:mail✔️✔️
make:middleware✔️✔️
make:migration✔️✔️
make:model✔️✔️
make:notification✔️✔️
make:observer✔️✔️
make:policy✔️✔️
make:provider✔️✔️
make:request✔️✔️
make:resource✔️✔️
make:rule✔️✔️
make:seeder✔️✔️
migrate:fresh✔️✔️
migrate:refresh✔️✔️✔️
migrate:reset✔️✔️✔️✔️
migrate:rollback✔️✔️✔️✔️
migrate:status✔️✔️✔️✔️
migrate✔️✔️✔️✔️
db:seed✔️✔️✔️
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 | --------------------------------------------------------------------------------