├── LICENSE.md ├── README.md ├── composer.json └── src ├── Commands ├── Concerns │ ├── AskToRunMigrations.php │ ├── AskToStarRepoOnGitHub.php │ ├── PublishesResources.php │ ├── SupportsServiceProviderInApp.php │ └── SupportsStartWithEndWith.php └── InstallCommand.php ├── Concerns ├── Package │ ├── HasAssets.php │ ├── HasBladeComponents.php │ ├── HasCommands.php │ ├── HasConfigs.php │ ├── HasInertia.php │ ├── HasInstallCommand.php │ ├── HasMigrations.php │ ├── HasRoutes.php │ ├── HasServiceProviders.php │ ├── HasTranslations.php │ ├── HasViewComposers.php │ ├── HasViewSharedData.php │ └── HasViews.php └── PackageServiceProvider │ ├── ProcessAssets.php │ ├── ProcessBladeComponents.php │ ├── ProcessCommands.php │ ├── ProcessConfigs.php │ ├── ProcessInertia.php │ ├── ProcessMigrations.php │ ├── ProcessRoutes.php │ ├── ProcessServiceProviders.php │ ├── ProcessTranslations.php │ ├── ProcessViewComposers.php │ ├── ProcessViewSharedData.php │ └── ProcessViews.php ├── Exceptions └── InvalidPackage.php ├── Package.php └── PackageServiceProvider.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-package-tools 6 | 7 | 8 | 9 |

Tools for creating Laravel packages

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-package-tools.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-package-tools) 12 | ![Tests](https://github.com/spatie/laravel-package-tools/workflows/Tests/badge.svg) 13 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-package-tools.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-package-tools) 14 | 15 |
16 | 17 | This package contains a `PackageServiceProvider` that you can use in your packages to easily register config files, 18 | migrations, and more. 19 | 20 | Here's an example of how it can be used. 21 | 22 | ```php 23 | use Spatie\LaravelPackageTools\PackageServiceProvider; 24 | use Spatie\LaravelPackageTools\Package; 25 | use MyPackage\ViewComponents\Alert; 26 | use Spatie\LaravelPackageTools\Commands\Concerns; 27 | 28 | class YourPackageServiceProvider extends PackageServiceProvider 29 | { 30 | public function configurePackage(Package $package): void 31 | { 32 | $package 33 | ->name('your-package-name') 34 | ->hasConfigFile() 35 | ->hasViews() 36 | ->hasViewComponent('spatie', Alert::class) 37 | ->hasViewComposer('*', MyViewComposer::class) 38 | ->sharesDataWithAllViews('downloads', 3) 39 | ->hasTranslations() 40 | ->hasAssets() 41 | ->publishesServiceProvider('MyProviderName') 42 | ->hasRoute('web') 43 | ->hasMigration('create_package_tables') 44 | ->hasCommand(YourCoolPackageCommand::class) 45 | ->hasInstallCommand(function(InstallCommand $command) { 46 | $command 47 | ->publishConfigFile() 48 | ->publishAssets() 49 | ->publishMigrations() 50 | ->copyAndRegisterServiceProviderInApp() 51 | ->askToStarRepoOnGitHub(); 52 | }); 53 | } 54 | } 55 | ``` 56 | 57 | Under the hood it will do the necessary work to register the necessary things and make all sorts of files publishable. 58 | 59 | ## Support us 60 | 61 | [](https://spatie.be/github-ad-click/laravel-package-tools) 62 | 63 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can 64 | support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 65 | 66 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 67 | You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards 68 | on [our virtual postcard wall](https://spatie.be/open-source/postcards). 69 | 70 | ## Getting started 71 | 72 | This package is opinionated on how you should structure your package. To get started easily, consider 73 | using [our package-skeleton repo](https://github.com/spatie/package-skeleton-laravel) to start your package. The 74 | skeleton is structured perfectly to work perfectly with the `PackageServiceProvider` in this package. 75 | 76 | ## Usage 77 | 78 | To avoid needing to scroll through to find the right usage section, here is a Table of Contents: 79 | 80 | * [Directory Structure](#directory-structure) 81 | * [Making your functionality publishable](#making-your-functionality-publishable) 82 | * [Getting Started](#getting-started) 83 | * [Assets](#assets) 84 | * [Blade Components](#blade-view-components) 85 | * [Blade Anonymous Components](#blade-anonymous-components) 86 | * [Blade Custom Directives](#blade-custom-directives) 87 | * [Blade Custom Echo Handlers](#blade-custom-echo-handlers) 88 | * [Blade Custom Conditionals](#blade-custom-conditionals) 89 | * [Commands - Callable and Console](#commands-callable-and-console) 90 | * [Optimize Commands (Laravel v11+)](#optimize-commands) 91 | * [Config Files](#config-files) 92 | * [Events & Listeners](#events-and-listeners) 93 | * [Inertia Components](#inertia-components) 94 | * [Livewire Views and Components](#livewire-views-and-components) 95 | * [Database Migrations](#database-migrations) 96 | * [Routes](#routes) 97 | * [Publishable Service Providers](#publishable-service-providers) 98 | * [Translations](#translations) 99 | * [Views](#views) 100 | * [View Composers](#view-composers) 101 | * [Views Global Shared Data](#views-global-shared-data) 102 | * [Creating and Install Command](#creating-an-install-command) 103 | * [Lifecycle Hooks](#lifecycle-hooks) 104 | 105 | ### Directory Structure 106 | 107 | This package is opinionated on how you should structure your package, 108 | and by default expects a structure based on 109 | [our package-skeleton repo](https://github.com/spatie/package-skeleton-laravel), 110 | and to get started easily you should consider using this to start your package. 111 | 112 | The structure for a package expected by default looks like this: 113 | 114 | ``` 115 | 116 | /src/ Default location for PackageServiceProvider extended class 117 | /src/Commands/ Commands (callable and console-only) 118 | /src/Components/ Blade components 119 | /src/Providers/ Other Service Providers 120 | /config/ Mergeable and publishable config files 121 | /database/factories/ Database factories 122 | /database/migrations/ Publishable stubs and loadable migrations 123 | /resources/dist/ Publishable assets 124 | /resources/js/pages/ Inertia views 125 | /resources/lang/ International translations 126 | /resources/views/ Views 127 | /routes/ Routes 128 | ``` 129 | 130 | Note: When using paths in any Package method except `discoversMigrations()`, 131 | the path given is relative to the location of your primary Service Provider 132 | i.e. relative to `/src` 133 | so e.g. `/ConfigFiles` would be specified as `../ConfigFiles`. 134 | 135 | ### Getting Started 136 | 137 | In your package you should let your service provider extend `Spatie\LaravelPackageTools\PackageServiceProvider`. 138 | 139 | ```php 140 | use Spatie\LaravelPackageTools\PackageServiceProvider; 141 | use Spatie\LaravelPackageTools\Package; 142 | 143 | class YourPackageServiceProvider extends PackageServiceProvider 144 | { 145 | public function configurePackage(Package $package) : void 146 | { 147 | $package->name('your-package-name'); 148 | } 149 | } 150 | ``` 151 | 152 | Defining your package name with a call to `name()` is mandatory. 153 | 154 | **Note:** If your package name starts with `laravel-` then this prefix will be omitted 155 | and the remainder of the name used as a short-name instead when publishing files etc. 156 | 157 | And now let's look at all the different Laravel functions this supports... 158 | 159 | assing the package name to `name` is mandatory. 160 | 161 | ### Assets 162 | 163 | Any assets your package provides, should be placed in the `/resources/dist/` directory. 164 | 165 | You can make these assets publishable the `hasAssets` method. 166 | 167 | ```php 168 | $package 169 | ->name('your-package-name') 170 | ->hasAssets(); 171 | ``` 172 | 173 | Users of your package will be able to publish the assets with this command: 174 | 175 | ```bash 176 | php artisan vendor:publish --tag=your-package-name-assets 177 | ``` 178 | 179 | This will copy over the assets to the `public/vendor/` directory in the app where your package is 180 | installed in. 181 | 182 | ### Blade view components 183 | 184 | Any Blade view components that your package provides should be placed in the `/src/Components` directory. 185 | 186 | You can register these views with the `hasViewComponents` command. 187 | 188 | ```php 189 | $package 190 | ->name('your-package-name') 191 | ->hasViewComponents('spatie', Alert::class); 192 | ``` 193 | 194 | This will register your view components with Laravel. In the case of `Alert::class`, it can be referenced in views 195 | as ``, where `spatie` is the prefix you provided during registration. 196 | 197 | Calling `hasViewComponents` will also make view components publishable, and will be published 198 | to `app/Views/Components/vendor/`. 199 | 200 | Users of your package will be able to publish the view components with this command: 201 | 202 | ```bash 203 | php artisan vendor:publish --tag=your-package-name-components 204 | ``` 205 | 206 | ### Commands - Callable and Console 207 | 208 | You can register any command you package provides with the `hasCommand` function. 209 | 210 | ```php 211 | $package 212 | ->name('your-package-name') 213 | ->hasCommand(YourCoolPackageCommand::class); 214 | ```` 215 | 216 | If your package provides multiple commands, you can either use `hasCommand` multiple times, or pass an array 217 | to `hasCommands` 218 | 219 | ```php 220 | $package 221 | ->name('your-package-name') 222 | ->hasCommands([ 223 | YourCoolPackageCommand::class, 224 | YourOtherCoolPackageCommand::class, 225 | ]); 226 | ``` 227 | 228 | ### Config Files 229 | 230 | To register a config file, you should create a php file with your package name in the `config` directory of your 231 | package. In this example it should be at `/config/your-package-name.php`. 232 | 233 | If your package name starts with `laravel-`, we expect that your config file does not contain that prefix. So if your 234 | package name is `laravel-cool-package`, the config file should be named `cool-package.php`. 235 | 236 | To register that config file, call `hasConfigFile()` on `$package` in the `configurePackage` method. 237 | 238 | ```php 239 | $package 240 | ->name('your-package-name') 241 | ->hasConfigFile(); 242 | ``` 243 | 244 | The `hasConfigFile` method will also make the config file publishable. Users of your package will be able to publish the 245 | config file with this command. 246 | 247 | ```bash 248 | php artisan vendor:publish --tag=your-package-name-config 249 | ``` 250 | 251 | Should your package have multiple config files, you can pass their names as an array to `hasConfigFile` 252 | 253 | ```php 254 | $package 255 | ->name('your-package-name') 256 | ->hasConfigFile(['my-config-file', 'another-config-file']); 257 | ``` 258 | 259 | ### Inertia Components 260 | 261 | Any `.vue` or `.jsx` files your package provides, should be placed in the `/resources/js/Pages` directory. 262 | 263 | You can register these components with the `hasInertiaComponents` command. 264 | 265 | ```php 266 | $package 267 | ->name('your-package-name') 268 | ->hasInertiaComponents(); 269 | ``` 270 | 271 | This will register your components with Laravel. 272 | 273 | The user should publish the inertia components manually or using the [installer-command](#adding-an-installer-command) in order to use them. 274 | 275 | If you have an inertia component `/resources/js/Pages/myComponent.vue`, you can use it like 276 | this: `Inertia::render('YourPackageName/myComponent')`. Of course, you can also use subdirectories to organise your components. 277 | 278 | #### Publishing inertia components 279 | 280 | Calling `hasInertiaComponents` will also make inertia components publishable. Users of your package will be able to publish the views with this 281 | command: 282 | 283 | ```bash 284 | php artisan vendor:publish --tag=your-package-name-inertia-components 285 | ``` 286 | 287 | Also, the inertia components are available in a convenient way with your package [installer-command](#adding-an-installer-command) 288 | 289 | ### Working with migrations 290 | 291 | The `PackageServiceProvider` assumes that any migrations are placed in this 292 | directory: `/database/migrations`. Inside that directory you can put any migrations. 293 | 294 | To register your migration, you should pass its name without the extension to the `hasMigration` table. 295 | 296 | If your migration file is called `create_my_package_tables.php.stub` you can register them like this: 297 | 298 | ```php 299 | $package 300 | ->name('your-package-name') 301 | ->hasMigration('create_my_package_tables'); 302 | ``` 303 | 304 | Should your package contain multiple migration files, you can just call `hasMigration` multiple times or 305 | use `hasMigrations`. 306 | 307 | ```php 308 | $package 309 | ->name('your-package-name') 310 | ->hasMigrations(['my_package_tables', 'some_other_migration']); 311 | ``` 312 | 313 | Alternatively, if you wish to publish all migrations in your package by default, you may call `discoversMigrations`. 314 | 315 | ```php 316 | $package 317 | ->name('your-package-name') 318 | ->discoversMigrations(); 319 | ``` 320 | 321 | Calling this method will look for migrations in the `./database/migrations` directory of your project. However, if you have defined your migrations 322 | in a different folder, you may pass a value to the `$path` variable to instruct the app to discover migrations from that location. 323 | 324 | ```php 325 | $package 326 | ->name('your-package-name') 327 | ->discoversMigrations(path: '/path/to/your/migrations/folder'); 328 | ``` 329 | 330 | Calling either `hasMigration`, `hasMigration` or `discoversMigrations` will also make migrations publishable. Users of your package will be able to publish the 331 | migrations with this command: 332 | 333 | ```bash 334 | php artisan vendor:publish --tag=your-package-name-migrations 335 | ``` 336 | 337 | Like you might expect, published migration files will be prefixed with the current datetime. 338 | 339 | You can also enable the migrations to be registered without needing the users of your package to publish them: 340 | 341 | ```php 342 | $package 343 | ->name('your-package-name') 344 | ->hasMigrations(['my_package_tables', 'some_other_migration']) 345 | ->runsMigrations(); 346 | ``` 347 | 348 | ### Routes 349 | 350 | The `PackageServiceProvider` assumes that any route files are placed in this directory: `/routes`. Inside 351 | that directory you can put any route files. 352 | 353 | To register your route, you should pass its name without the extension to the `hasRoute` method. 354 | 355 | If your route file is called `web.php` you can register them like this: 356 | 357 | ```php 358 | $package 359 | ->name('your-package-name') 360 | ->hasRoute('web'); 361 | ``` 362 | 363 | Should your package contain multiple route files, you can just call `hasRoute` multiple times or use `hasRoutes`. 364 | 365 | ```php 366 | $package 367 | ->name('your-package-name') 368 | ->hasRoutes(['web', 'admin']); 369 | ``` 370 | 371 | ### Publishable Service Providers 372 | 373 | Some packages need an example service provider to be copied into the `app\Providers` directory of the Laravel app. Think 374 | of for instance, the `laravel/horizon` package that copies an `HorizonServiceProvider` into your app with some sensible 375 | defaults. 376 | 377 | ```php 378 | $package 379 | ->name('your-package-name') 380 | ->publishesServiceProvider($nameOfYourServiceProvider); 381 | ``` 382 | 383 | The file that will be copied to the app should be stored in your package 384 | in `/resources/stubs/{$nameOfYourServiceProvider}.php.stub`. 385 | 386 | When your package is installed into an app, running this command... 387 | 388 | ```bash 389 | php artisan vendor:publish --tag=your-package-name-provider 390 | ``` 391 | 392 | ... will copy `/resources/stubs/{$nameOfYourServiceProvider}.php.stub` in your package 393 | to `app/Providers/{$nameOfYourServiceProvider}.php` in the app of the user. 394 | 395 | ### Translations 396 | 397 | Any translations your package provides, should be placed in the `/resources/lang/` 398 | directory. 399 | 400 | You can register these translations with the `hasTranslations` command. 401 | 402 | ```php 403 | $package 404 | ->name('your-package-name') 405 | ->hasTranslations(); 406 | ``` 407 | 408 | This will register the translations with Laravel. 409 | 410 | Assuming you save this translation file at `/resources/lang/en/translations.php`... 411 | 412 | ```php 413 | return [ 414 | 'translatable' => 'translation', 415 | ]; 416 | ``` 417 | 418 | ... your package and users will be able to retrieve the translation with: 419 | 420 | ```php 421 | trans('your-package-name::translations.translatable'); // returns 'translation' 422 | ``` 423 | 424 | If your package name starts with `laravel-` then you should leave that off in the example above. 425 | 426 | Coding with translation strings as keys, you should create JSON files 427 | in `/resources/lang/.json`. 428 | 429 | For example, creating `/resources/lang/it.json` file like so: 430 | 431 | ```json 432 | { 433 | "Hello!": "Ciao!" 434 | } 435 | ``` 436 | 437 | ...the output of... 438 | 439 | ```php 440 | trans('Hello!'); 441 | ``` 442 | 443 | ...will be `Ciao!` if the application uses the Italian language. 444 | 445 | Calling `hasTranslations` will also make translations publishable. Users of your package will be able to publish the 446 | translations with this command: 447 | 448 | ```bash 449 | php artisan vendor:publish --tag=your-package-name-translations 450 | ``` 451 | 452 | ### Views 453 | 454 | Any views your package provides, should be placed in the `/resources/views` directory. 455 | 456 | You can register these views with the `hasViews` command. 457 | 458 | ```php 459 | $package 460 | ->name('your-package-name') 461 | ->hasViews(); 462 | ``` 463 | 464 | This will register your views with Laravel. 465 | 466 | If you have a view `/resources/views/myView.blade.php`, you can use it like 467 | this: `view('your-package-name::myView')`. Of course, you can also use subdirectories to organise your views. A view 468 | located at `/resources/views/subdirectory/myOtherView.blade.php` can be used 469 | with `view('your-package-name::subdirectory.myOtherView')`. 470 | 471 | #### Using a custom view namespace 472 | 473 | You can pass a custom view namespace to the `hasViews` method. 474 | 475 | ```php 476 | $package 477 | ->name('your-package-name') 478 | ->hasViews('custom-view-namespace'); 479 | ``` 480 | 481 | You can now use the views of the package like this: 482 | 483 | ```php 484 | view('custom-view-namespace::myView'); 485 | ``` 486 | 487 | #### Publishing the views 488 | 489 | Calling `hasViews` will also make views publishable. Users of your package will be able to publish the views with this 490 | command: 491 | 492 | ```bash 493 | php artisan vendor:publish --tag=your-package-name-views 494 | ``` 495 | 496 | > **Note:** 497 | > 498 | > If you use custom view namespace then you should change your publish command like this: 499 | ```bash 500 | php artisan vendor:publish --tag=custom-view-namespace-views 501 | ``` 502 | 503 | ### View Composers 504 | 505 | You can register any view composers that your project uses with the `hasViewComposers` method. You may also register a 506 | callback that receives a `$view` argument instead of a classname. 507 | 508 | To register a view composer with all views, use an asterisk as the view name `'*'`. 509 | 510 | ```php 511 | $package 512 | ->name('your-package-name') 513 | ->hasViewComposer('viewName', MyViewComposer::class) 514 | ->hasViewComposer('*', function($view) { 515 | $view->with('sharedVariable', 123); 516 | }); 517 | ``` 518 | 519 | ### Views Global Shared Data 520 | 521 | You can share data with all views using the `sharesDataWithAllViews` method. This will make the shared variable 522 | available to all views. 523 | 524 | ```php 525 | $package 526 | ->name('your-package-name') 527 | ->sharesDataWithAllViews('companyName', 'Spatie'); 528 | ``` 529 | 530 | ### Creating an Install Command 531 | 532 | Instead of letting your users manually publishing config files, migrations, and other files manually, you could opt to 533 | add an install command that does all this work in one go. Packages like Laravel Horizon and Livewire provide such 534 | commands. 535 | 536 | When using Laravel Package Tools, you don't have to write an `InstallCommand` yourself. Instead, you can simply 537 | call, `hasInstallCommand` and configure it using a closure. Here's an example. 538 | 539 | ```php 540 | use Spatie\LaravelPackageTools\PackageServiceProvider; 541 | use Spatie\LaravelPackageTools\Package; 542 | use Spatie\LaravelPackageTools\Commands\Concerns; 543 | 544 | class YourPackageServiceProvider extends PackageServiceProvider 545 | { 546 | public function configurePackage(Package $package): void 547 | { 548 | $package 549 | ->name('your-package-name') 550 | ->hasConfigFile() 551 | ->hasMigration('create_package_tables') 552 | ->publishesServiceProvider('MyServiceProviderName') 553 | ->hasInstallCommand(function(InstallCommand $command) { 554 | $command 555 | ->publishConfigFile() 556 | ->publishAssets() 557 | ->publishMigrations() 558 | ->askToRunMigrations() 559 | ->copyAndRegisterServiceProviderInApp() 560 | ->askToStarRepoOnGitHub('your-vendor/your-repo-name') 561 | }); 562 | } 563 | } 564 | ``` 565 | 566 | With this in place, the package user can call this command: 567 | 568 | ```bash 569 | php artisan your-package-name:install 570 | ``` 571 | 572 | Using the code above, that command will: 573 | 574 | - publish the config file 575 | - publish the assets 576 | - publish the migrations 577 | - copy the `/resources/stubs/MyProviderName.php.stub` from your package to `app/Providers/MyServiceProviderName.php`, and also register that 578 | provider in `config/app.php` 579 | - ask if migrations should be run now 580 | - prompt the user to open up `https://github.com/'your-vendor/your-repo-name'` in the browser in order to star it 581 | 582 | You can also call `startWith` and `endWith` on the `InstallCommand`. They will respectively be executed at the start and 583 | end when running `php artisan your-package-name:install`. You can use this to perform extra work or display extra 584 | output. 585 | 586 | ```php 587 | use Spatie\LaravelPackageTools\Commands\Concerns; 588 | 589 | public function configurePackage(Package $package): void 590 | { 591 | $package 592 | // ... configure package 593 | ->hasInstallCommand(function(InstallCommand $command) { 594 | $command 595 | ->startWith(function(InstallCommand $command) { 596 | $command->info('Hello, and welcome to my great new package!'); 597 | }) 598 | ->publishConfigFile() 599 | ->publishAssets() 600 | ->publishMigrations() 601 | ->askToRunMigrations() 602 | ->copyAndRegisterServiceProviderInApp() 603 | ->askToStarRepoOnGitHub('your-vendor/your-repo-name') 604 | ->endWith(function(InstallCommand $command) { 605 | $command->info('Have a great day!'); 606 | }) 607 | }); 608 | } 609 | ``` 610 | 611 | ### Lifecycle Hooks 612 | 613 | You can put any custom logic your package needs while starting up in one of these methods: 614 | 615 | - `registeringPackage`: will be called at the start of the `register` method of `PackageServiceProvider` 616 | - `packageRegistered`: will be called at the end of the `register` method of `PackageServiceProvider` 617 | - `bootingPackage`: will be called at the start of the `boot` method of `PackageServiceProvider` 618 | - `packageBooted`: will be called at the end of the `boot` method of `PackageServiceProvider` 619 | 620 | ## Testing 621 | 622 | ```bash 623 | composer test 624 | ``` 625 | 626 | This package now supports test groups as follows: 627 | 628 | ```bash 629 | composer test -- --group=blade 630 | ``` 631 | 632 | The current groups suported are: 633 | * base 634 | * assets 635 | * blade 636 | * commands 637 | * config 638 | * inertia 639 | * migrations 640 | * provider 641 | * routes 642 | * shareddata 643 | * translations 644 | * viewcomposer 645 | * views 646 | * installer 647 | 648 | Additionally, if you wish to test only backwards compatibility you can use: 649 | * legacy 650 | 651 | **Note:** `InvalidPackage` exceptions thrown during Laravel application bootup are reported by Pest, 652 | but because the occur before the start of a test case 653 | Pest by default does not allow you intentionally to test for them being thrown. 654 | The tests in this package now include checks for intentional `InvalidPackage` exceptions being thrown 655 | by catching and saving such exceptions in the TestServiceProvider, 656 | and then rethrowing the exception at the very start of a Pest test case, 657 | and this is achieved by loading a modified version of the Pest `test()` function 658 | before anything else is loaded. 659 | Whilst this is done for you if you run `composer test`, 660 | if you want to run `vendor/bin/pest` directly you now need to run it like this: 661 | 662 | ```bash 663 | php -d auto_prepend_file=tests/Prepend.php vendor/bin/pest 664 | ``` 665 | 666 | ## Changelog 667 | 668 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 669 | 670 | ## Contributing 671 | 672 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 673 | 674 | ## Security Vulnerabilities 675 | 676 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 677 | 678 | ## Credits 679 | 680 | - [Freek Van der Herten](https://github.com/freekmurze) 681 | - [All Contributors](../../contributors) 682 | 683 | ## License 684 | 685 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 686 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-package-tools", 3 | "description": "Tools for creating Laravel packages", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-package-tools" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-package-tools", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.0", 19 | "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0" 20 | }, 21 | "require-dev": { 22 | "mockery/mockery": "^1.5", 23 | "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", 24 | "pestphp/pest": "^1.23|^2.1|^3.1", 25 | "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", 26 | "phpunit/phpunit": "^9.5.24|^10.5|^11.5", 27 | "spatie/pest-plugin-test-time": "^1.1|^2.2" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Spatie\\LaravelPackageTools\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Spatie\\LaravelPackageTools\\Tests\\": "tests" 37 | } 38 | }, 39 | "scripts": { 40 | "test": "php -d auto_prepend_file=tests/Prepend.php vendor/bin/pest --colors=always", 41 | "test-coverage": "@test --coverage", 42 | "test-dirty": "@test --dirty --compact" 43 | }, 44 | "config": { 45 | "sort-packages": true, 46 | "allow-plugins": { 47 | "pestphp/pest-plugin": true 48 | } 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true 52 | } 53 | -------------------------------------------------------------------------------- /src/Commands/Concerns/AskToRunMigrations.php: -------------------------------------------------------------------------------- 1 | askToRunMigrations = true; 12 | 13 | return $this; 14 | } 15 | 16 | protected function processAskToRunMigrations(): self 17 | { 18 | if ($this->askToRunMigrations) { 19 | if ($this->confirm('Would you like to run the migrations now?')) { 20 | $this->comment('Running migrations...'); 21 | 22 | $this->call('migrate'); 23 | } 24 | } 25 | 26 | return $this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Commands/Concerns/AskToStarRepoOnGitHub.php: -------------------------------------------------------------------------------- 1 | starRepo = $vendorSlashRepoName; 12 | 13 | return $this; 14 | } 15 | 16 | protected function processStarRepo(): self 17 | { 18 | if ($this->starRepo) { 19 | if ($this->confirm('Would you like to star our repo on GitHub?')) { 20 | $repoUrl = "https://github.com/{$this->starRepo}"; 21 | 22 | if (PHP_OS_FAMILY == 'Darwin') { 23 | exec("open {$repoUrl}"); 24 | } 25 | if (PHP_OS_FAMILY == 'Windows') { 26 | exec("start {$repoUrl}"); 27 | } 28 | if (PHP_OS_FAMILY == 'Linux') { 29 | exec("xdg-open {$repoUrl}"); 30 | } 31 | } 32 | } 33 | 34 | return $this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Commands/Concerns/PublishesResources.php: -------------------------------------------------------------------------------- 1 | publishes = array_merge($this->publishes, $tag); 12 | 13 | return $this; 14 | } 15 | 16 | public function publishAssets(): self 17 | { 18 | return $this->publish('assets'); 19 | } 20 | 21 | public function publishConfigFile(): self 22 | { 23 | return $this->publish('config'); 24 | } 25 | 26 | public function publishInertiaComponents(): self 27 | { 28 | return $this->publish('inertia-components'); 29 | } 30 | 31 | public function publishMigrations(): self 32 | { 33 | return $this->publish('migrations'); 34 | } 35 | 36 | protected function processPublishes(): self 37 | { 38 | foreach ($this->publishes as $tag) { 39 | $name = str_replace('-', ' ', $tag); 40 | $this->comment("Publishing {$name}..."); 41 | 42 | $this->callSilently("vendor:publish", [ 43 | '--tag' => "{$this->package->shortName()}-{$tag}", 44 | ]); 45 | } 46 | 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Commands/Concerns/SupportsServiceProviderInApp.php: -------------------------------------------------------------------------------- 1 | copyServiceProviderInApp = true; 14 | 15 | return $this; 16 | } 17 | 18 | protected function processCopyServiceProviderInApp(): self 19 | { 20 | if ($this->copyServiceProviderInApp) { 21 | $this->comment('Publishing service provider...'); 22 | 23 | $this->copyServiceProviderInApp(); 24 | } 25 | 26 | return $this; 27 | } 28 | 29 | protected function copyServiceProviderInApp(): self 30 | { 31 | $providerName = $this->package->publishableProviderName; 32 | 33 | if (! $providerName) { 34 | return $this; 35 | } 36 | 37 | $this->callSilent('vendor:publish', ['--tag' => $this->package->shortName() . '-provider']); 38 | 39 | $namespace = Str::replaceLast('\\', '', $this->laravel->getNamespace()); 40 | 41 | if (intval(app()->version()) < 11 || ! file_exists(base_path('bootstrap/providers.php'))) { 42 | $appConfig = file_get_contents(config_path('app.php')); 43 | } else { 44 | $appConfig = file_get_contents(base_path('bootstrap/providers.php')); 45 | } 46 | 47 | $class = '\\Providers\\' . Str::replace('/', '\\', $providerName) . '::class'; 48 | 49 | if (Str::contains($appConfig, $namespace . $class)) { 50 | return $this; 51 | } 52 | 53 | if (intval(app()->version()) < 11 || ! file_exists(base_path('bootstrap/providers.php'))) { 54 | file_put_contents(config_path('app.php'), str_replace( 55 | "{$namespace}\\Providers\\BroadcastServiceProvider::class,", 56 | "{$namespace}\\Providers\\BroadcastServiceProvider::class," . PHP_EOL . " {$namespace}{$class},", 57 | $appConfig 58 | )); 59 | } else { 60 | file_put_contents(base_path('bootstrap/providers.php'), str_replace( 61 | "{$namespace}\\Providers\\AppServiceProvider::class,", 62 | "{$namespace}\\Providers\\AppServiceProvider::class," . PHP_EOL . " {$namespace}{$class},", 63 | $appConfig 64 | )); 65 | } 66 | 67 | file_put_contents(app_path('Providers/' . $providerName . '.php'), str_replace( 68 | "namespace App\Providers;", 69 | "namespace {$namespace}\Providers;", 70 | file_get_contents(app_path('Providers/' . $providerName . '.php')) 71 | )); 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Commands/Concerns/SupportsStartWithEndWith.php: -------------------------------------------------------------------------------- 1 | startWith = $callable; 15 | 16 | return $this; 17 | } 18 | 19 | public function endWith(callable $callable): self 20 | { 21 | $this->endWith = $callable; 22 | 23 | return $this; 24 | } 25 | 26 | protected function processStartWith(): self 27 | { 28 | if ($this->startWith) { 29 | ($this->startWith)($this); 30 | } 31 | 32 | return $this; 33 | } 34 | 35 | protected function processEndWith(): self 36 | { 37 | if ($this->endWith) { 38 | ($this->endWith)($this); 39 | } 40 | 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | signature = $package->shortName() . ':install'; 26 | 27 | $this->description = 'Install ' . $package->name; 28 | 29 | $this->package = $package; 30 | 31 | $this->hidden = true; 32 | 33 | parent::__construct(); 34 | } 35 | 36 | public function handle() 37 | { 38 | $this 39 | ->processStartWith() 40 | ->processPublishes() 41 | ->processAskToRunMigrations() 42 | ->processCopyServiceProviderInApp() 43 | ->processStarRepo() 44 | ->processEndWith(); 45 | 46 | $this->info("{$this->package->shortName()} has been installed!"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasAssets.php: -------------------------------------------------------------------------------- 1 | hasAssets = true; 12 | 13 | return $this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasBladeComponents.php: -------------------------------------------------------------------------------- 1 | viewComponents[$viewComponentName] = $prefix; 12 | 13 | return $this; 14 | } 15 | 16 | public function hasViewComponents(string $prefix, ...$viewComponentNames): static 17 | { 18 | foreach ($viewComponentNames as $componentName) { 19 | $this->viewComponents[$componentName] = $prefix; 20 | } 21 | 22 | return $this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasCommands.php: -------------------------------------------------------------------------------- 1 | commands[] = $commandClassName; 13 | 14 | return $this; 15 | } 16 | 17 | public function hasCommands(...$commandClassNames): static 18 | { 19 | $this->commands = array_merge( 20 | $this->commands, 21 | collect($commandClassNames)->flatten()->toArray() 22 | ); 23 | 24 | return $this; 25 | } 26 | 27 | public function hasConsoleCommand(string $commandClassName): static 28 | { 29 | $this->consoleCommands[] = $commandClassName; 30 | 31 | return $this; 32 | } 33 | 34 | public function hasConsoleCommands(...$commandClassNames): static 35 | { 36 | $this->consoleCommands = array_merge( 37 | $this->consoleCommands, 38 | collect($commandClassNames)->flatten()->toArray() 39 | ); 40 | 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasConfigs.php: -------------------------------------------------------------------------------- 1 | shortName(); 12 | 13 | if (! is_array($configFileName)) { 14 | $configFileName = [$configFileName]; 15 | } 16 | 17 | $this->configFileNames = $configFileName; 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasInertia.php: -------------------------------------------------------------------------------- 1 | hasInertiaComponents = true; 12 | 13 | $this->viewNamespace = $namespace; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasInstallCommand.php: -------------------------------------------------------------------------------- 1 | consoleCommands[] = $installCommand; 16 | 17 | return $this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasMigrations.php: -------------------------------------------------------------------------------- 1 | runsMigrations = $runsMigrations; 18 | 19 | return $this; 20 | } 21 | 22 | public function hasMigration(string $migrationFileName): static 23 | { 24 | $this->migrationFileNames[] = $migrationFileName; 25 | 26 | return $this; 27 | } 28 | 29 | public function hasMigrations(...$migrationFileNames): static 30 | { 31 | $this->migrationFileNames = array_merge( 32 | $this->migrationFileNames, 33 | collect($migrationFileNames)->flatten()->toArray() 34 | ); 35 | 36 | return $this; 37 | } 38 | 39 | public function discoversMigrations(bool $discoversMigrations = true, string $path = '/database/migrations'): static 40 | { 41 | $this->discoversMigrations = $discoversMigrations; 42 | $this->migrationsPath = $path; 43 | 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasRoutes.php: -------------------------------------------------------------------------------- 1 | routeFileNames[] = $routeFileName; 12 | 13 | return $this; 14 | } 15 | 16 | public function hasRoutes(...$routeFileNames): static 17 | { 18 | $this->routeFileNames = array_merge( 19 | $this->routeFileNames, 20 | collect($routeFileNames)->flatten()->toArray() 21 | ); 22 | 23 | return $this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasServiceProviders.php: -------------------------------------------------------------------------------- 1 | publishableProviderName = $providerName; 12 | 13 | return $this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasTranslations.php: -------------------------------------------------------------------------------- 1 | hasTranslations = true; 12 | 13 | return $this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasViewComposers.php: -------------------------------------------------------------------------------- 1 | viewComposers[$viewName] = $viewComposer; 17 | } 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasViewSharedData.php: -------------------------------------------------------------------------------- 1 | sharedViewData[$name] = $value; 12 | 13 | return $this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Concerns/Package/HasViews.php: -------------------------------------------------------------------------------- 1 | hasViews = true; 14 | 15 | $this->viewNamespace = $namespace; 16 | 17 | return $this; 18 | } 19 | 20 | public function viewNamespace(): string 21 | { 22 | return $this->viewNamespace ?? $this->shortName(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessAssets.php: -------------------------------------------------------------------------------- 1 | package->hasAssets || ! $this->app->runningInConsole()) { 10 | return $this; 11 | } 12 | 13 | $vendorAssets = $this->package->basePath('/../resources/dist'); 14 | $appAssets = public_path("vendor/{$this->package->shortName()}"); 15 | 16 | $this->publishes([$vendorAssets => $appAssets], "{$this->package->shortName()}-assets"); 17 | 18 | return $this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessBladeComponents.php: -------------------------------------------------------------------------------- 1 | package->viewComponents)) { 10 | return $this; 11 | } 12 | 13 | foreach ($this->package->viewComponents as $componentClass => $prefix) { 14 | $this->loadViewComponentsAs($prefix, [$componentClass]); 15 | } 16 | 17 | if ($this->app->runningInConsole()) { 18 | $vendorComponents = $this->package->basePath('/Components'); 19 | $appComponents = base_path("app/View/Components/vendor/{$this->package->shortName()}"); 20 | 21 | $this->publishes([$vendorComponents => $appComponents], "{$this->package->name}-components"); 22 | } 23 | 24 | return $this; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessCommands.php: -------------------------------------------------------------------------------- 1 | package->commands)) { 10 | return $this; 11 | } 12 | 13 | $this->commands($this->package->commands); 14 | 15 | return $this; 16 | } 17 | 18 | protected function bootPackageConsoleCommands(): self 19 | { 20 | if (empty($this->package->consoleCommands) || ! $this->app->runningInConsole()) { 21 | return $this; 22 | } 23 | 24 | $this->commands($this->package->consoleCommands); 25 | 26 | return $this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessConfigs.php: -------------------------------------------------------------------------------- 1 | package->configFileNames)) { 10 | return $this; 11 | } 12 | 13 | foreach ($this->package->configFileNames as $configFileName) { 14 | $vendorConfig = $this->package->basePath("/../config/{$configFileName}.php"); 15 | 16 | // Only mergeConfigFile if a .php file and not if a stub file 17 | if (! is_file($vendorConfig)) { 18 | continue; 19 | } 20 | 21 | $this->mergeConfigFrom($vendorConfig, $configFileName); 22 | } 23 | 24 | return $this; 25 | } 26 | 27 | protected function bootPackageConfigs(): self 28 | { 29 | if (empty($this->package->configFileNames) || ! $this->app->runningInConsole()) { 30 | return $this; 31 | } 32 | 33 | foreach ($this->package->configFileNames as $configFileName) { 34 | $vendorConfig ; 35 | if ( 36 | ! is_file($vendorConfig = $this->package->basePath("/../config/{$configFileName}.php")) 37 | && 38 | ! is_file($vendorConfig = $this->package->basePath("/../config/{$configFileName}.php.stub")) 39 | ) { 40 | continue; 41 | } 42 | 43 | $this->publishes( 44 | [$vendorConfig => config_path("{$configFileName}.php")], 45 | "{$this->package->shortName()}-config" 46 | ); 47 | } 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessInertia.php: -------------------------------------------------------------------------------- 1 | package->hasInertiaComponents) { 12 | return $this; 13 | } 14 | 15 | $namespace = $this->package->viewNamespace; 16 | $directoryName = Str::of($this->packageView($namespace))->studly()->remove('-')->value(); 17 | $vendorComponents = $this->package->basePath('/../resources/js/Pages'); 18 | $appComponents = base_path("resources/js/Pages/{$directoryName}"); 19 | 20 | if ($this->app->runningInConsole()) { 21 | $this->publishes( 22 | [$vendorComponents => $appComponents], 23 | "{$this->packageView($namespace)}-inertia-components" 24 | ); 25 | } 26 | 27 | return $this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessMigrations.php: -------------------------------------------------------------------------------- 1 | package->discoversMigrations) { 14 | $this->discoverPackageMigrations(); 15 | 16 | return $this; 17 | } 18 | 19 | $now = Carbon::now(); 20 | 21 | foreach ($this->package->migrationFileNames as $migrationFileName) { 22 | $vendorMigration = $this->package->basePath("/../database/migrations/{$migrationFileName}.php"); 23 | $appMigration = $this->generateMigrationName($migrationFileName, $now->addSecond()); 24 | 25 | // Support for the .stub file extension 26 | if (! file_exists($vendorMigration)) { 27 | $vendorMigration .= '.stub'; 28 | } 29 | 30 | if ($this->app->runningInConsole()) { 31 | $this->publishes( 32 | [$vendorMigration => $appMigration], 33 | "{$this->package->shortName()}-migrations" 34 | ); 35 | } 36 | 37 | if ($this->package->runsMigrations) { 38 | $this->loadMigrationsFrom($vendorMigration); 39 | } 40 | } 41 | 42 | return $this; 43 | } 44 | 45 | protected function discoverPackageMigrations(): void 46 | { 47 | $now = Carbon::now(); 48 | $migrationsPath = trim($this->package->migrationsPath, '/'); 49 | 50 | $files = (new Filesystem())->files($this->package->basePath("/../{$migrationsPath}")); 51 | 52 | foreach ($files as $file) { 53 | $filePath = $file->getPathname(); 54 | $migrationFileName = Str::replace(['.stub', '.php'], '', $file->getFilename()); 55 | 56 | // Publish but do not add timestamp to non migration files 57 | if (Str::endsWith($filePath, [".php", ".php.stub"])) { 58 | $appMigration = $this->generateMigrationName($migrationFileName, $now->addSecond()); 59 | } else { 60 | $appMigration = database_path("migrations/{$file->getFilename()}"); 61 | } 62 | 63 | if ($this->app->runningInConsole()) { 64 | $this->publishes( 65 | [$filePath => $appMigration], 66 | "{$this->package->shortName()}-migrations" 67 | ); 68 | } 69 | 70 | // Do not load non migration files 71 | if ($this->package->runsMigrations && Str::endsWith($filePath, [".php", ".php.stub"])) { 72 | $this->loadMigrationsFrom($filePath); 73 | } 74 | } 75 | } 76 | 77 | protected function generateMigrationName(string $migrationFileName, Carbon $now): string 78 | { 79 | $migrationsPath = 'migrations/' . dirname($migrationFileName) . '/'; 80 | $migrationFileName = basename($migrationFileName); 81 | 82 | $len = strlen($migrationFileName) + 4; 83 | 84 | if (Str::contains($migrationFileName, '/')) { 85 | $migrationsPath .= Str::of($migrationFileName)->beforeLast('/')->finish('/'); 86 | $migrationFileName = Str::of($migrationFileName)->afterLast('/'); 87 | } 88 | 89 | foreach (glob(database_path("{$migrationsPath}*.php")) as $filename) { 90 | if ((substr($filename, -$len) === $migrationFileName . '.php')) { 91 | return $filename; 92 | } 93 | } 94 | 95 | $migrationFileName = self::stripTimestampPrefix($migrationFileName); 96 | $timestamp = $now->format('Y_m_d_His'); 97 | $formattedFileName = Str::of($migrationFileName)->snake()->finish('.php'); 98 | 99 | return database_path("{$migrationsPath}{$timestamp}_{$formattedFileName}"); 100 | } 101 | 102 | private static function stripTimestampPrefix(string $filename): string 103 | { 104 | return preg_replace('/^\d{4}_\d{2}_\d{2}_\d{6}_/', '', $filename); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessRoutes.php: -------------------------------------------------------------------------------- 1 | package->routeFileNames)) { 10 | return $this; 11 | } 12 | 13 | foreach ($this->package->routeFileNames as $routeFileName) { 14 | $this->loadRoutesFrom("{$this->package->basePath('/../routes/')}{$routeFileName}.php"); 15 | } 16 | 17 | return $this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessServiceProviders.php: -------------------------------------------------------------------------------- 1 | package->publishableProviderName || ! $this->app->runningInConsole()) { 10 | return $this; 11 | } 12 | 13 | $providerName = $this->package->publishableProviderName; 14 | $vendorProvider = $this->package->basePath("/../resources/stubs/{$providerName}.php.stub"); 15 | $appProvider = base_path("app/Providers/{$providerName}.php"); 16 | 17 | $this->publishes([$vendorProvider => $appProvider], "{$this->package->shortName()}-provider"); 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessTranslations.php: -------------------------------------------------------------------------------- 1 | package->hasTranslations) { 10 | return $this; 11 | } 12 | 13 | $vendorTranslations = $this->package->basePath('/../resources/lang'); 14 | $appTranslations = (function_exists('lang_path')) 15 | ? lang_path("vendor/{$this->package->shortName()}") 16 | : resource_path("lang/vendor/{$this->package->shortName()}"); 17 | 18 | $this->loadTranslationsFrom($vendorTranslations, $this->package->shortName()); 19 | 20 | $this->loadJsonTranslationsFrom($vendorTranslations); 21 | $this->loadJsonTranslationsFrom($appTranslations); 22 | 23 | if ($this->app->runningInConsole()) { 24 | $this->publishes( 25 | [$vendorTranslations => $appTranslations], 26 | "{$this->package->shortName()}-translations" 27 | ); 28 | } 29 | 30 | return $this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessViewComposers.php: -------------------------------------------------------------------------------- 1 | package->viewComposers)) { 12 | return $this; 13 | } 14 | 15 | foreach ($this->package->viewComposers as $viewName => $viewComposer) { 16 | View::composer($viewName, $viewComposer); 17 | } 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessViewSharedData.php: -------------------------------------------------------------------------------- 1 | package->sharedViewData)) { 12 | return $this; 13 | } 14 | 15 | foreach ($this->package->sharedViewData as $name => $value) { 16 | View::share($name, $value); 17 | } 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Concerns/PackageServiceProvider/ProcessViews.php: -------------------------------------------------------------------------------- 1 | package->hasViews) { 10 | return $this; 11 | } 12 | 13 | $namespace = $this->package->viewNamespace; 14 | $vendorViews = $this->package->basePath('/../resources/views'); 15 | $appViews = base_path("resources/views/vendor/{$this->packageView($namespace)}"); 16 | 17 | $this->loadViewsFrom($vendorViews, $this->package->viewNamespace()); 18 | 19 | if ($this->app->runningInConsole()) { 20 | $this->publishes([$vendorViews => $appViews], "{$this->packageView($namespace)}-views"); 21 | } 22 | 23 | return $this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidPackage.php: -------------------------------------------------------------------------------- 1 | name("yourName")`'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Package.php: -------------------------------------------------------------------------------- 1 | name = $name; 43 | 44 | return $this; 45 | } 46 | 47 | public function shortName(): string 48 | { 49 | return Str::after($this->name, 'laravel-'); 50 | } 51 | 52 | public function basePath(?string $directory = null): string 53 | { 54 | if ($directory === null) { 55 | return $this->basePath; 56 | } 57 | 58 | return $this->basePath . DIRECTORY_SEPARATOR . ltrim($directory, DIRECTORY_SEPARATOR); 59 | } 60 | 61 | public function setBasePath(string $path): static 62 | { 63 | $this->basePath = $path; 64 | 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/PackageServiceProvider.php: -------------------------------------------------------------------------------- 1 | registeringPackage(); 44 | 45 | $this->package = $this->newPackage(); 46 | $this->package->setBasePath($this->getPackageBaseDir()); 47 | 48 | $this->configurePackage($this->package); 49 | if (empty($this->package->name)) { 50 | throw InvalidPackage::nameIsRequired(); 51 | } 52 | 53 | $this->registerPackageConfigs(); 54 | 55 | $this->packageRegistered(); 56 | 57 | return $this; 58 | } 59 | 60 | public function registeringPackage() 61 | { 62 | } 63 | 64 | public function newPackage(): Package 65 | { 66 | return new Package(); 67 | } 68 | 69 | public function packageRegistered() 70 | { 71 | } 72 | 73 | public function boot() 74 | { 75 | $this->bootingPackage(); 76 | 77 | $this 78 | ->bootPackageAssets() 79 | ->bootPackageBladeComponents() 80 | ->bootPackageCommands() 81 | ->bootPackageConsoleCommands() 82 | ->bootPackageConfigs() 83 | ->bootPackageInertia() 84 | ->bootPackageMigrations() 85 | ->bootPackageRoutes() 86 | ->bootPackageServiceProviders() 87 | ->bootPackageTranslations() 88 | ->bootPackageViews() 89 | ->bootPackageViewComposers() 90 | ->bootPackageViewSharedData() 91 | ->packageBooted(); 92 | 93 | return $this; 94 | } 95 | 96 | public function bootingPackage() 97 | { 98 | } 99 | 100 | public function packageBooted() 101 | { 102 | } 103 | 104 | protected function getPackageBaseDir(): string 105 | { 106 | $reflector = new ReflectionClass(get_class($this)); 107 | 108 | $packageBaseDir = dirname($reflector->getFileName()); 109 | 110 | // Some packages like to keep Laravels directory structure and place 111 | // the service providers in a Providers folder. 112 | // move up a level when this is the case. 113 | if (str_ends_with($packageBaseDir, DIRECTORY_SEPARATOR.'Providers')) { 114 | $packageBaseDir = dirname($packageBaseDir); 115 | } 116 | 117 | return $packageBaseDir; 118 | } 119 | 120 | public function packageView(?string $namespace): ?string 121 | { 122 | return is_null($namespace) 123 | ? $this->package->shortName() 124 | : $this->package->viewNamespace; 125 | } 126 | } 127 | --------------------------------------------------------------------------------