| Install | |
|---|---|
composer require andrebhas/laravel-brick |
|
| Latest Version: | v1.0.0 |
| PHP: | ^8.1 |
Build modular Laravel applications with a solid foundation. Each module is an independent, self-contained unit (a "brick") that can be composed into robust, scalable applications using Domain-Driven Design principles.
"Each module is a brick. Stack them wisely."
| Feature | Description |
|---|---|
| ๐ Modular Core | Create isolated modules with a standard DDD structure |
| ๐ Internal Bridge | Optional typed, validated cross-module communication |
| โก Async Calls | Dispatch bridge calls to queues, poll results by job ID |
| ๐ก Circuit Breaker | Protect against cascading failures (closed/open/half-open) |
| ๐ Middleware Pipeline | Logging, circuit breaker, caching per bridge call |
| ๐งช Pest Testing | Auto-generate test scaffolding with every module |
| ๐ฆ Module Dependencies | Modules declare dependencies; enable/disable with checks |
| โ๏ธ Per-Module Config | Each module has its own Config/config.php |
| ๐จ Asset Publishing | Copy or symlink module assets to public/bricks/ |
| ๐ Octane Compatible | No static state; fully safe for Laravel Octane |
composer require andrebhas/laravel-brick
The package uses Laravel's auto-discovery. The service provider is registered automatically.
php artisan vendor:publish --tag=brick-config
php artisan vendor:publish --tag=brick-stubs
After running php artisan brick:make, each module follows this structure:
bricks/
โโโ Hotel/
โโโ module.json # Module manifest
โโโ Actions/
โ โโโ CreateHotelAction.php
โโโ Models/
โ โโโ Hotel.php
โโโ Repositories/
โ โโโ HotelRepositoryInterface.php
โ โโโ EloquentHotelRepository.php
โโโ Services/
โ โโโ HotelService.php
โโโ Bridge/ # Optional: inter-module communication
โ โโโ HotelBridge.php
โโโ Http/
โ โโโ Controllers/
โ โโโ Requests/
โโโ Database/
โ โโโ Migrations/
โ โโโ Seeders/
โโโ Routes/
โ โโโ api.php
โ โโโ web.php
โโโ Resources/
โ โโโ views/
โ โโโ lang/
โ โโโ assets/
โโโ Config/
โ โโโ config.php
โโโ Providers/
โ โโโ HotelServiceProvider.php
โโโ tests/
โ โโโ Pest.php
โ โโโ Feature/
โ โโโ Unit/
โโโ README.md
If you prefer to organizer your module by business features rather than technical layers, you can use the --features flag (or answer yes during the interactive prompt). This replaces the Actions directory with a Features directory:
bricks/
โโโ Hotel/
โโโ Features/
โ โโโ CreateHotel/ # A cohesive, isolated feature
โ โโโ CreateHotelAction.php # Core logic
โ โโโ CreateHotelRequest.php # Specific validation
โ โโโ CreateHotelResponse.php # Formatting Output
โ โโโ CreateHotelFeatureTest.php # Feature tests lives here
โโโ Models/
โโโ Http/
โโโ ... (other standard directories)
php artisan brick:make
Interactive flow:
Nama modul:
> Hotel
Deskripsi modul (opsional):
> Modul manajemen hotel
Versi modul [1.0.0]:
>
Buat jembatan (bridge)? (yes/no) [no]:
> yes
Buat rute API? (yes/no) [no]:
> yes
Buat rute web? (yes/no) [no]:
> no
Buat tes (Pest)? (yes/no) [yes]:
> yes
Gunakan struktur Feature-Driven? (yes/no) [no]:
> yes
Membuat modul...
โ Module [Hotel] created successfully
Or non-interactively:
php artisan brick:make Hotel --with-bridge --with-api --with-tests --features
Add to composer.json:
{
"autoload": {
"psr-4": {
"Bricks\\Hotel\\": "bricks/Hotel/"
}
}
}
Then regenerate autoload:
composer dump-autoload
php artisan brick:enable Hotel
php artisan brick:migrate Hotel
module.json){
"name": "Hotel",
"description": "Modul manajemen hotel",
"version": "1.0.0",
"namespace": "Bricks\\Hotel",
"active": true,
"order": 0,
"dependencies": ["Auth"],
"bridge": {
"class": "Bricks\\Hotel\\Bridge\\HotelBridge"
},
"author": {
"name": "Andre Bhaskoro"
}
}
namespace Bricks\Hotel\Bridge;
use AndreBhas\Brick\Brick\Bridge\Contracts\Bridgeable;
class HotelBridge implements Bridgeable
{
public static function getBridgeName(): string
{
return 'Hotel';
}
public static function getExposedMethods(): array
{
return ['getById', 'checkAvailability'];
}
public function getById(int $id): array
{
// Fetch hotel data...
return ['id' => $id, 'name' => 'Grand Hotel'];
}
public function checkAvailability(int $hotelId, string $date): bool
{
return true;
}
}
use AndreBhas\Brick\Brick\Bridge\InternalGateway;
$gateway = app(InternalGateway::class);
// Goes through middleware pipeline (logging, circuit breaker, etc.)
$hotel = $gateway->call('Hotel', 'getById', 42);
// Dispatch to queue, get a job ID
$jobId = $gateway->callAsync('Hotel', 'getById', 42);
// Later, poll for the result
$result = $gateway->getAsyncResult($jobId);
// ['status' => 'success', 'result' => [...], 'completed_at' => '...']
// or
// ['status' => 'pending', 'queued_at' => '...']
// or
// ['status' => 'failed', 'error' => '...']
php artisan brick:make-bridge Hotel
The circuit breaker prevents cascading failures when a module's bridge is unreliable.
config/brick.php)'circuit_breaker' => [
'default' => [
'threshold' => 5, // failures before opening
'timeout' => 30, // seconds before half-open
'half_open_max_attempts' => 3,
],
],
php artisan brick:bridge:status
+--------+------------+---------------+
| Bridge | State | Failure Count |
+--------+------------+---------------+
| Hotel | โ CLOSED | 0 |
| Auth | โ OPEN | 7 |
+--------+------------+---------------+
php artisan brick:bridge:status --reset=Hotel
composer test
Each generated module includes its own tests directory with Pest setup:
# Run a specific module's tests
./vendor/bin/pest bricks/Hotel/tests/
BridgeFake โ Mock bridge calls in testsuse AndreBhas\Brick\Brick\Testing\BridgeFake;
use AndreBhas\Brick\Brick\Bridge\InternalGateway;
$fake = new BridgeFake(
container: app(),
cache: app('cache')->store(),
queue: app('queue'),
);
// Register a fake response
$fake->shouldReceive('Hotel', 'getById', fn ($id) => ['id' => $id, 'name' => 'Test Hotel']);
// Swap in the container
app()->instance(InternalGateway::class, $fake);
// ... run your code ...
// Assert calls
$fake->assertCalled('Hotel', 'getById', 1);
$fake->assertNotCalled('Hotel', 'delete');
$fake->assertNothingCalled(); // if no calls expected
CircuitBreakerFake โ Simulate circuit statesuse AndreBhas\Brick\Brick\Testing\CircuitBreakerFake;
use AndreBhas\Brick\Brick\Bridge\CircuitBreaker;
$fake = new CircuitBreakerFake();
$fake->forceOpen('Hotel'); // simulate open circuit
$fake->forceHalfOpen('Payment');
$fake->forceClose('Auth');
app()->instance(CircuitBreaker::class, $fake);
# Feature test
php artisan brick:make-test Hotel HotelAvailability --type=feature
# Unit test
php artisan brick:make-test Hotel HotelPricing --type=unit
| Command | Description |
|---|---|
brick:make |
Create a new module interactively |
brick:make-feature {module} {feature} |
Scaffold a full feature (Action, Request, etc) |
brick:make-action {module} {name} |
Generate an Action class |
brick:make-request {module} {name} |
Generate a FormRequest class |
brick:make-response {module} {name} |
Generate a Response/Resource class |
brick:make-job {module} {name} |
Generate a specific Job |
brick:make-event {module} {name} |
Generate an Event |
brick:make-listener {module} {name} |
Generate a Listener |
brick:make-bridge {module} |
Generate bridge for a module |
brick:make-test {module} {name} |
Generate a Pest test file |
brick:bridge:status |
Show circuit breaker status for all bridges |
brick:enable {module} |
Enable a module (checks dependencies) |
brick:disable {module} |
Disable a module (checks dependents) |
brick:publish-assets {module?} |
Publish module assets to public/ |
brick:migrate {module} |
Run module migrations |
brick:rollback {module} |
Rollback module migrations |
brick:refresh {module} |
Refresh (rollback + re-run) module migrations |
brick:seed {module} |
Run module seeders |
# Enable with --force to skip dependency check
php artisan brick:enable Hotel --force
# Disable with --force to skip dependents check
php artisan brick:disable Hotel --force
# Publish assets as symlinks
php artisan brick:publish-assets Hotel --symlink
# Run specific number of rollback steps
php artisan brick:rollback Hotel --step=2
# Refresh and seed
php artisan brick:refresh Hotel --seed
# Run specific seeder class
php artisan brick:seed Hotel --class="Bricks\\Hotel\\Database\\Seeders\\HotelDemoSeeder"
// config/brick.php
return [
// Root directory for all modules
'modules_path' => base_path('bricks'),
'bridge' => [
'enabled' => env('BRICK_BRIDGE_ENABLED', true),
// Middleware applied to every synchronous bridge call
'middlewares' => [
\AndreBhas\Brick\Brick\Bridge\Middleware\LoggingMiddleware::class,
\AndreBhas\Brick\Brick\Bridge\Middleware\CircuitBreakerMiddleware::class,
// \AndreBhas\Brick\Brick\Bridge\Middleware\CacheMiddleware::class,
],
'circuit_breaker' => [
'default' => [
'threshold' => 5,
'timeout' => 30,
'half_open_max_attempts' => 3,
],
],
'queue' => env('BRICK_BRIDGE_QUEUE', 'default'),
'cache_store' => env('BRICK_BRIDGE_CACHE', null),
'cache_ttl' => 3600,
],
'assets' => [
'publish_method' => env('BRICK_ASSETS_METHOD', 'copy'), // 'copy' or 'symlink'
'public_path' => public_path('bricks'),
],
'dependencies' => [
'auto_enable' => env('BRICK_AUTO_ENABLE_DEPENDENCIES', false),
],
];
| Variable | Default | Description |
|---|---|---|
BRICK_BRIDGE_ENABLED |
true |
Enable/disable the bridge globally |
BRICK_BRIDGE_QUEUE |
default |
Queue for async bridge calls |
BRICK_BRIDGE_CACHE |
null |
Cache store for circuit breaker & async results |
BRICK_ASSETS_METHOD |
copy |
Asset publishing method (copy or symlink) |
BRICK_AUTO_ENABLE_DEPENDENCIES |
false |
Auto-enable missing dependencies |
Create a middleware class:
namespace App\Brick\Middleware;
use Closure;
class RateLimitMiddleware
{
public function handle(Closure $next, string $bridge, string $method, array $args): mixed
{
// Rate limiting logic...
return $next($bridge, $method, $args);
}
}
Register in config/brick.php:
'middlewares' => [
\AndreBhas\Brick\Brick\Bridge\Middleware\LoggingMiddleware::class,
\AndreBhas\Brick\Brick\Bridge\Middleware\CircuitBreakerMiddleware::class,
\App\Brick\Middleware\RateLimitMiddleware::class, // <-- add here
],
Declare dependencies in module.json:
{
"name": "Booking",
"dependencies": ["Hotel", "Auth"]
}
When enabling:
php artisan brick:enable Booking
# Error: Cannot enable [Booking]. Missing dependencies: Hotel (inactive).
# Use --force to override.
php artisan brick:enable Hotel
php artisan brick:enable Booking # Now works
| Package | Notes |
|---|---|
| Spatie Media Library | Use HasMedia trait directly in module models |
| Laravel Socialite | Place OAuth controllers in Auth module's Http/Controllers/ |
| Laravel Passport | Register Passport in Auth module's service provider |
| Laravel Telescope | Automatically captures all bridge calls via logging middleware |
| Laravel Debugbar | No conflicts; bridge calls appear in the timeline |
| Laravel Octane | Fully safe โ no static state anywhere in the package |
laravel-brick/
โโโ src/
โ โโโ BrickServiceProvider.php # Auto-discovery entry point
โ โโโ Brick/
โ โโโ Bridge/
โ โ โโโ Contracts/Bridgeable.php # Interface for bridge classes
โ โ โโโ CircuitBreaker.php # State machine (closed/open/half-open)
โ โ โโโ InternalGateway.php # Main call dispatcher
โ โ โโโ Jobs/BridgeJob.php # Queued async bridge job
โ โ โโโ Middleware/
โ โ โโโ LoggingMiddleware.php
โ โ โโโ CircuitBreakerMiddleware.php
โ โ โโโ CacheMiddleware.php
โ โโโ Commands/ # 11 Artisan commands
โ โโโ Providers/
โ โ โโโ BrickServiceProvider.php
โ โ โโโ BridgeServiceProvider.php
โ โโโ Support/
โ โ โโโ Module.php # Module value object
โ โ โโโ ModuleManager.php # Module discovery & state management
โ โโโ Stubs/ # Templates used by generators
โ โ โโโ module/
โ โ โโโ bridge/
โ โโโ Testing/
โ โโโ BridgeFake.php # Test double for InternalGateway
โ โโโ CircuitBreakerFake.php # Test double for CircuitBreaker
โโโ config/brick.php
โโโ tests/
โโโ composer.json
โโโ phpunit.xml
โโโ README.md
Contributions are welcome! Please:
git checkout -b feature/my-featurecomposer testThe MIT License (MIT). See LICENSE for details.
"Each module is a brick. Stack them wisely." โ andrebhas/laravel-brick