Documentation Index Fetch the complete documentation index at: https://mintlify.com/internachi/modular/llms.txt
Use this file to discover all available pages before exploring further.
What are Plugins?
Plugins are the core mechanism that Laravel Modular uses to discover and register module resources. Each plugin:
Discovers specific types of files (routes, commands, views, etc.)
Registers those files with Laravel’s services
Runs at the appropriate time in Laravel’s lifecycle
Built-in Plugins
Laravel Modular ships with 9 plugins that handle different Laravel features:
ModulesPlugin Discovers modules via composer.json
RoutesPlugin Loads route files
ViewPlugin Registers view namespaces
ArtisanPlugin Discovers Artisan commands
BladePlugin Registers Blade components
EventsPlugin Auto-discovers event listeners
GatePlugin Maps policies to models
MigratorPlugin Registers migration paths
TranslatorPlugin Registers translation namespaces
Plugin Base Class
All plugins extend the abstract Plugin class:
abstract class Plugin
{
// Boot the plugin (called during app boot)
public static function boot ( Closure $handler , Application $app ) : void
{
static :: firstBootableAttribute () ?-> newInstance () -> boot ( static :: class , $handler , $app );
}
// Discover files/data in modules
abstract public function discover ( FinderFactory $finders ) : iterable ;
// Process discovered data
abstract public function handle ( Collection $data );
}
Required Methods
The discover() method scans modules and returns data to be cached: public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> routeFileFinder ()
-> values ()
-> map ( fn ( SplFileInfo $file ) => $file -> getRealPath ());
}
Returns an iterable (array, Collection, generator) of data.
The handle() method processes the discovered data: public function handle ( Collection $data ) : void
{
$data -> each ( fn ( string $filename ) => require $filename );
}
Receives a Collection of the cached discovery data.
Plugin Lifecycle Attributes
Plugins use PHP 8 attributes to control when they execute:
OnBoot Attribute
Runs immediately during application boot:
use InterNACHI\Modular\Plugins\Attributes\ OnBoot ;
#[ OnBoot ]
class RoutesPlugin extends Plugin
{
public static function boot ( Closure $handler , Application $app ) : void
{
if ( ! $app -> routesAreCached ()) {
$handler ( static :: class );
}
}
public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> routeFileFinder ()
-> values ()
-> map ( fn ( SplFileInfo $file ) => $file -> getRealPath ());
}
public function handle ( Collection $data ) : void
{
$data -> each ( fn ( string $filename ) => require $filename );
}
}
The default behavior (no attribute) is OnBoot. Most plugins use this pattern.
AfterResolving Attribute
Runs after a specific service is resolved from the container:
use InterNACHI\Modular\Plugins\Attributes\ AfterResolving ;
use Illuminate\View\ Factory as ViewFactory ;
#[ AfterResolving ( ViewFactory :: class , parameter : 'factory' )]
class ViewPlugin extends Plugin
{
public function __construct (
protected ViewFactory $factory ,
) {}
public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> viewDirectoryFinder ()
-> withModuleInfo ()
-> values ()
-> map ( fn ( ModuleFileInfo $dir ) => [
'namespace' => $dir -> module () -> name ,
'path' => $dir -> getRealPath (),
]);
}
public function handle ( Collection $data )
{
$data -> each ( fn ( array $d ) => $this -> factory -> addNamespace ( $d [ 'namespace' ], $d [ 'path' ]));
}
}
The parameter argument specifies how the resolved service is injected into the plugin’s constructor.
Plugin Registry
The PluginRegistry manages all registered plugins:
class PluginRegistry
{
protected array $plugins = [];
// Register plugins
public function add ( string ... $class ) : static
{
foreach ( $class as $plugin ) {
$this -> plugins [ $plugin ] ??= true ;
}
return $this ;
}
// Get a plugin instance
public function get ( string $plugin , array $parameters = []) : Plugin
{
if ( ! array_key_exists ( $plugin , $this -> plugins )) {
throw new InvalidArgumentException ( "The plugin '{ $plugin }' has not been registered." );
}
$plugin = $this -> container -> make ( $plugin , $parameters );
$this -> container -> instance ( $plugin :: class , $plugin );
return $plugin ;
}
// Get all registered plugin class names
public function all () : array
{
return array_keys ( $this -> plugins );
}
}
Registering Plugins
Plugins are registered in the service provider:
protected function registerDefaultPlugins () : void
{
$registry = $this -> app -> make ( PluginRegistry :: class );
$registry -> add (
ArtisanPlugin :: class ,
BladePlugin :: class ,
EventsPlugin :: class ,
GatePlugin :: class ,
MigratorPlugin :: class ,
ModulesPlugin :: class ,
RoutesPlugin :: class ,
TranslatorPlugin :: class ,
ViewPlugin :: class ,
);
}
Plugin Examples
Routes Plugin
Loads route files from all modules:
class RoutesPlugin extends Plugin
{
public static function boot ( Closure $handler , Application $app ) : void
{
if ( ! $app -> routesAreCached ()) {
$handler ( static :: class );
}
}
public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> routeFileFinder () // Finds *.php in */routes
-> values ()
-> map ( fn ( SplFileInfo $file ) => $file -> getRealPath ());
}
public function handle ( Collection $data ) : void
{
$data -> each ( fn ( string $filename ) => require $filename );
}
}
Artisan Plugin
Registers console commands from modules:
class ArtisanPlugin extends Plugin
{
public static function boot ( Closure $handler , Application $app ) : void
{
Artisan :: starting ( fn ( $artisan ) => $handler ( static :: class , [ 'artisan' => $artisan ]));
}
public function __construct (
protected Artisan $artisan ,
protected ModuleRegistry $registry ,
) {}
public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> commandFileFinder () // Finds *.php in */src/Console/Commands
-> withModuleInfo ()
-> values ()
-> map ( fn ( ModuleFileInfo $file ) => $file -> fullyQualifiedClassName ())
-> filter ( $this -> isInstantiableCommand ( ... ));
}
public function handle ( Collection $data ) : void
{
$data -> each ( fn ( string $fqcn ) => $this -> artisan -> resolve ( $fqcn ));
$this -> registerNamespacesInTinker ();
}
protected function isInstantiableCommand ( $command ) : bool
{
return is_subclass_of ( $command , Command :: class )
&& ! ( new ReflectionClass ( $command )) -> isAbstract ();
}
}
The Artisan plugin filters out abstract commands to prevent instantiation errors.
Gate Plugin
Automatically maps policies to models:
#[ AfterResolving ( Gate :: class , parameter : 'gate' )]
class GatePlugin extends Plugin
{
public function __construct (
protected Gate $gate
) {}
public function discover ( FinderFactory $finders ) : iterable
{
return $finders
-> modelFileFinder ()
-> withModuleInfo ()
-> values ()
-> map ( function ( ModuleFileInfo $file ) {
$fqcn = $file -> fullyQualifiedClassName ();
$namespace = rtrim ( $file -> module () -> namespaces -> first (), ' \\ ' );
$candidates = [
$namespace . ' \\ Policies \\ ' . Str :: after ( $fqcn , 'Models \\ ' ) . 'Policy' ,
$namespace . ' \\ Policies \\ ' . Str :: afterLast ( $fqcn , ' \\ ' ) . 'Policy' ,
];
foreach ( $candidates as $candidate ) {
if ( class_exists ( $candidate )) {
return [
'fqcn' => $fqcn ,
'policy' => $candidate ,
];
}
}
return null ;
})
-> filter ();
}
public function handle ( Collection $data ) : void
{
$data -> each ( fn ( array $row ) => $this -> gate -> policy ( $row [ 'fqcn' ], $row [ 'policy' ]));
}
}
Events Plugin
Auto-discovers event listeners:
#[ AfterResolving ( Dispatcher :: class , parameter : 'events' )]
class EventsPlugin extends Plugin
{
public function __construct (
protected Application $app ,
protected Dispatcher $events ,
protected Repository $config ,
) {}
public function discover ( FinderFactory $finders ) : array
{
if ( ! $this -> shouldDiscoverEvents ()) {
return [];
}
return $finders
-> listenerDirectoryFinder ()
-> withModuleInfo ()
-> reduce ( fn ( array $discovered , ModuleFileInfo $file ) => array_merge_recursive (
$discovered ,
DiscoverEvents :: within ( $file -> getPathname (), $file -> module () -> path ( 'src' ))
), []);
}
public function handle ( Collection $data ) : void
{
$data -> each ( function ( array $listeners , string $event ) {
foreach ( array_unique ( $listeners , SORT_REGULAR ) as $listener ) {
$this -> events -> listen ( $event , $listener );
}
});
}
}
Plugin Data Repository
The PluginDataRepository manages plugin discovery data and caching:
class PluginDataRepository
{
public function __construct (
protected array $data , // Cached data
protected PluginRegistry $registry , // Plugin registry
protected FinderFactory $finders , // File finders
) {}
// Get data for a specific plugin
public function get ( string $name ) : Collection
{
$this -> data [ $name ] ??= $this -> registry -> get ( $name ) -> discover ( $this -> finders );
return collect ( $this -> data [ $name ]);
}
// Get all plugin data (triggers discovery for all)
public function all () : array
{
foreach ( $this -> registry -> all () as $plugin ) {
$this -> get ( $plugin );
}
return $this -> data ;
}
}
Plugin data is lazily loaded - discovery only runs when the data is first accessed.
Creating Custom Plugins
You can create custom plugins for your own needs:
namespace App\Plugins ;
use Illuminate\Support\ Collection ;
use InterNACHI\Modular\Plugins\ Plugin ;
use InterNACHI\Modular\Support\ FinderFactory ;
class ConfigPlugin extends Plugin
{
public function discover ( FinderFactory $finders ) : iterable
{
return FinderCollection :: forFiles ()
-> name ( '*.php' )
-> inOrEmpty ( $finders -> base_path . '/*/config' )
-> values ()
-> map ( fn ( $file ) => [
'key' => pathinfo ( $file -> getFilename (), PATHINFO_FILENAME ),
'path' => $file -> getRealPath (),
]);
}
public function handle ( Collection $data ) : void
{
$data -> each ( function ( array $config ) {
config ([ $config [ 'key' ] => require $config [ 'path' ]]);
});
}
}
Registering Custom Plugins
Register in your AppServiceProvider:
use InterNACHI\Modular\ PluginRegistry ;
use App\Plugins\ ConfigPlugin ;
public function register ()
{
PluginRegistry :: register ( ConfigPlugin :: class );
}
Custom plugins follow the same lifecycle and caching behavior as built-in plugins.
Plugin Handler
The PluginHandler orchestrates plugin execution:
class PluginHandler
{
public function __construct (
protected PluginRegistry $registry ,
protected PluginDataRepository $data ,
) {}
// Boot all plugins
public function boot ( Application $app ) : void
{
foreach ( $this -> registry -> all () as $class ) {
$class :: boot ( $this -> handle ( ... ), $app );
}
}
// Execute a specific plugin
public function handle ( string $name , array $parameters = []) : mixed
{
return $this -> registry -> get ( $name , $parameters ) -> handle ( $this -> data -> get ( $name ));
}
}
Boot Attributes Interface
All boot attributes implement the HandlesBoot interface:
interface HandlesBoot
{
public function boot ( string $plugin , Closure $handler , Application $app );
}
OnBoot Implementation
#[ Attribute ( Attribute :: TARGET_CLASS )]
class OnBoot implements HandlesBoot
{
public function boot ( string $plugin , Closure $handler , Application $app )
{
$handler ( $plugin );
}
}
AfterResolving Implementation
#[ Attribute ( Attribute :: TARGET_CLASS )]
class AfterResolving implements HandlesBoot
{
public function __construct (
public string $abstract ,
public string $parameter ,
) {}
public function boot ( string $plugin , Closure $handler , Application $app )
{
$app -> afterResolving ( $this -> abstract , fn ( $resolved ) => $handler ( $plugin , [ $this -> parameter => $resolved ]));
if ( $app -> resolved ( $this -> abstract )) {
$handler ( $plugin );
}
}
}
You can create custom boot attributes by implementing the HandlesBoot interface.