Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Haking with Laravel modules
Benjamin Delespierre
Benjamin Delespierre

Posted on • Edited on

     

Haking with Laravel modules

As a project grows it becomes advantageous to break down its monolythicalapp/ folder into smaller chunks called modules. You know, to keep things structured and stuff 😜

A typical approach is to replicate the Laravel folder structure in many folders. For Example

modules/    billing/        app/ database/ routes/    shop/        app/ database/ routes/    blog/        app/ database/ routes/
Enter fullscreen modeExit fullscreen mode

But there's a small problem. Themake:something commands that we love 😍 and rely on every day for our spectacular productivity won't work with this structure 🙁

Well, fear not, my friend! Today, I'm going to show you how you can fix that problem and makemake:* commands play nicely with a modular folder structure!

And we're going to tackle thatin just 5 minutes of copying & pasting code around ⚡⚡⚡ Ready?

A new option for all Artisan commands

We want to be able to do something like this:

php artisan make:model--module billing--all Invoice
Enter fullscreen modeExit fullscreen mode

But we don't want to rewrite all the*MakeCommand classes. So we're going to inject 💉 this snippet directly inside theartisan file:

require__DIR__.'/vendor/autoload.php';$app=require_once__DIR__.'/bootstrap/app.php';// <--- be sure to paste AFTER this line/*|--------------------------------------------------------------------------| Detect The Module Context|--------------------------------------------------------------------------|| If you wish to run a given command (usually a make:something) in the| context of a module, you may pass --module <name> as arguments. The| following snippet will swap the base directory with the module directory| and erase the module arguments so the command can run normally.|*/if((false!==$offset=array_search('--module',$argv))&&!empty($argv[$offset+1])){$modulePath=$app->basePath("modules/{$argv[$offset+1]}");$app->useAppPath("{$modulePath}/app");$app->useDatabasePath("{$modulePath}/database");unset($argv[$offset],$argv[$offset+1]);}
Enter fullscreen modeExit fullscreen mode

We also need to make a small change at line 56:

$status=$kernel->handle($input=newSymfony\Component\Console\Input\ArgvInput($argv),// <---- add ($argv) here!newSymfony\Component\Console\Output\ConsoleOutput);
Enter fullscreen modeExit fullscreen mode

Introducing a new service provider

Laravel issomewhat made to handle modules and packages, but we need to tell it how to discover them. For that, we're going to need a service provider:

php artisan make:provider ModuleServiceProvider
Enter fullscreen modeExit fullscreen mode

Fill it with:

namespaceApp\Providers;useIlluminate\Database\Eloquent\Factories\Factory;useIlluminate\Support\ServiceProvider;useIlluminate\Support\Str;classModuleServiceProviderextendsServiceProvider{/**     * Register services.     *     * @return void     */publicfunctionregister(){/** Fixing Factory::resolveFactoryName */Factory::guessFactoryNamesUsing(function(string$modelName){$namespace=Str::contains($modelName,"Models\\")?Str::before($modelName,"App\\Models\\"):Str::before($modelName,"App\\");$modelName=Str::contains($modelName,"Models\\")?Str::after($modelName,"App\\Models\\"):Str::after($modelName,"App\\");return$namespace."Database\\Factories\\".$modelName."Factory";});}/**     * Bootstrap services.     *     * @return void     */publicfunctionboot(){foreach(glob(base_path('modules/*'))?:[]as$dir){$this->loadMigrationsFrom("{$dir}/database/migrations");$this->loadTranslationsFrom("{$dir}/resources/lang",basename($dir));$this->loadViewsFrom("{$dir}/resources/views",basename($dir));}}}
Enter fullscreen modeExit fullscreen mode

Register it inconfig/app.php:

/* * Application Service Providers... */App\Providers\AppServiceProvider::class,App\Providers\ModuleServiceProvider::class,// <--- here it is!App\Providers\AuthServiceProvider::class,// App\Providers\BroadcastServiceProvider::class,App\Providers\EventServiceProvider::class,App\Providers\RouteServiceProvider::class,
Enter fullscreen modeExit fullscreen mode

Let's make our first module

We need the folder structure for our module (or model classes will be generated at the root ofmodules/name/app/):

mkdir-p modules/billing/app/Models
Enter fullscreen modeExit fullscreen mode

And we need to updatecomposer.json as well:

{"autoload":{"psr-4":{"App\\":"app/","Database\\Factories\\":"database/factories/","Database\\Seeders\\":"database/seeders/","Modules\\Billing\\App\\":"modules/billing/app","Modules\\Billing\\Database\\Factories\\":"modules/billing/database/factories/","Modules\\Billing\\Database\\Seeders\\":"modules/billing/database/seeders/"}}}
Enter fullscreen modeExit fullscreen mode

You're going to copy/paste those last three lines for each module you create down the road.

Now we can make our model and its associated classes:

php artisan make:model--module billing--all Invoice
Enter fullscreen modeExit fullscreen mode

The result?

billing/app/    Http/Controllers/OrderController.php    Models/Order.php    Policies/OrderPolicy.phpbilling/database/    factories/OrderFactory.php    migrations/2021_09_21_203852_create_orders_table.php    seeders/OrderSeeder.php
Enter fullscreen modeExit fullscreen mode

Fixing some stuff

Some make commands won't generate the correct namespace, no matter whatbase_path() we're using (for the seeder stub, it's even hardcoded 🤦). They were simply not intended to work this way. So let's fix that.

Inmodules/billing/database/factories/InvoiceFactory.php:

namespaceModules\Billing\Database\Factories;// <--- add the Modules\Billing prefix
Enter fullscreen modeExit fullscreen mode

Do exactly the same inmodules/billing/database/seeders/InvoiceSeeder.php.

That's it. Now if you runphp artisan migrate, you'll see somehting like:

Migrating: 2021_09_21_203852_create_invoices_tableMigrated:  2021_09_21_203852_create_invoices_table (38.79ms)
Enter fullscreen modeExit fullscreen mode

And if you try to generate an invoice using tinker:

Psy Shell v0.10.8 (PHP 8.0.9 — cli) by Justin Hileman>>> Modules\Billing\App\Models\Invoice::factory()->create()=> Modules\Billing\App\Models\Invoice {#3518     updated_at: "2021-09-21 21:32:15",     created_at: "2021-09-21 21:32:15",     id: 1,   }
Enter fullscreen modeExit fullscreen mode

Looks like everything works well in our database 👌

Congratulations, you're done!

Well, that's pretty much it. I tested all the vanillamake:* commands available, and most of them work fine (except for the database ones we had to fix, of course.)

Now if your module needs views, routes, events etc. I suggest you abuse themake:provider command.

php artisan make:provider--module billing RouteServiceProvider
Enter fullscreen modeExit fullscreen mode

Thanks for reading

I hope you enjoyed reading this article! If so, please leave a ❤️ or a 🦄 andconsider subscribing! I write posts on PHP, architecture, and Laravel monthly.

Disclaimer I had this idea this morning taking my shower 🚿I haven't thoroughly tested the implications of this, and I suggest you exert caution applying this method. Let me know in the comment what you found out 👍

Top comments(16)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
davorminchorov profile image
Davor Minchorov
Senior PHP contractor via Adeva. Laravel, VueJS. TailwindCSS, NodeJS, MySQL, ElasticSearch
  • Location
    Skopje, Macedonia
  • Work
    Senior PHP Contractor via Adeva
  • Joined

Interesting idea, I am currently working on my personal website and blog API, and one of the things I am experimenting is something similar to what you've done here, complicating it for educational purposes mainly.

Here's the repository if you want to check it out.

It's in early development but I'll clean things up as I develop it further.

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Hey Davor, thanks for your comment 👍

I see you've restructured Laravel's default folder structure entirely. How does that work for you?

CollapseExpand
 
davorminchorov profile image
Davor Minchorov
Senior PHP contractor via Adeva. Laravel, VueJS. TailwindCSS, NodeJS, MySQL, ElasticSearch
  • Location
    Skopje, Macedonia
  • Work
    Senior PHP Contractor via Adeva
  • Joined
• Edited on• Edited

It's been working great in the past when I had to work on a project from scratch and think of something modular like this one. I'll see how it will work in the near future, even though this repository won't grow too much, but I do plan to use these ideas for other side projects as well and see if it will be a good idea or not.

CollapseExpand
 
shealavington profile image
Shea Lavington
  • Work
    Web Developer
  • Joined

Good work! I've been using a laravel-modules package for a while now which works wonders. Registers new commands calledphp artisan module-make:x Name

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Thanks for your comment 👍

There are a lot of packages like this out there, which one is it?

CollapseExpand
 
saltibarsciai profile image
Saltibarsciai
  • Joined

Will play with it, looks promising, the whole idea of DDD in laravel needs more development. I really like zend's approach and this article reminded me of it

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Hey@saltibarsciai thanks for you comment 👍

Glad you like it. You’ll find some more articles on DDD and Laravel on my page. Don’t forget to follow me to stay updated 😎 and as always, your input & notes are welcome 🙏

CollapseExpand
 
victoor profile image
Víctor Falcón
  • Location
    Spain
  • Work
    Senior Developer at Wallbox
  • Joined

You should make a package with this in order to get this in any project with just a composer require.

I love it!

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined
• Edited on• Edited

Thanks for your comment 👍

Several other packages 📦 already exists to deal with Laravel modules. I want to see if people are actually interested before investing time and efforts into making another one 😉

CollapseExpand
 
ltsochevdev profile image
Sk1ppeR
  • Joined

Why don't you make a wrapper command to take care of the module syntax though? The way you do it would be gone the moment you upgrade the framework. I'm talking about the artisan file changes.

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Hey@ltsochevdev thanks for your message 👍

I'm not sureartisan updates when you upgrade the framework 🤔 Anyway at this stage it's just a hack. If I see people are interessted, I may make a package out of it.

CollapseExpand
 
xyluz profile image
Seyi Onifade
  • Joined

This is brilliant! Thank you

CollapseExpand
 
shunmugam profile image
shunmugam
Web dev
  • Joined

great info, you can usegithub.com/shunnmugam/laravel-admin

for achieving modular structure with admin feature s

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Thanks for your comment 🙂

CollapseExpand
 
mohammadalavi profile image
Mohammad Alavi
  • Work
    Freelance web developer
  • Joined

I think this architectural pattern would be interesting to you:
github.com/Mahmoudz/Porto

And there is an implementation of it for Laravel:
github.com/apiato/apiato

CollapseExpand
 
bdelespierre profile image
Benjamin Delespierre
I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

Thanks for your comment Mohamed 👍

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I do all sorts of mischiefs with Laravel
  • Location
    Paris, France
  • Joined

More fromBenjamin Delespierre

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp