ngarak-dev / laravel-modularization
A Laravel package for module-based application architecture with Repository + Service Pattern
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0|^12.0
- livewire/livewire: ^3.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2025-05-03 04:42:27 UTC
README
This package implements a modular architecture for Laravel applications, combining the Repository Pattern and Service Layer pattern to create maintainable, scalable applications organized by business domain rather than technical function.
Table of Contents
- Features
- Requirements
- Installation
- Step-by-Step Usage Guide
- Available Commands
- Module Structure
- Module Lifecycle
- Design Patterns
- Facade Usage
- Configuration Options
- Stub Customization
- Advanced Usage
- Troubleshooting
- Contributing
- Version History
- License
Features
- Modular Architecture: Organize code by business domain with auto-discovery
- Command-Line Generation: Generate modules, repositories, services, controllers with a single command
- Repository Pattern: Clean separation between data access and business logic
- Service Layer: Domain-specific business logic encapsulation
- Auto-Discovery: Automatic module registration, routes, views, translations and assets
- Module Management: Enable/disable modules or export them as packages
- View & Layout System: Module-specific views with dedicated layouts
- API Support: Built-in API controllers and routes
- Livewire Integration: Create interactive UIs with auto-registered components
- Events & Translations: Module-specific events and translations
- Repository Enforcement: Enforce repository pattern implementation
Requirements
- PHP 8.0 or higher
- Laravel 9.0 or higher
- Composer
Installation
Step 1: Install the Package
composer require ngarak-dev/laravel-modularization
Step 2: Publish the Configuration
php artisan vendor:publish --provider="NgarakDev\Modularization\Providers\ModularizationServiceProvider" --tag="modularization-config"
Step 3: Customize Configuration (Optional)
Edit config/modularization.php
to:
- Change the modules directory (default:
modules/
) - Adjust module namespace (default:
Modules
) - Customize auto-registration settings
- Enable/disable repository pattern enforcement
Step 4: Create Directory Structure
The package will automatically create the modules directory when you create your first module.
Step 5: Run composer dump-autoload
After installation, run the following command to ensure helper functions are properly loaded:
composer dump-autoload
Helper Functions
The package provides helper functions to simplify working with modules:
module_path()
Get the path to modules or a specific module:
// Get path to a module $modulePath = module_path('Products'); // /path/to/your/app/modules/Products // Get path to a specific directory within a module $viewsPath = module_path('Products', 'Resources/views'); // /path/to/your/app/modules/Products/Resources/views
The module_path()
function is especially useful for:
- Loading view files from specific module directories
- Including configuration files
- Specifying paths for migrations and seeders
- Working with module-specific assets
Note: The function respects the modules_path
setting in your config/modularization.php
file, so if you change the default modules directory, this function will continue to work correctly.
Step-by-Step Usage Guide
1. Create Your First Module
php artisan module:make Products
This creates a new module with the following structure:
modules/Products/
├── Http/Controllers/
├── Models/
├── Providers/
│ └── ProductsServiceProvider.php
├── Repositories/
│ └── Interfaces/
├── Services/
│ └── Interfaces/
└── Routes/
├── web.php
└── api.php
2. Add API Support (Optional)
php artisan module:make Orders --api
This adds API controllers and routes to your module:
modules/Orders/
├── Http/
│ ├── Controllers/
│ │ └── API/
│ │ └── OrdersController.php
...
└── Routes/
├── web.php
└── api.php
3. Add Views (Optional)
php artisan module:make Customers --with-views
This adds view templates and layouts to your module:
modules/Customers/
...
├── Resources/
│ └── views/
│ ├── layouts/
│ │ ├── module-layout.blade.php
│ │ └── navigation.blade.php
│ └── customers/
│ ├── index.blade.php
│ ├── create.blade.php
│ ├── edit.blade.php
│ └── show.blade.php
...
4. Add Livewire Components (Optional)
php artisan module:make Inventory --with-livewire
This adds Livewire components and views to your module:
modules/Inventory/
...
├── Livewire/
│ ├── InventoryTable.php
│ └── InventoryForm.php
├── Resources/
│ └── views/
│ └── livewire/
│ ├── inventory-table.blade.php
│ └── inventory-form.blade.php
├── Routes/
│ ├── web.php
│ ├── api.php
│ └── livewire.php
...
5. Add Resource Controller and Routes (Optional)
php artisan module:make Products --with-resource
This generates a resourceful controller with all standard CRUD methods and registers resource routes:
modules/Products/
├── Http/
│ ├── Controllers/
│ │ └── ProductsController.php # With resource methods: index, create, store, show, edit, update, destroy
├── Routes/
│ └── web.php # With resource routes
...
The generated controller includes all RESTful resource methods following Laravel's conventions:
// modules/Products/Http/Controllers/ProductsController.php namespace Modules\Products\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Routing\Controller; class ProductsController extends Controller { /** * Display a listing of the resource. * @return \Illuminate\View\View */ public function index() { return view('products::index'); } /** * Show the form for creating a new resource. * @return \Illuminate\View\View */ public function create() { return view('products::create'); } /** * Store a newly created resource in storage. * @param Request $request * @return \Illuminate\Http\RedirectResponse */ public function store(Request $request) { // Store logic here return redirect()->route('products.index'); } /** * Show the specified resource. * @param int $id * @return \Illuminate\View\View */ public function show($id) { return view('products::show', compact('id')); } /** * Show the form for editing the specified resource. * @param int $id * @return \Illuminate\View\View */ public function edit($id) { return view('products::edit', compact('id')); } /** * Update the specified resource in storage. * @param Request $request * @param int $id * @return \Illuminate\Http\RedirectResponse */ public function update(Request $request, $id) { // Update logic here return redirect()->route('products.index'); } /** * Remove the specified resource from storage. * @param int $id * @return \Illuminate\Http\RedirectResponse */ public function destroy($id) { // Delete logic here return redirect()->route('products.index'); } }
The routes are also automatically registered as a resource in web.php:
// modules/Products/Routes/web.php Route::resource('products', 'ProductsController');
6. Define Your Model
Create a model in your module:
// modules/Products/Models/Product.php namespace Modules\Products\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; class Product extends Model { use HasFactory; protected $fillable = [ 'name', 'description', 'price', 'is_active' ]; protected $casts = [ 'price' => 'decimal:2', 'is_active' => 'boolean', ]; }
7. Create a Migration
php artisan make:migration create_products_table
Move this migration to your module's Database/Migrations
directory and customize it:
// modules/Products/Database/Migrations/xxxx_xx_xx_create_products_table.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description')->nullable(); $table->decimal('price', 10, 2); $table->boolean('is_active')->default(true); $table->timestamps(); }); } public function down() { Schema::dropIfExists('products'); } };
8. Implement Repository Interface and Class
The repository interface and class should already be scaffolded. Update them to match your model:
// modules/Products/Repositories/Interfaces/ProductRepositoryInterface.php namespace Modules\Products\Repositories\Interfaces; interface ProductRepositoryInterface { public function getAll(); public function findById($id); public function create(array $data); public function update($id, array $data); public function delete($id); public function getActive(); } // modules/Products/Repositories/ProductRepository.php namespace Modules\Products\Repositories; use Modules\Products\Models\Product; use Modules\Products\Repositories\Interfaces\ProductRepositoryInterface; class ProductRepository implements ProductRepositoryInterface { protected $model; public function __construct(Product $model) { $this->model = $model; } public function getAll() { return $this->model->all(); } public function findById($id) { return $this->model->findOrFail($id); } public function create(array $data) { return $this->model->create($data); } public function update($id, array $data) { $model = $this->findById($id); $model->update($data); return $model; } public function delete($id) { $model = $this->findById($id); return $model->delete(); } public function getActive() { return $this->model->where('is_active', true)->get(); } }
9. Implement Service Interface and Class
// modules/Products/Services/Interfaces/ProductServiceInterface.php namespace Modules\Products\Services\Interfaces; interface ProductServiceInterface { public function getAllProducts(); public function getProductById($id); public function createProduct(array $data); public function updateProduct($id, array $data); public function deleteProduct($id); public function getActiveProducts(); } // modules/Products/Services/ProductService.php namespace Modules\Products\Services; use Modules\Products\Repositories\Interfaces\ProductRepositoryInterface; use Modules\Products\Services\Interfaces\ProductServiceInterface; class ProductService implements ProductServiceInterface { protected $repository; public function __construct(ProductRepositoryInterface $repository) { $this->repository = $repository; } public function getAllProducts() { return $this->repository->getAll(); } public function getProductById($id) { return $this->repository->findById($id); } public function createProduct(array $data) { // You can add business logic here before creating return $this->repository->create($data); } public function updateProduct($id, array $data) { // You can add business logic here before updating return $this->repository->update($id, $data); } public function deleteProduct($id) { return $this->repository->delete($id); } public function getActiveProducts() { return $this->repository->getActive(); } }
10. Register Dependencies in Service Provider
The module service provider is automatically created and registered. Bind your interfaces:
// modules/Products/Providers/ProductsServiceProvider.php namespace Modules\Products\Providers; use Illuminate\Support\ServiceProvider; use Modules\Products\Repositories\Interfaces\ProductRepositoryInterface; use Modules\Products\Repositories\ProductRepository; use Modules\Products\Services\Interfaces\ProductServiceInterface; use Modules\Products\Services\ProductService; class ProductsServiceProvider extends ServiceProvider { public function register() { $this->app->bind(ProductRepositoryInterface::class, ProductRepository::class); $this->app->bind(ProductServiceInterface::class, ProductService::class); } public function boot() { // Additional module boot logic here } }
11. Update Controllers
The web and API controllers should already be scaffolded. Update them to use your service:
// modules/Products/Http/Controllers/ProductsController.php namespace Modules\Products\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\Products\Services\Interfaces\ProductServiceInterface; use Modules\Products\Http\Requests\StoreProductRequest; use Modules\Products\Http\Requests\UpdateProductRequest; class ProductsController extends Controller { protected $productService; public function __construct(ProductServiceInterface $productService) { $this->productService = $productService; } public function index() { $products = $this->productService->getAllProducts(); return view('products::products.index', compact('products')); } public function create() { return view('products::products.create'); } public function store(StoreProductRequest $request) { $product = $this->productService->createProduct($request->validated()); return redirect()->route('products.show', $product->id) ->with('success', 'Product created successfully.'); } public function show($id) { $product = $this->productService->getProductById($id); return view('products::products.show', compact('product')); } public function edit($id) { $product = $this->productService->getProductById($id); return view('products::products.edit', compact('product')); } public function update(UpdateProductRequest $request, $id) { $product = $this->productService->updateProduct($id, $request->validated()); return redirect()->route('products.show', $product->id) ->with('success', 'Product updated successfully.'); } public function destroy($id) { $this->productService->deleteProduct($id); return redirect()->route('products.index') ->with('success', 'Product deleted successfully.'); } }
For API controllers:
// modules/Products/Http/Controllers/API/ProductsController.php namespace Modules\Products\Http\Controllers\API; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\Products\Services\Interfaces\ProductServiceInterface; use Modules\Products\Http\Requests\StoreProductRequest; use Modules\Products\Http\Requests\UpdateProductRequest; class ProductsController extends Controller { protected $productService; public function __construct(ProductServiceInterface $productService) { $this->productService = $productService; } public function index() { $products = $this->productService->getAllProducts(); return response()->json(['data' => $products]); } public function store(StoreProductRequest $request) { $product = $this->productService->createProduct($request->validated()); return response()->json(['data' => $product], 201); } public function show($id) { $product = $this->productService->getProductById($id); return response()->json(['data' => $product]); } public function update(UpdateProductRequest $request, $id) { $product = $this->productService->updateProduct($id, $request->validated()); return response()->json(['data' => $product]); } public function destroy($id) { $this->productService->deleteProduct($id); return response()->json(null, 204); } }
12. Create Form Requests
Generate form requests for validation:
// modules/Products/Http/Requests/StoreProductRequest.php namespace Modules\Products\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreProductRequest extends FormRequest { public function authorize() { return true; } public function rules() { return [ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'price' => 'required|numeric|min:0', 'is_active' => 'boolean', ]; } } // modules/Products/Http/Requests/UpdateProductRequest.php namespace Modules\Products\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class UpdateProductRequest extends FormRequest { public function authorize() { return true; } public function rules() { return [ 'name' => 'sometimes|required|string|max:255', 'description' => 'nullable|string', 'price' => 'sometimes|required|numeric|min:0', 'is_active' => 'boolean', ]; } }
13. Add Routes
Routes are already set up in the module, but you may need to customize them:
// modules/Products/Routes/web.php use Illuminate\Support\Facades\Route; use Modules\Products\Http\Controllers\ProductsController; Route::middleware('web')->group(function() { Route::prefix('products')->group(function() { Route::get('/', [ProductsController::class, 'index'])->name('products.index'); Route::get('/create', [ProductsController::class, 'create'])->name('products.create'); Route::post('/', [ProductsController::class, 'store'])->name('products.store'); Route::get('/{id}', [ProductsController::class, 'show'])->name('products.show'); Route::get('/{id}/edit', [ProductsController::class, 'edit'])->name('products.edit'); Route::put('/{id}', [ProductsController::class, 'update'])->name('products.update'); Route::delete('/{id}', [ProductsController::class, 'destroy'])->name('products.destroy'); }); }); // modules/Products/Routes/api.php use Illuminate\Support\Facades\Route; use Modules\Products\Http\Controllers\API\ProductsController; Route::middleware('api')->prefix('api')->group(function() { Route::apiResource('products', ProductsController::class); });
14. Create Livewire Components (Optional)
You can add Livewire components to an existing module:
php artisan module:make-livewire Products ProductForm php artisan module:make-livewire Products ProductTable
Update the components to work with your services:
// modules/Products/Livewire/ProductForm.php namespace Modules\Products\Livewire; use Livewire\Component; use Modules\Products\Services\Interfaces\ProductServiceInterface; class ProductForm extends Component { public $name; public $description; public $price; public $is_active = true; public $product; public $editing = false; protected $rules = [ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'price' => 'required|numeric|min:0', 'is_active' => 'boolean', ]; public function mount($productId = null, ProductServiceInterface $productService) { if ($productId) { $this->editing = true; $this->product = $productService->getProductById($productId); $this->name = $this->product->name; $this->description = $this->product->description; $this->price = $this->product->price; $this->is_active = $this->product->is_active; } } public function save(ProductServiceInterface $productService) { $validatedData = $this->validate(); if ($this->editing) { $productService->updateProduct($this->product->id, $validatedData); $this->dispatch('productUpdated'); } else { $productService->createProduct($validatedData); $this->dispatch('productCreated'); } $this->reset(['name', 'description', 'price']); $this->is_active = true; session()->flash('success', $this->editing ? 'Product updated successfully!' : 'Product created successfully!'); } public function render() { return view('products::livewire.product-form'); } }
Use the component in your views:
@livewire('products::product-form')
Available Commands
Module Management
# Create a new module php artisan module:make ModuleName [options] # Alternative command php artisan make:module ModuleName [options] # Module creation options: # --api : Generate API controller and routes # --force : Force overwrite if module already exists # --resource=ResourceName : Create a resource within the module (can specify multiple separated by comma) # --with-views : Generate view files for the module # --with-livewire : Generate Livewire components # --with-livewire-only : Generate only Livewire components without controllers # --with-crud : Generate CRUD operations # --with-translations : Generate translation files (en, es, fr, de) # --languages=* : Specify languages for translation files # Enable or disable a module php artisan module:toggle ModuleName [--enable] [--disable] # Export a module as a package php artisan module:export ModuleName # Create a module manager dashboard php artisan module:make-manager [ModuleName] [--force] # Create module-specific migrations php artisan module:make-migration migration_name ModuleName [--create=table_name] [--table=table_name] [--path=custom/path] # Run migrations for a specific module php artisan module:migrate ModuleName [--force] [--seed] [--step] [--pretend] [--fresh] [--rollback] [--status] [--reset] [--refresh] # Run migrations for all modules php artisan module:migrate-all [--force] [--seed] [--step] [--pretend] [--only-enabled] [--fresh] [--rollback] [--status] [--reset] [--refresh]
Authentication Scaffolding
# Generate a standalone authentication module
php artisan module:make-auth [ModuleName] [--force]
This command creates a complete standalone authentication module for your Laravel application, including:
- Login and registration controllers
- Password reset functionality
- Email verification
- Authentication middleware
- Blade templates with Tailwind CSS styling
- Authentication routes
- Dashboard page
The generated authentication system uses pure Blade templates (no Livewire) and follows Laravel's best practices. By default, it will create a module named 'Auth' if no name is provided.
When you run this command, it generates the following structure:
modules/Auth/
├── Http/
│ ├── Controllers/
│ │ └── Auth/ # Authentication controllers
│ │ ├── LoginController.php
│ │ ├── RegisterController.php
│ │ ├── ForgotPasswordController.php
│ │ ├── ResetPasswordController.php
│ │ └── VerifyEmailController.php
│ └── Middleware/ # Authentication middleware
│ ├── Authenticate.php
│ └── RedirectIfAuthenticated.php
├── Resources/
│ └── views/
│ ├── auth/ # Authentication views
│ ├── login.blade.php
│ ├── register.blade.php
│ ├── forgot-password.blade.php
│ ├── reset-password.blade.php
│ └── verify-email.blade.php
│ ├── dashboard/
│ └── index.blade.php # Dashboard view
│ └── layouts/
│ └── auth-layout.blade.php # Authentication layout
├── Providers/
│ └── AuthServiceProvider.php # Service provider with route and middleware registration
└── Routes/
├── auth.php # Authentication routes
└── web.php # Dashboard routes
Sample Generated Code
Controllers (LoginController.php):
namespace Modules\Auth\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class LoginController extends Controller { public function showLoginForm() { return view('auth::auth.login'); } public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]); if (Auth::attempt($credentials, $request->boolean('remember'))) { $request->session()->regenerate(); return redirect()->intended(route('auth.dashboard')); } return back()->withErrors([ 'email' => 'The provided credentials do not match our records.', ])->onlyInput('email'); } public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('auth.login'); } }
Routes (auth.php):
use Illuminate\Support\Facades\Route; use Modules\Auth\Http\Controllers\Auth\LoginController; use Modules\Auth\Http\Controllers\Auth\RegisterController; use Modules\Auth\Http\Controllers\Auth\ForgotPasswordController; use Modules\Auth\Http\Controllers\Auth\ResetPasswordController; use Modules\Auth\Http\Controllers\Auth\VerifyEmailController; Route::middleware('web')->group(function () { // Guest routes Route::middleware('auth.guest')->group(function () { // Login routes Route::get('/login', [LoginController::class, 'showLoginForm']) ->name('auth.login'); Route::post('/login', [LoginController::class, 'login']); // Registration routes Route::get('/register', [RegisterController::class, 'showRegistrationForm']) ->name('auth.register'); Route::post('/register', [RegisterController::class, 'register']); // Password reset routes Route::get('/forgot-password', [ForgotPasswordController::class, 'showLinkRequestForm']) ->name('auth.password.request'); Route::post('/forgot-password', [ForgotPasswordController::class, 'sendResetLinkEmail']) ->name('auth.password.email'); Route::get('/reset-password/{token}', [ResetPasswordController::class, 'showResetForm']) ->name('auth.password.reset'); Route::post('/reset-password', [ResetPasswordController::class, 'reset']) ->name('auth.password.update'); }); // Auth routes Route::middleware('auth.auth')->group(function () { Route::post('/logout', [LoginController::class, 'logout']) ->name('auth.logout'); // Email verification routes Route::get('/email/verify', [VerifyEmailController::class, 'show']) ->name('auth.verification.notice'); Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, 'verify']) ->name('auth.verification.verify'); Route::post('/email/verification-notification', [VerifyEmailController::class, 'send']) ->name('auth.verification.send'); }); });
View (login.blade.php):
@extends('auth::layouts.auth-layout') @section('content') <div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100"> <div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg"> <h2 class="text-center text-2xl font-bold text-gray-800 mb-8">{{ __('Login') }}</h2> @if (session('status')) <div class="mb-4 font-medium text-sm text-green-600"> {{ session('status') }} </div> @endif <form method="POST" action="{{ route('auth.login') }}"> @csrf <!-- Email Address --> <div class="mb-4"> <label for="email" class="block text-sm font-medium text-gray-700">{{ __('Email') }}</label> <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> @error('email') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror </div> <!-- Password --> <div class="mb-4"> <label for="password" class="block text-sm font-medium text-gray-700">{{ __('Password') }}</label> <input id="password" type="password" name="password" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> @error('password') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror </div> <!-- Remember Me --> <div class="flex items-center justify-between mb-4"> <div class="flex items-center"> <input id="remember_me" type="checkbox" name="remember" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"> <label for="remember_me" class="ml-2 block text-sm text-gray-700"> {{ __('Remember me') }} </label> </div> @if (Route::has('auth.password.request')) <a class="text-sm text-indigo-600 hover:text-indigo-900" href="{{ route('auth.password.request') }}"> {{ __('Forgot your password?') }} </a> @endif </div> <div class="flex items-center justify-between mt-6"> <div> @if (Route::has('auth.register')) <a class="text-sm text-indigo-600 hover:text-indigo-900" href="{{ route('auth.register') }}"> {{ __('Need an account?') }} </a> @endif </div> <button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150"> {{ __('Log in') }} </button> </div> </form> </div> </div> @endsection
Component Generation
# Add a Livewire component to a module php artisan module:make-livewire ModuleName ComponentName [--force] [--subdirectory=Subfolder] [--view-only] [--class-only] # Create module-specific events php artisan module:make-event ModuleName EventName [--force] # Create module-specific translations php artisan module:make-translation ModuleName Language [--force]
Customization
# Publish stubs for customization
php artisan module:publish-stubs
Module Migrations
The package provides commands to run migrations specifically for your modules:
# Create a migration for a specific module php artisan module:make-migration migration_name ModuleName [--create=table_name] [--table=table_name] [--path=custom/path]
This command creates a new migration file in the specified module's Database/Migrations
directory.
Options:
--create=table_name
: Create a new table migration--table=table_name
: Create a table migration for an existing table--path=custom/path
: Specify a custom path within the module for the migration
# Run migrations for a specific module
php artisan module:migrate ModuleName [--force] [--seed] [--step] [--pretend]
This command runs the migrations located in the Database/Migrations
directory of the specified module.
Options:
--force
: Force the operation to run in production--seed
: Run the database seeds after migration--step
: Run the migrations incrementally--pretend
: Show the SQL queries that would run without actually executing them
Additional Migration Operations
# Run fresh migrations (drop all tables and re-run migrations) php artisan module:migrate ModuleName --fresh # Rollback the last batch of migrations php artisan module:migrate ModuleName --rollback # Show migration status php artisan module:migrate ModuleName --status # Reset all migrations (rollback all migrations) php artisan module:migrate ModuleName --reset # Refresh all migrations (reset and re-migrate) php artisan module:migrate ModuleName --refresh
# Run migrations for all modules
php artisan module:migrate-all [--force] [--seed] [--step] [--pretend] [--only-enabled]
The module:migrate-all
command runs migrations for all modules, with an additional option:
--only-enabled
: Only run migrations for modules that are enabled
All the migration operations (fresh, rollback, status, reset, refresh) are also available for the module:migrate-all
command.
This helps you manage your database migrations in a modular way, allowing you to:
- Run migrations for specific modules during development
- Deploy only certain modules to production
- Skip migrations for disabled modules
Module Structure and Configuration
All modules follow a standardized configuration structure:
// modules/ModuleName/Config/config.php return [ 'name' => 'ModuleName', // Module display name 'description' => 'Description', // Module description 'enabled' => true, // Module enabled status 'routes' => [ 'prefix' => 'modulename', // URL prefix for routes 'middleware' => ['web'], // Default middleware ], 'menu' => [ 'title' => 'ModuleName', // Menu item title 'icon' => 'fa fa-th-large', // Font Awesome icon class ], ];
Module Manager Dashboard
The Module Manager provides a web interface for managing your modules. To create a module manager:
php artisan module:make-manager
Features:
- Beautiful dashboard UI with Tailwind CSS
- List of all installed modules with their status
- Enable/disable modules with a single click
- Module details including description and configuration
- Integrated with Font Awesome icons
After installation, you can access your module manager at /modulemanager
and control all your modules from one place.
The generated module manager automatically:
- Scans for all modules in your application
- Displays status, description and other details from module configs
- Allows toggling module status (enabled/disabled)
- Uses the icon defined in each module's config
The module manager is fully customizable - you can extend it with additional functionality or modify its appearance.
Module Structure
Each module follows a consistent structure:
modules/ModuleName/
├── Http/
│ ├── Controllers/ # Web controllers
│ │ └── API/ # API controllers
│ ├── Middleware/ # Module-specific middleware
│ └── Requests/ # Form requests with validation
├── Livewire/ # Livewire components
├── Models/ # Domain models
├── Providers/ # Service providers
├── Repositories/ # Data access layer
│ └── Interfaces/ # Repository interfaces
├── Services/ # Business logic layer
│ └── Interfaces/ # Service interfaces
├── Resources/
│ ├── views/ # Module-specific views
│ │ ├── layouts/ # Module-specific layouts
│ │ └── livewire/ # Livewire component views
│ └── lang/ # Module-specific translations
├── Routes/
│ ├── web.php # Module web routes
│ ├── api.php # Module API routes
│ └── livewire.php # Livewire-specific routes
├── Config/ # Module config files
└── Database/
├── Migrations/ # Module-specific migrations
├── Seeders/ # Module-specific seeders
└── Factories/ # Model factories
Module Lifecycle
Auto-Discovery Process
The package's service provider automatically:
- Scans the configured modules directory
- Registers each module's service provider
- Loads routes (web, API, and Livewire)
- Registers views, translations, and migrations
- Auto-registers Livewire components
- Loads module-specific assets and configurations
Enabling and Disabling Modules
Modules can be enabled or disabled without removing their code:
# Disable a module php artisan module:toggle ModuleName --disable # Enable a module php artisan module:toggle ModuleName --enable
When a module is disabled:
- Its routes are not registered
- Its service provider is not loaded
- Its views and translations are not available
- Its Livewire components are not registered
Exporting Modules
Modules can be exported as standalone packages:
php artisan module:export ModuleName
This creates a package in the packages
directory with:
- A properly structured Laravel package
- Composer configuration
- Service provider
- All module files
Design Patterns
Repository Pattern
The repository pattern separates data access logic from business logic:
// Interface defines the contract interface ProductRepositoryInterface { public function all(); public function find($id); public function create(array $data); public function update($id, array $data); public function delete($id); // Added compatibility methods to standardize naming public function getAll(); public function findById($id); } // Implementation handles actual data access class ProductRepository implements ProductRepositoryInterface { protected $model; public function __construct(Product $model) { $this->model = $model; } public function all() { return $this->model->all(); } public function find($id) { return $this->model->findOrFail($id); } // Compatibility methods to standardize naming public function getAll() { return $this->all(); } public function findById($id) { return $this->find($id); } // Other methods... }
Benefits:
- Makes code more testable by allowing mock repositories in tests
- Centralizes data access logic
- Enables easy swapping of data sources without affecting business logic
- Added compatibility methods maintain consistency between repository and service naming conventions
Service Layer Pattern
The service layer contains business logic:
// Interface defines the contract interface ProductServiceInterface { public function getAllProducts(); public function getProductById($id); public function createProduct(array $data); public function updateProduct($id, array $data); public function deleteProduct($id); } // Implementation contains business rules class ProductService implements ProductServiceInterface { protected $repository; public function __construct(ProductRepositoryInterface $repository) { $this->repository = $repository; } public function getAllProducts() { return $this->repository->getAll(); } public function getProductById($id) { return $this->repository->findById($id); } // Other methods... }
View & Layout System
The package includes a module-specific view and layout system:
modules/Products/
├── Resources/
│ └── views/
│ ├── layouts/
│ │ ├── module-layout.blade.php # Module-specific layout with consistent structure
│ │ └── navigation.blade.php # Module-specific navigation menu
│ └── products/ # Module views
│ ├── index.blade.php
│ ├── create.blade.php
│ ├── edit.blade.php
│ └── show.blade.php
Benefits:
- Each module has its own isolated view structure
- Module-specific layouts allow for customization
- Navigation can be tailored to module functionality
- Views are namespaced to avoid conflicts
To use module views in controllers:
// Inside a module controller return view('products::products.index'); // Uses modules/Products/Resources/views/products/index.blade.php
For layouts:
// Inside a module view @extends('products::layouts.module-layout')
The package's view system ensures each module has its own isolated views while maintaining a consistent structure across your application.
Facade Usage
The package provides a facade for easier access to the modularization service:
use NgarakDev\Modularization\Facades\Modularization; // Get all modules $modules = Modularization::getModules(); // Check if a module exists if (Modularization::hasModule('Products')) { // ... } // Check if a module is enabled if (Modularization::isEnabled('Products')) { // ... } // Enable a module Modularization::enable('Products'); // Disable a module Modularization::disable('Products');
Configuration Options
Key options in config/modularization.php
:
return [ // Path where modules are stored 'modules_path' => 'modules', // Namespace for all modules 'namespace' => 'Modules', // Default directories created in each module 'directories' => [ 'Http/Controllers', 'Http/Controllers/API', 'Http/Middleware', 'Http/Requests', 'Models', 'Repositories', 'Repositories/Interfaces', 'Services', 'Services/Interfaces', 'Providers', 'Database/Migrations', 'Database/Seeders', 'Database/Factories', 'Routes', 'Config', 'Resources/views', 'Resources/lang', 'Livewire', 'Tests/Unit', 'Tests/Feature', ], // Auto-register controllers with routes 'auto_register_controllers' => true, // Auto-register Livewire components 'auto_register_livewire' => true, // Enforce repository interface implementation 'enforce_repository_pattern' => true, ];
Stub Customization
You can publish and customize the stubs used for code generation:
php artisan module:publish-stubs
This will copy all stubs to stubs/vendor/modularization/
in your project root, including:
- Module layouts and views
- Controllers (web and API)
- Repository interfaces and implementations
- Service interfaces and implementations
- Livewire components and views
- Route files
- And more
You can then edit these stubs to match your coding style and requirements.
Advanced Usage
Module-Specific Layouts
Each module can have its own layouts:
// In your controller return view('modulename::page')->extends('modulename::layouts.module-layout'); // Or in your blade file @extends('modulename::layouts.module-layout')
Sharing Data Between Modules
Use Laravel's event system for cross-module communication:
// In one module, create an event namespace Modules\Orders\Events; class OrderCreated { public $order; public function __construct($order) { $this->order = $order; } } // Dispatch the event event(new \Modules\Orders\Events\OrderCreated($order)); // In another module's service provider, listen for the event $this->app['events']->listen( \Modules\Orders\Events\OrderCreated::class, function ($event) { // Handle event } );
Adding Custom Module Commands
You can create custom commands for your modules:
- Create a Commands directory in your module
- Create your command class
- Register it in your module's service provider
// In your module's service provider public function boot() { if ($this->app->runningInConsole()) { $this->commands([ \Modules\YourModule\Commands\YourCustomCommand::class, ]); } }
Troubleshooting
Common Issues and Solutions
View Not Found Errors
If you encounter "View not found" errors:
- Make sure your view exists in the module's Resources/views directory
- Check that you're using the correct namespace format (
modulename::view
) - Verify that your module's service provider properly loads views
- Ensure the module is enabled (no .disabled file in module directory)
Example:
// Correct view reference return view('products::products.index'); // Incorrect view reference return view('products.index'); // Missing module namespace
Route Issues (Double-Prefixing)
If your route names are being double-prefixed (e.g., "products.products.index"):
- Make sure you're explicitly naming routes in your routes file:
// In modules/Products/Routes/web.php Route::resource('products', 'ProductsController')->names([ 'index' => 'products.index', 'create' => 'products.create', 'store' => 'products.store', 'show' => 'products.show', 'edit' => 'products.edit', 'update' => 'products.update', 'destroy' => 'products.destroy', ]);
Repository-Service Method Mismatch
If you're getting errors about undefined methods between repositories and services:
- Make sure your repository implements both standard methods (
all()
,find()
) and compatibility methods (getAll()
,findById()
) - Use appropriate method names in your service classes (
getAll()
,findById()
)
Module Not Being Discovered
If your module is not auto-discovered:
- Check that your module directory structure follows the expected pattern
- Verify that the module's service provider exists and is properly formatted
- Make sure there's no
.disabled
file in the module directory - Run
composer dump-autoload
to refresh class autoloading
Navigation Not Displaying Correctly
If your module's navigation isn't displaying correctly:
- Check that your module has a
Resources/views/layouts/navigation.blade.php
file - Verify that your module-layout.blade.php correctly includes this navigation file
- Make sure you're using the module's layout in your views with
@extends('modulename::layouts.module-layout')
Missing Navigation File in New Modules
If new modules don't have a navigation.blade.php file:
- Make sure you're using the latest version of the package (v0.1.5-alpha or later)
- Run
php artisan module:publish-stubs
to update your local stubs - Create a new module with the
--with-views
option - Verify that the navigation file is created in
Resources/views/layouts/
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Version History
v1.0.6 (Latest)
- Fixed repository-service method naming mismatch by adding compatibility methods (
getAll()
,findById()
) - Fixed double-prefixing of route names in module routes
- Resolved "View [layouts.app] not found" errors by adding module-specific navigation file
- Updated view stubs to use module-specific layouts
- Created navigation.stub for new modules
- Fixed type errors in the repository pattern implementation
- Enhanced route naming convention
v1.0.5
- Added module:make-migration command for creating module-specific migrations
- Support for custom migration paths within modules
- Options for table creation and modification in module migrations
v1.0.4
- Added module migration commands to run migrations for specific modules
- Added migrate:fresh, migrate:rollback, and migrate:status functionality
- Expanded documentation for resource generation
v1.0.3
- Fixed missing RouteServiceProvider in module:make-manager command
- Improved module manager routes registration
v1.0.2
- Added module_path() helper function for easier module path resolution
- Added standardized config structure and module manager dashboard
- Created Module Manager UI for enabling/disabling modules
- Added icon support for module menu items in configuration
v1.0.1
- Fixed missing Config/config.php file in Auth module
- Added automatic config file creation for Auth module
- Ensured Config directory is always created in module structure
v1.0.0
- First stable release with full feature set
- Complete authentication module generation
- Enhanced view styling and module discovery
v0.1.9-alpha
- Beta release with authentication scaffolding
- Improved repository and service pattern implementation
v0.1.4-alpha
- Initial public release with core functionality
- Added module generation capabilities
- Implemented repository pattern infrastructure
- Added service layer implementation
- Built module discovery and auto-registration
- Integrated Livewire component support
License
The MIT License (MIT). Please see License File for more information.