├── tests
└── .gitkeep
├── src
├── config
│ ├── .gitkeep
│ └── config.php
├── Commands
│ ├── ModulesCommand.php
│ ├── ModulesScanCommand.php
│ ├── ModulesSeedCommand.php
│ ├── ModulesGenerateCommand.php
│ ├── AbstractCommand.php
│ ├── ModulesCreateCommand.php
│ ├── ModulesPublishCommand.php
│ └── ModulesMigrateCommand.php
├── ModuleCollection.php
├── Manifest.php
├── ServiceProvider.php
├── Finder.php
└── Module.php
├── .gitignore
├── .travis.yml
├── phpunit.xml
├── composer.json
└── readme.md
/tests/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | .idea
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 | - 5.5
7 |
8 | before_script:
9 | - curl -s http://getcomposer.org/installer | php
10 | - php composer.phar install --dev
11 |
12 | script: phpunit
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "creolab/laravel-modules",
3 | "keywords": ["modules", "laravel"],
4 | "description": "Application specific modules in Laravel 4",
5 | "homepage": "http://creolab.hr",
6 | "authors": [
7 | {
8 | "name": "Boris Strahija",
9 | "email": "bstrahija@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=5.3.0",
14 | "laravel/framework": "~4"
15 | },
16 | "require-dev": {
17 | "way/generators": "~2"
18 | },
19 | "suggest": {
20 | "way/generators": "Required for modules:generate functionality."
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Creolab\\LaravelModules\\": "src/"
25 | }
26 | },
27 | "minimum-stability": "dev"
28 | }
29 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | 'app/modules',
10 |
11 | /**
12 | * If set to 'auto', the modules path will be scanned for modules
13 | */
14 | 'mode' => 'auto',
15 |
16 | /**
17 | * In case the auto detect mode is disabled, these modules will be loaded
18 | * If the mode is set to 'auto', this setting will be discarded
19 | */
20 | 'modules' => array(
21 | 'auth' => array('enabled' => true),
22 | 'content' => array('enabled' => false),
23 | 'shop' => array('enabled' => true),
24 | ),
25 |
26 | /**
27 | * Default files that are included automatically for each module
28 | */
29 | 'include' => array(
30 | 'helpers.php',
31 | 'bindings.php',
32 | 'observers.php',
33 | 'filters.php',
34 | 'composers.php',
35 | 'routes.php',
36 | ),
37 |
38 | /**
39 | * Debug mode
40 | */
41 | 'debug' => false,
42 |
43 | );
44 |
--------------------------------------------------------------------------------
/src/Commands/ModulesCommand.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class ModulesCommand extends AbstractCommand {
13 |
14 | /**
15 | * Name of the command
16 | * @var string
17 | */
18 | protected $name = 'modules';
19 |
20 | /**
21 | * Command description
22 | * @var string
23 | */
24 | protected $description = 'Just return all registered modules.';
25 |
26 | /**
27 | * Execute the console command.
28 | * @return void
29 | */
30 | public function fire()
31 | {
32 | // Return error if no modules found
33 | if (count($this->getModules()) == 0)
34 | {
35 | return $this->error("Your application doesn't have any registered modules.");
36 | }
37 |
38 | // Display the modules info
39 | $this->displayModules($this->getModules());
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/ModuleCollection.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ModuleCollection extends Collection {
11 |
12 | /**
13 | * List of all modules
14 | * @var array
15 | */
16 | public $items = array();
17 |
18 | /**
19 | * IoC
20 | * @var Illuminate\Foundation\Application
21 | */
22 | protected $app;
23 |
24 | /**
25 | * Initialize a module collection
26 | * @param Application $app
27 | */
28 | public function __construct(Application $app)
29 | {
30 | $this->app = $app;
31 | }
32 |
33 | /**
34 | * Initialize all modules
35 | * @return void
36 | */
37 | public function registerModules()
38 | {
39 | // First we need to sort the modules
40 | $this->sort(function($a, $b) {
41 | if ($a->order == $b->order) return 0;
42 | else return $a->order < $b->order ? -1 : 1;
43 | });
44 |
45 | // Then register each one
46 | foreach ($this->items as $module)
47 | {
48 | $module->register();
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/Commands/ModulesScanCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class ModulesScanCommand extends AbstractCommand {
14 |
15 | /**
16 | * Name of the command
17 | * @var string
18 | */
19 | protected $name = 'modules:scan';
20 |
21 | /**
22 | * Command description
23 | * @var string
24 | */
25 | protected $description = 'Scan modules and cache module meta data.';
26 |
27 | /**
28 | * Path to the modules monifest
29 | * @var string
30 | */
31 | protected $manifestPath;
32 |
33 | /**
34 | * Execute the console command.
35 | * @return void
36 | */
37 | public function fire()
38 | {
39 | $this->info('Scanning modules');
40 |
41 | // Get table helper
42 | $this->table = $this->getHelperSet()->get('table');
43 |
44 | // Delete the manifest
45 | $this->app['modules']->deleteManifest();
46 |
47 | // Run the scanner
48 | $this->modules = $this->app['modules']->scan();
49 |
50 | // Return error if no modules found
51 | if (count($this->modules) == 0)
52 | {
53 | return $this->error("Your application doesn't have any valid modules.");
54 | }
55 |
56 | // Also run composer dump-autoload
57 | $this->dumpAutoload();
58 |
59 | // Display number of found modules
60 | $this->info('Found ' . count($this->modules) . ' modules:');
61 |
62 | // Display the modules info
63 | $this->displayModules($this->getModules());
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/Commands/ModulesSeedCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class ModulesSeedCommand extends AbstractCommand {
14 |
15 | /**
16 | * Name of the command
17 | * @var string
18 | */
19 | protected $name = 'modules:seed';
20 |
21 | /**
22 | * Command description
23 | * @var string
24 | */
25 | protected $description = 'Seed the database from modules.';
26 |
27 | /**
28 | * Execute the console command.
29 | * @return void
30 | */
31 | public function fire()
32 | {
33 | $this->info('Seeding database from modules');
34 |
35 | // Get all modules or 1 specific
36 | if ($moduleName = $this->input->getArgument('module')) $modules = array(app('modules')->module($moduleName));
37 | else $modules = app('modules')->modules();
38 |
39 | foreach ($modules as $module)
40 | {
41 | if ($module)
42 | {
43 | if ($module->def('seeder'))
44 | {
45 | $module->seed();
46 | $this->info("Seeded '" . $module->name() . "' module.");
47 | }
48 | else
49 | {
50 | $this->line("Module '" . $module->name() . "' has no seeds.");
51 | }
52 | }
53 | else
54 | {
55 | $this->error("Module '" . $moduleName . "' does not exist.");
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Get the console command arguments.
62 | * @return array
63 | */
64 | protected function getArguments()
65 | {
66 | return array(
67 | array('module', InputArgument::OPTIONAL, 'The name of module being seeded.'),
68 | );
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/Manifest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class Manifest {
10 |
11 | /**
12 | * Path to manifest file
13 | * @var string
14 | */
15 | protected $path;
16 |
17 | /**
18 | * Manifest data
19 | * @var array
20 | */
21 | protected $data;
22 |
23 | /**
24 | * IoC
25 | * @var Illuminate\Foundation\Application
26 | */
27 | protected $app;
28 |
29 | /**
30 | * Initialize the manifest
31 | * @param Application $app [description]
32 | */
33 | public function __construct(Application $app)
34 | {
35 | $this->app = $app;
36 |
37 | // Path to manifest file
38 | $this->path = storage_path('meta/modules.json');
39 |
40 | // Try to read the file
41 | if ($this->app['files']->exists($this->path))
42 | {
43 | $this->data = @json_decode($this->app['files']->get($this->path), true);
44 | }
45 | }
46 |
47 | /**
48 | * Save the manifest data
49 | * @return void
50 | */
51 | public function save($modules)
52 | {
53 | // Prepare manifest data
54 | foreach ($modules as $module)
55 | {
56 | $this->data[$module->name()] = $module->def();
57 | }
58 |
59 | // Cache it
60 | try
61 | {
62 | $this->app['files']->put($this->path, $this->app['modules']->prettyJsonEncode($this->data));
63 | }
64 | catch(\Exception $e)
65 | {
66 | $this->app['log']->error("[MODULES] Failed when saving manifest file: " . $e->getMessage());
67 | }
68 |
69 | return $this->data;
70 | }
71 |
72 | /**
73 | * Get the manifest data as an array
74 | * @return array
75 | */
76 | public function toArray($module = null)
77 | {
78 | if ($module) return $this->data[$module];
79 | else return $this->data;
80 | }
81 |
82 | /**
83 | * Delete the manifest file
84 | * @return void
85 | */
86 | public function delete()
87 | {
88 | $this->data = null;
89 |
90 | $this->app['files']->delete($this->path);
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/Commands/ModulesGenerateCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class ModulesGenerateCommand extends AbstractCommand {
14 |
15 | /**
16 | * Name of the command
17 | * @var string
18 | */
19 | protected $name = 'modules:generate';
20 |
21 | /**
22 | * Command description
23 | * @var string
24 | */
25 | protected $description = 'Generate module resources.';
26 |
27 | /**
28 | * Execute the console command.
29 | * @return void
30 | */
31 | public function fire()
32 | {
33 | // Name of new module
34 | $module = $this->getModule($this->input->getArgument('module'));
35 | $modulePath = base_path(ltrim($module['path'], '/'));
36 | $type = $this->input->getArgument('type');
37 | $resource = $this->input->getArgument('resource');
38 |
39 | // Generate a controller
40 | if ($type == 'controller')
41 | {
42 | $dirPath = $modulePath . '/controllers';
43 | $this->call('generate:controller', array('controllerName' => $resource, '--path' => $dirPath));
44 | }
45 |
46 | // Generate a model
47 | if ($type == 'model')
48 | {
49 | $dirPath = $modulePath . '/models';
50 | $this->call('generate:model', array('modelName' => $resource, '--path' => $dirPath));
51 | }
52 |
53 | // Generate a migration
54 | if ($type == 'migration')
55 | {
56 | $dirPath = $modulePath . '/migrations';
57 | $this->call('generate:migration', array('migrationName' => $resource, '--path' => $dirPath));
58 | }
59 |
60 | // Generate a view
61 | if ($type == 'view')
62 | {
63 | $dirPath = $modulePath . '/views';
64 | $this->call('generate:view', array('viewName' => $resource, '--path' => $dirPath));
65 | }
66 | }
67 |
68 | /**
69 | * Get the console command arguments.
70 | * @return array
71 | */
72 | protected function getArguments()
73 | {
74 | return array(
75 | array('module', InputArgument::REQUIRED, 'The name of module.'),
76 | array('type', InputArgument::REQUIRED, 'Type of resource you want to generate.'),
77 | array('resource', InputArgument::REQUIRED, 'Name of resource.'),
78 | );
79 | }
80 |
81 | /**
82 | * Get the console command options.
83 | * @return array
84 | */
85 | protected function getOptions()
86 | {
87 | return array(
88 | array('path', null, InputOption::VALUE_OPTIONAL, 'Path to the directory.'),
89 | array('template', null, InputOption::VALUE_OPTIONAL, 'Path to template.')
90 | );
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/Commands/AbstractCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | abstract class AbstractCommand extends Command {
14 |
15 | /**
16 | * List of all available modules
17 | *
18 | * @var array
19 | */
20 | protected $modules;
21 |
22 | /**
23 | * IoC
24 | *
25 | * @var Illuminate\Foundation\Application
26 | */
27 | protected $app;
28 |
29 | /**
30 | * DI
31 | *
32 | * @param Application $app
33 | */
34 | public function __construct(Application $app)
35 | {
36 | parent::__construct();
37 | $this->app = $app;
38 | }
39 |
40 | /**
41 | * Get the console command options.
42 | *
43 | * @return array
44 | */
45 | protected function getOptions()
46 | {
47 | return array();
48 | }
49 |
50 | /**
51 | * Reformats the modules list for table display
52 | *
53 | * @return array
54 | */
55 | public function getModules()
56 | {
57 | $results = array();
58 |
59 | foreach($this->app['modules']->modules() as $name => $module)
60 | {
61 | $path = str_replace(app()->make('path.base'), '', $module->path());
62 |
63 | $results[] = array(
64 | 'name' => $name,
65 | 'path' => $path,
66 | 'order' => $module->order,
67 | 'enabled' => $module->enabled() ? 'true' : '',
68 | );
69 | }
70 |
71 | return array_filter($results);
72 | }
73 |
74 |
75 | /**
76 | * Return a given module
77 | *
78 | * @param $module_name
79 | * @return mixed
80 | */
81 | public function getModule($module_name)
82 | {
83 | foreach ($this->getModules() as $module) {
84 | if ($module['name'] == $module_name) {
85 | return $module;
86 | }
87 | }
88 | return false;
89 | }
90 |
91 | /**
92 | * Display a module info table in the console
93 | *
94 | * @param array $modules
95 | * @return void
96 | */
97 | public function displayModules($modules)
98 | {
99 | // Get table helper
100 | $this->table = $this->getHelperSet()->get('table');
101 |
102 | $headers = array('Name', 'Path', 'Order', 'Enabled');
103 |
104 | $this->table->setHeaders($headers)->setRows($modules);
105 |
106 | $this->table->render($this->getOutput());
107 | }
108 |
109 | /**
110 | * Dump autoload classes
111 | *
112 | * @return void
113 | */
114 | public function dumpAutoload()
115 | {
116 | // Also run composer dump-autoload
117 | $composer = new Composer($this->app['files']);
118 | $this->info('Generating optimized class loader');
119 | $composer->dumpOptimized();
120 | $this->line('');
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Commands/ModulesCreateCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class ModulesCreateCommand extends AbstractCommand {
14 |
15 | /**
16 | * Name of the command
17 | * @var string
18 | */
19 | protected $name = 'modules:create';
20 |
21 | /**
22 | * Command description
23 | * @var string
24 | */
25 | protected $description = 'Create a new module.';
26 |
27 | /**
28 | * Execute the console command.
29 | * @return void
30 | */
31 | public function fire()
32 | {
33 | // Name of new module
34 | $moduleName = $this->input->getArgument('module');
35 | $this->info('Creating module "'.$moduleName.'"');
36 |
37 | // Chech if module exists
38 | if ( ! app('modules')->module($moduleName))
39 | {
40 | // Get path to modules
41 | $modulePath = $this->app['config']->get('modules::path');
42 | if (is_array($modulePath)) $modulePath = $modulePath[0];
43 | $modulePath .= '/' . $moduleName;
44 |
45 | // Create the directory
46 | if ( ! $this->app['files']->exists($modulePath))
47 | {
48 | $this->app['files']->makeDirectory($modulePath, 0755, true);
49 | }
50 |
51 | // Create definition and write to file
52 | $definition = $this->app['modules']->prettyJsonEncode(array('enabled' => true));
53 | $this->app['files']->put($modulePath . '/module.json', $definition);
54 |
55 | // Create routes and write to file
56 | $routes = 'app['files']->put($modulePath . '/routes.php', $routes);
58 |
59 | // Create some resource directories
60 | $this->app['files']->makeDirectory($modulePath . '/assets', 0755);
61 | $this->app['files']->makeDirectory($modulePath . '/config', 0755);
62 | $this->app['files']->makeDirectory($modulePath . '/controllers', 0755);
63 | $this->app['files']->makeDirectory($modulePath . '/lang', 0755);
64 | $this->app['files']->makeDirectory($modulePath . '/models', 0755);
65 | $this->app['files']->makeDirectory($modulePath . '/migrations', 0755);
66 | $this->app['files']->makeDirectory($modulePath . '/views', 0755);
67 |
68 | // Autoload classes
69 | $this->dumpAutoload();
70 | }
71 | else
72 | {
73 | $this->error('Module with name "'.$moduleName.'" already exists.');
74 | }
75 | }
76 |
77 | /**
78 | * Get the console command arguments.
79 | * @return array
80 | */
81 | protected function getArguments()
82 | {
83 | return array(
84 | array('module', InputArgument::REQUIRED, 'The name of module being created.'),
85 | );
86 | }
87 |
88 | /**
89 | * Get the console command options.
90 | * @return array
91 | */
92 | protected function getOptions()
93 | {
94 | return array();
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/Commands/ModulesPublishCommand.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class ModulesPublishCommand extends AbstractCommand {
14 |
15 | /**
16 | * Name of the command
17 | * @var string
18 | */
19 | protected $name = 'modules:publish';
20 |
21 | /**
22 | * Command description
23 | * @var string
24 | */
25 | protected $description = 'Publish public assets for modules.';
26 |
27 | /**
28 | * Execute the console command.
29 | * @return void
30 | */
31 | public function fire()
32 | {
33 | $this->info('Publishing module assets');
34 |
35 | // Get all modules or 1 specific
36 | if ($moduleName = $this->input->getArgument('module')) $modules = array(app('modules')->module($moduleName));
37 | else $modules = app('modules')->modules();
38 |
39 | foreach ($modules as $module)
40 | {
41 | if ($module)
42 | {
43 | if ($this->app['files']->exists($module->path('assets')))
44 | {
45 | // Group path
46 | $groupPath = $module->def('group') ? str_replace('/', '_', $module->def('group')) : null;
47 |
48 | // Prepare params
49 | $path = ltrim(str_replace(app()->make('path.base'), '', $module->path()), "/") . "/assets";
50 |
51 | // Get destination path
52 | if (is_array($this->app['config']->get('modules::path')))
53 | {
54 | $destination = app()->make('path.public') . '/packages/module/' . $groupPath . '/' . $module->name() . '/assets';
55 | }
56 | else
57 | {
58 | $destination = app()->make('path.public') . '/packages/module/' . $module->name() . '/assets';
59 | }
60 |
61 | // Try to copy
62 | $success = $this->app['files']->copyDirectory($path, $destination);
63 |
64 | // Result
65 | if ( ! $success) $this->line("Unable to publish assets for module '" . $module->name() . "'");
66 | else $this->info("Published assets for module '" . $module->name() . "'");
67 | }
68 | else
69 | {
70 | $this->line("Module '" . $module->name() . "' has no assets available.");
71 | }
72 | }
73 | else
74 | {
75 | $this->error("Module '" . $moduleName . "' does not exist.");
76 | }
77 | }
78 | }
79 |
80 | /**
81 | * Get the console command arguments.
82 | * @return array
83 | */
84 | protected function getArguments()
85 | {
86 | return array(
87 | array('module', InputArgument::OPTIONAL, 'The name of module being published.'),
88 | );
89 | }
90 |
91 | /**
92 | * Get the console command options.
93 | * @return array
94 | */
95 | protected function getOptions()
96 | {
97 | return array();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | package('creolab/laravel-modules', 'modules', __DIR__);
18 |
19 | // Register commands
20 | $this->bootCommands();
21 |
22 | try
23 | {
24 | // Auto scan if specified
25 | $this->app['modules']->start();
26 |
27 | // And finally register all modules
28 | $this->app['modules']->register();
29 | }
30 | catch (\Exception $e)
31 | {
32 | $this->app['modules']->logError("There was an error when starting modules: [".$e->getMessage()."]");
33 | }
34 | }
35 |
36 | /**
37 | * Register the service provider.
38 | * @return void
39 | */
40 | public function register()
41 | {
42 | // Register IoC bindings
43 | $this->app['modules'] = $this->app->share(function($app)
44 | {
45 | return new Finder($app, $app['files'], $app['config']);
46 | });
47 | }
48 |
49 | /**
50 | * Register all available commands
51 | * @return void
52 | */
53 | public function bootCommands()
54 | {
55 | // Add modules command
56 | $this->app['modules.list'] = $this->app->share(function($app)
57 | {
58 | return new Commands\ModulesCommand($app);
59 | });
60 |
61 | // Add scan command
62 | $this->app['modules.scan'] = $this->app->share(function($app)
63 | {
64 | return new Commands\ModulesScanCommand($app);
65 | });
66 |
67 | // Add publish command
68 | $this->app['modules.publish'] = $this->app->share(function($app)
69 | {
70 | return new Commands\ModulesPublishCommand($app);
71 | });
72 |
73 | // Add migrate command
74 | $this->app['modules.migrate'] = $this->app->share(function($app)
75 | {
76 | return new Commands\ModulesMigrateCommand($app);
77 | });
78 |
79 | // Add seed command
80 | $this->app['modules.seed'] = $this->app->share(function($app)
81 | {
82 | return new Commands\ModulesSeedCommand($app);
83 | });
84 |
85 | // Add create command
86 | $this->app['modules.create'] = $this->app->share(function($app)
87 | {
88 | return new Commands\ModulesCreateCommand($app);
89 | });
90 |
91 | // Add generate command
92 | $this->app['modules.generate'] = $this->app->share(function($app)
93 | {
94 | return new Commands\ModulesGenerateCommand($app);
95 | });
96 |
97 | // Now register all the commands
98 | $this->commands(array(
99 | 'modules.list',
100 | 'modules.scan',
101 | 'modules.publish',
102 | 'modules.migrate',
103 | 'modules.seed',
104 | 'modules.create',
105 | 'modules.generate'
106 | ));
107 | }
108 |
109 | /**
110 | * Provided service
111 | * @return array
112 | */
113 | public function provides()
114 | {
115 | return array('Modules');
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/Commands/ModulesMigrateCommand.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ModulesMigrateCommand extends AbstractCommand {
16 |
17 | /**
18 | * Name of the command
19 | *
20 | * @var string
21 | */
22 | protected $name = 'modules:migrate';
23 |
24 | /**
25 | * Command description
26 | *
27 | * @var string
28 | */
29 | protected $description = 'Run migrations for modules.';
30 |
31 | /**
32 | * List of migrations
33 | *
34 | * @var array
35 | */
36 | protected $migrationList = array();
37 |
38 | /**
39 | * Execute the console command.
40 | *
41 | * @return void
42 | */
43 | public function fire()
44 | {
45 | $this->info('Migrating modules');
46 |
47 | // Get all modules or 1 specific
48 | if ($moduleName = $this->input->getArgument('module'))
49 | {
50 | $modules = array(app('modules')->module($moduleName));
51 | }
52 | else
53 | {
54 | $modules = app('modules')->modules();
55 | }
56 |
57 | foreach ($modules as $module)
58 | {
59 | if ($module)
60 | {
61 | if ($this->app['files']->exists($module->path('migrations')))
62 | {
63 | // Prepare params
64 | $path = ltrim(str_replace(app()->make('path.base'), '', $module->path()), "/") . "/migrations";
65 | $_info = array('path' => $path);
66 |
67 | // Add to migration list
68 | array_push($this->migrationList, $_info);
69 | $this->info("Added '" . $module->name() . "' to migration list.");
70 |
71 | }
72 | else
73 | {
74 | $this->line("Module '" . $module->name() . "' has no migrations.");
75 | }
76 | }
77 | else
78 | {
79 | $this->error("Module '" . $moduleName . "' does not exist.");
80 | }
81 | }
82 |
83 | if (count($this->migrationList))
84 | {
85 | $this->info("
86 | Running Migrations...");
87 |
88 | // Process migration list
89 | $this->runPathsMigration();
90 | }
91 |
92 | if ($this->input->getOption('seed'))
93 | {
94 | $this->info("Running Seeding Command...");
95 | $this->call('modules:seed');
96 | }
97 | }
98 |
99 | /**
100 | * Run paths migrations
101 | *
102 | * @return void
103 | */
104 | protected function runPathsMigration()
105 | {
106 | $_fileService = new Filesystem();
107 | $_tmpPath = app_path('storage') . DIRECTORY_SEPARATOR . 'migrations';
108 |
109 | if (!is_dir($_tmpPath) && !$_fileService->exists($_tmpPath))
110 | {
111 | $_fileService->mkdir($_tmpPath);
112 | }
113 |
114 | $this->info("Gathering migration files to {$_tmpPath}");
115 |
116 | // Copy all files to storage/migrations
117 | foreach ($this->migrationList as $migration)
118 | {
119 | $_fileService->mirror($migration['path'], $_tmpPath);
120 | }
121 |
122 | //call migrate command on temporary path
123 | $this->info("Migrating...");
124 |
125 | $opts = array('--path' => ltrim(str_replace(base_path(), '', $_tmpPath), '/'));
126 |
127 | if($this->input->getOption('force')) {
128 | $opts['--force'] = true;
129 | }
130 |
131 | if ($this->input->getOption('database')) {
132 | $opts['--database'] = $this->input->getOption('database');
133 | }
134 |
135 | $this->call('migrate', $opts);
136 |
137 | // Delete all temp migration files
138 | $this->info("Cleaning temporary files");
139 | $_fileService->remove($_tmpPath);
140 |
141 | // Done
142 | $this->info("DONE!");
143 |
144 | }
145 |
146 | /**
147 | * Get the console command arguments.
148 | *
149 | * @return array
150 | */
151 | protected function getArguments()
152 | {
153 | return array(
154 | array('module', InputArgument::OPTIONAL, 'The name of module being migrated.'),
155 | );
156 | }
157 |
158 | /**
159 | * Get the console command options.
160 | *
161 | * @return array
162 | */
163 | protected function getOptions()
164 | {
165 | return array(
166 | array('seed', null, InputOption::VALUE_NONE, 'Indicates if the module should seed the database.'),
167 | array('force', '-f', InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
168 | array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection.', null)
169 | );
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/src/Finder.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class Finder {
10 |
11 | /**
12 | * Modules collection
13 | * @var ModuleCollection
14 | */
15 | protected $modules;
16 |
17 | /**
18 | * The modules manifest
19 | * @var Manifest
20 | */
21 | protected $manifest;
22 |
23 | /**
24 | * IoC
25 | * @var Illuminate\Foundation\Application
26 | */
27 | protected $app;
28 |
29 | /**
30 | * Initialize the finder
31 | * @param Application $app
32 | */
33 | public function __construct(Application $app)
34 | {
35 | $this->app = $app;
36 | $this->modules = new ModuleCollection($app);
37 | $this->manifest = new Manifest($app);
38 | }
39 |
40 | /**
41 | * Start finder
42 | * @return void
43 | */
44 | public function start()
45 | {
46 | if ($this->app['config']->get('modules::mode') == 'auto')
47 | {
48 | $this->app['modules']->scan();
49 | }
50 | elseif ($this->app['config']->get('modules::mode') == 'manifest')
51 | {
52 | if ($manifest = $this->manifest->toArray())
53 | {
54 | $this->app['modules']->manual($this->manifest->toArray());
55 | }
56 | else
57 | {
58 | $this->app['modules']->scan();
59 | }
60 | }
61 | else
62 | {
63 | $this->app['modules']->manual();
64 | }
65 | }
66 |
67 | /**
68 | * Return module collection
69 | * @return ModuleCollection
70 | */
71 | public function modules()
72 | {
73 | return $this->modules;
74 | }
75 |
76 | /**
77 | * Return single module
78 | * @param string $id
79 | * @return Module
80 | */
81 | public function module($id)
82 | {
83 | if (isset($this->modules[$id])) return $this->modules[$id];
84 | }
85 |
86 | /**
87 | * Scan module folder and add valid modules to collection
88 | * @return array
89 | */
90 | public function scan()
91 | {
92 | // Get the modules directory paths
93 | $modulesPaths = $this->app['config']->get('modules::path');
94 | if ( ! is_array($modulesPaths)) $modulesPaths = array($modulesPaths);
95 |
96 | // Now prepare an array with all directories
97 | $paths = array();
98 | foreach ($modulesPaths as $modulesPath) $paths[$modulesPath] = $this->app['files']->directories(base_path($modulesPath));
99 |
100 | if ($paths)
101 | {
102 | foreach ($paths as $path => $directories)
103 | {
104 | if ($directories)
105 | {
106 | foreach ($directories as $directory)
107 | {
108 | // Check if dir contains a module definition file
109 | if ($this->app['files']->exists($directory . '/module.json'))
110 | {
111 | $name = pathinfo($directory, PATHINFO_BASENAME);
112 | $this->modules[$name] = new Module($name, $directory, null, $this->app, $path);
113 | }
114 | }
115 | }
116 | }
117 |
118 | // Save the manifest file
119 | $this->saveManifest();
120 | }
121 |
122 | return $this->modules;
123 | }
124 |
125 | /**
126 | * Get modules from config array
127 | * @return array
128 | */
129 | public function manual($config = null)
130 | {
131 | if (! is_null($config))
132 | {
133 | $this->createInstances($config);
134 | }
135 |
136 | else
137 | {
138 | $moduleGroups = $this->app['config']->get('modules::modules');
139 |
140 | if ($moduleGroups)
141 | {
142 | foreach ($moduleGroups as $group => $modules)
143 | {
144 | $this->createInstances($modules, $group);
145 | }
146 | }
147 | }
148 |
149 | return $this->modules;
150 | }
151 |
152 | /**
153 | * Create module instances
154 | * @param array $modules
155 | * @param string|null $groupPath
156 | * @return array
157 | */
158 | public function createInstances($modules, $groupPath = null)
159 | {
160 | foreach ($modules as $key => $module)
161 | {
162 | // Get name and defintion
163 | if (is_string($module))
164 | {
165 | $name = $module;
166 |
167 | $definition = array();
168 | }
169 |
170 | elseif (is_array($module))
171 | {
172 | $name = $key;
173 |
174 | $definition = $module;
175 | }
176 |
177 | // Get group. Manifest mode has group defined on the module.
178 | $group = (! is_null($groupPath)) ? $groupPath : $module['group'];
179 |
180 | // The path
181 | $path = base_path($group . '/' . $name);
182 |
183 | // Create instance
184 | $this->modules[$name] = new Module($name, $path, $definition, $this->app, $group);
185 | }
186 | }
187 |
188 | /**
189 | * Return manifest object
190 | * @return Manifest
191 | */
192 | public function manifest($module = null)
193 | {
194 | return $this->manifest->toArray($module);
195 | }
196 |
197 | /**
198 | * Save the manifest file
199 | * @param array $modules
200 | * @return void
201 | */
202 | public function saveManifest($modules = null)
203 | {
204 | $this->manifest->save($this->modules);
205 | }
206 |
207 | /**
208 | * Delete the manifest file
209 | * @return void
210 | */
211 | public function deleteManifest()
212 | {
213 | $this->manifest->delete();
214 | }
215 |
216 | /**
217 | * Register all modules in collection
218 | * @return void
219 | */
220 | public function register()
221 | {
222 | return $this->modules->registerModules();
223 | }
224 |
225 | /**
226 | * Log a debug message
227 | * @param string $message
228 | * @return void
229 | */
230 | public function logDebug($message)
231 | {
232 | $this->log($message);
233 | }
234 |
235 | /**
236 | * Log an error message
237 | * @param string $message
238 | * @return void
239 | */
240 | public function logError($message)
241 | {
242 | $this->log($message, 'error');
243 | }
244 |
245 | /**
246 | * Log a message
247 | * @param string $type
248 | * @param string $message
249 | * @return void
250 | */
251 | public function log($message, $type = 'debug')
252 | {
253 | if ($this->app['config']->get('modules::debug'))
254 | {
255 | $namespace = 'MODULES';
256 | $message = "[$namespace] $message";
257 |
258 | if ($type == 'error') $this->app['log']->error($message);
259 | else $this->app['log']->debug($message);
260 | }
261 | }
262 |
263 | /**
264 | * Prettify a JSON Encode ( PHP 5.4+ )
265 | * @param mixed $values
266 | * @return string
267 | */
268 | public function prettyJsonEncode($values)
269 | {
270 | return version_compare(PHP_VERSION, '5.4.0', '>=') ? json_encode($values, JSON_PRETTY_PRINT) : json_encode($values);
271 | }
272 |
273 | }
--------------------------------------------------------------------------------
/src/Module.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Module extends \Illuminate\Support\ServiceProvider {
11 |
12 | /**
13 | * Name of the module
14 | * @var string
15 | */
16 | protected $name;
17 |
18 | /**
19 | * Path to module directory
20 | * @var string
21 | */
22 | protected $path;
23 |
24 | /**
25 | * Path to module definition JSON file
26 | * @var string
27 | */
28 | protected $definitionPath;
29 |
30 | /**
31 | * Module definition
32 | * @var array
33 | */
34 | protected $definition;
35 |
36 | /**
37 | * Is the module enabled
38 | * @var boolean
39 | */
40 | protected $enabled = true;
41 |
42 | /**
43 | * Order to register the module
44 | * @var integer
45 | */
46 | public $order = 0;
47 |
48 | /**
49 | * IoC
50 | * @var Illuminate\Foundation\Application
51 | */
52 | protected $app;
53 |
54 | /**
55 | * Path for module group
56 | * @var string
57 | */
58 | public $group;
59 |
60 | /**
61 | * Initialize a module
62 | * @param Application $app
63 | */
64 | public function __construct($name, $path = null, $definition = null, Application $app, $group = null)
65 | {
66 | $this->name = $name;
67 | $this->app = $app;
68 | $this->path = $path;
69 | $this->group = $group;
70 |
71 | // Get definition
72 | if ($path and ! $definition)
73 | {
74 | $this->definitionPath = $path . '/module.json';
75 | }
76 | elseif (is_array($definition))
77 | {
78 | $this->definition = $definition;
79 | }
80 |
81 | // Try to get the definition
82 | $this->readDefinition();
83 | }
84 |
85 | /**
86 | * Read the module definition
87 | * @return array
88 | */
89 | public function readDefinition()
90 | {
91 | // Read mode from configuration
92 | $mode = $this->app['config']->get('modules::mode');
93 |
94 | if ($mode == 'auto' or ($mode == 'manifest' and ! $this->app['modules']->manifest()))
95 | {
96 | if ($this->definitionPath)
97 | {
98 | $this->definition = @json_decode($this->app['files']->get($this->definitionPath), true);
99 |
100 | if ( ! $this->definition or (isset($this->definition['enabled']) and $this->definition['enabled'] === false))
101 | {
102 | $this->enabled = false;
103 | }
104 | }
105 | else
106 | {
107 | $this->enabled = false;
108 | }
109 | }
110 | else
111 | {
112 | if ((isset($this->definition['enabled']) and $this->definition['enabled'] === false))
113 | {
114 | $this->enabled = false;
115 | }
116 | }
117 |
118 | // Add name to defintion
119 | if ( ! isset($this->definition['name'])) $this->definition['name'] = $this->name;
120 |
121 | // Assign order number
122 | if ( ! isset($this->definition['order'])) $this->definition['order'] = $this->order = 0;
123 | else $this->definition['order'] = $this->order = (int) $this->definition['order'];
124 |
125 | // Add group to definition
126 | $this->definition['group'] = $this->group;
127 |
128 | return $this->definition;
129 | }
130 |
131 | /**
132 | * Register the module if enabled
133 | * @return boolean
134 | */
135 | public function register()
136 | {
137 | if ($this->enabled)
138 | {
139 | // Register module as a package
140 | $this->package('modules/' . $this->name, $this->name, $this->path());
141 |
142 | // Register service provider
143 | $this->registerProviders();
144 |
145 | // Get files for inclusion
146 | $moduleInclude = (array) array_get($this->definition, 'include');
147 | $globalInclude = $this->app['config']->get('modules::include');
148 | $include = array_merge($globalInclude, $moduleInclude);
149 |
150 | // Include all of them if they exist
151 | foreach ($include as $file)
152 | {
153 | $path = $this->path($file);
154 | if ($this->app['files']->exists($path)) require $path;
155 | }
156 |
157 | // Register alias(es) into artisan
158 | if(!is_null($this->def('alias'))) {
159 | $aliases = $this->def('alias');
160 |
161 | if(!is_array($aliases))
162 | $aliases = array($aliases);
163 |
164 | foreach($aliases as $alias => $facade) {
165 | AliasLoader::getInstance()->alias($alias, $facade);
166 | }
167 | }
168 |
169 | // Register command(s) into artisan
170 | if(!is_null($this->def('command'))) {
171 | $commands = $this->def('command');
172 |
173 | if(!is_array($commands))
174 | $commands = array($commands);
175 |
176 | $this->commands($commands);
177 | }
178 |
179 | // Log it
180 | $this->app['modules']->logDebug('Module "' . $this->name . '" has been registered.');
181 | }
182 | }
183 |
184 | /**
185 | * Register service provider for module
186 | * @return void
187 | */
188 | public function registerProviders()
189 | {
190 | $providers = $this->def('provider');
191 |
192 | if ($providers)
193 | {
194 | if (is_array($providers))
195 | {
196 | foreach ($providers as $provider)
197 | {
198 | $this->app->register($instance = new $provider($this->app));
199 | }
200 | }
201 | else
202 | {
203 | $this->app->register($instance = new $providers($this->app));
204 | }
205 | }
206 | }
207 |
208 | /**
209 | * Run the seeder if it exists
210 | * @return void
211 | */
212 | public function seed()
213 | {
214 | $class = $this->def('seeder');
215 |
216 | if (class_exists($class))
217 | {
218 | $seeder = new $class;
219 | $seeder->run();
220 | }
221 | }
222 |
223 | /**
224 | * Return name of module
225 | * @return string
226 | */
227 | public function name()
228 | {
229 | return $this->name;
230 | }
231 |
232 | /**
233 | * Module path
234 | * @param string $path
235 | * @return string
236 | */
237 | public function path($path = null)
238 | {
239 | if ($path) return $this->path . '/' . ltrim($path, '/');
240 | else return $this->path;
241 | }
242 |
243 | /**
244 | * Check if module is enabled
245 | * @return boolean
246 | */
247 | public function enabled()
248 | {
249 | return (bool) $this->enabled;
250 | }
251 |
252 | /**
253 | * Get definition value
254 | * @param string $key
255 | * @return mixed
256 | */
257 | public function def($key = null)
258 | {
259 | if ( ! isset($this->definition['enabled'])) $this->definition['enabled'] = $this->enabled;
260 |
261 | if ($key) return isset($this->definition[$key]) ? $this->definition[$key] : null;
262 | else return $this->definition;
263 | }
264 |
265 | }
266 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Abandoned
2 |
3 | **This package is no longer maintained.**
4 |
5 | I encourage you to use composer packages for your modules to get the most flexibility out of it.
6 |
7 | # Modules in Laravel 4
8 |
9 | Application specific modules in Laravel 4 can be enabled by adding the following to your **"composer.json"** file:
10 |
11 | "require": {
12 | "creolab/laravel-modules": "dev-master"
13 | }
14 |
15 | And by adding a new provider to your providers list in **"app/config/app.php"**:
16 |
17 | 'providers' => array(
18 | 'Creolab\LaravelModules\ServiceProvider',
19 | );
20 |
21 | Also you need to add your modules directory to the composer autoloader:
22 |
23 | "autoload": {
24 | "classmap": [
25 | "app/modules"
26 | ]
27 | }
28 |
29 | This also means you need to run **"composer dump-autoload"** every time you add a new class to your module.
30 |
31 | By default you can add a **"modules"** directory in your **"app"** directory. So as an example this is a structure for one of my projects:
32 |
33 | app/
34 | |-- modules
35 | |-- auth
36 | |-- controllers
37 | |-- models
38 | |-- views
39 | |-- module.json
40 | |-- content
41 | |-- controllers
42 | |-- models
43 | |-- views
44 | |-- module.json
45 | |-- shop
46 | |-- module.json
47 | |-- system
48 | |-- module.json
49 |
50 | Note that every module has a **"module.json"** file, in which you can enable/disable the module. I plan on adding more meta data to these module definitions, but I need feedback as to what to put in there.
51 | The first thing will probably be some kind of a bootstrap class.
52 |
53 | If you want to have your modules in more that 1 directories you need to change the packages config file as following:
54 |
55 | 'path' => array(
56 | 'app/modules',
57 | 'public/site',
58 | 'another/folder/containing/modules',
59 | ),
60 |
61 | And don't forget to add those directories to your autoload list inside the composer.json file.
62 |
63 | One of the available option is the order in which the modules are loaded. This can be done simply by adding the following to your module.json file:
64 |
65 | "order": 5
66 |
67 | The order defaults to 0, so keep that in mind if you don't define it.
68 |
69 | For now take a look at the example implementation, and please provide feedback ;)
70 |
71 | [https://github.com/bstrahija/laravel-modules-example](https://github.com/bstrahija/laravel-modules-example)
72 |
73 | # Commands
74 |
75 | There are 2 commands available through this package:
76 |
77 | php artisan modules
78 |
79 | Which simply diplays all current modules depending on the mode set in the configuration. And:
80 |
81 | php artisan modules:scan
82 |
83 | Which is only required if you have your modules setup in the **"manifest"** mode (see below).
84 | This command scans the modules exactly like in the **"auto"** mode, but caches the results into a manifest file.
85 |
86 | # Optimization
87 |
88 | By default the package scans the **"modules"** directory for **"module.json"** files. This is not the best solution way to discover modules, and I do plan to implement some kind of a caching to the Finder class.
89 | To optimize the modules Finder even more you can publish the package configuration, and add the modules and definitions directly inside the configuration file by running:
90 |
91 | php artisan config:publish creolab/laravel-modules
92 |
93 | And the editing the file **"app/config/packages/creolab/laravel-modules/config.php"**.
94 | You just need to change the **"mode"** parameter from **"auto"** to **"manual"**, and list your modules under the **"modules"** key. An example of that is already provided inside the configuration.
95 |
96 | **Note for Manual mode with multiple paths** : Laravel-Modules could not determine witch path to use. So please specify the folder containing the module you want to load. Like this example :
97 |
98 | 'modules' => [
99 | 'app/modules' => [
100 | 'system' => ['enabled' => true],
101 | ],
102 | 'another/modules/path' => [
103 | 'pages' => ['enabled' => false],
104 | 'seo' => ['enabled' => true],
105 | ],
106 | ],
107 |
108 | You can also add multiple module paths as an array, but do note that if a module has the same name, there will be problems.
109 |
110 | ## Including files
111 |
112 | You can also specify which files in the module will be automatically included. Simply add a list of files inside your **module.json** config:
113 |
114 | {
115 | "include": [
116 | "breadcrumbs.php"
117 | ]
118 | }
119 |
120 | There are some defaults set on which files will be included if they exist. Take a look at the latest config file, and republish the configuration if needed. By default these files will be included:
121 |
122 | 'include' => array(
123 | 'helpers.php',
124 | 'filters.php',
125 | 'composers.php',
126 | 'routes.php',
127 | 'bindings.php',
128 | 'observers.php',
129 | )
130 |
131 | So you have the choice to either add your custom files to the global configuration, which will look for these files in every module, or you can set it up on a per module basis by adding it to the **module.json** file.
132 |
133 | ## Service providers
134 |
135 | A new addition is registering service providers for each module. Just add a line to your **module.json** file that looks something like this:
136 |
137 | "provider": "App\\Modules\\Content\\ServiceProvider"
138 |
139 | These service provider classes work exactly like any other service provider added to your **app/config/app.php** configuration, so setup these classes by extending the **\Illuminate\Support\ServiceProvider** class and adding the appropriate methods.
140 |
141 | You can also register multiple providers for every module by simply providing an array:
142 |
143 | "provider": [
144 | "App\\Modules\\Content\\ServiceProvider",
145 | "App\\Modules\\Content\\AnotherServiceProvider"
146 | ]
147 |
148 | Keep in mind that you may have to run **composer dump-autoload** so you want get error on missing classes.
149 |
150 | ## Modules Manifest
151 |
152 | Another possible mode is **"manifest"** which basically writes a JSON manifest file in your Laravel storage directory that contains all the modules definitions.
153 | This is only done the first time and to update the manifest file you need to either delete it, or rescan the modules via the following command:
154 |
155 | php artisan modules:scan
156 |
157 | # Assets
158 |
159 | Just recently the ability to publish public assets for each module has been added. Just run:
160 |
161 | php artisan modules:publish
162 |
163 | And all modules that contain an **"assets"** directory will be published to the Laravel public directory.
164 | You can also publish assets for individual modules by providing the module name:
165 |
166 | php artisan modules:publish content
167 |
168 | # Migrations
169 |
170 | Every module can have it's own migrations, and they need to be in the module/migrations directory. So for example if you want to create a migration that creates a user table for the auth module:
171 |
172 | php artisan migrate:make create_users_table --path=app/modules/auth/migrations --table=users --create
173 |
174 | And to run all module migrations do this:
175 |
176 | php artisan modules:migrate
177 |
178 | Or to run migrations for a specific module:
179 |
180 | php artisan modules:migrate auth
181 |
182 | You can also seed the database form the module if your **module.json** contains a seeder setting. Just pass the **--seed** option to the command:
183 |
184 | php artisan modules:migrate --seed
185 |
186 | # Seeding
187 |
188 | The modules can also have seeders. Just create the class like you would create a normal seeder, place it somewhere inside your module, and be sure to run **composer dump-autoload**. Then add the following to your **module.json** file:
189 |
190 | "seeder": "App\\Modules\\Content\\Seeds\\DatabaseSeeder"
191 |
192 | This setting should contain the namespace path to your seeder class. Now simply run:
193 |
194 | php artisan modules:seed
195 |
196 | To seed all your modules. Or you can do it for a specific module:
197 |
198 | php artisan modules:seed content
199 |
200 | # Commands
201 |
202 | You can add module specific commands. This is a sample artisan command file creation :
203 |
204 | php artisan command:make --path=app/modules//commands --namespace=App\Modules\\Commands --command=modules.mymodule:mycommand
205 |
206 | Then in the **module.json** add (you can also add an array if you have multiple commands) :
207 |
208 | "command": "App\\Modules\\\\Commands\\MyModuleCommandName"
209 |
210 | After a dump-autoload you can now execute **modules.mymodule:mycommand** from command line :
211 |
212 | php artisan modules.mymodule:mycommand
213 |
214 | # Aliases
215 |
216 | If you declare Facades into your modules you will like to create Aliases for your module, you can simply reference your alias in the `module.json` :
217 |
218 | "alias": {
219 | "" "App\\Modules\\\\Facades\\"
220 | }
221 |
222 | # License
223 |
224 | Thi package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
225 |
--------------------------------------------------------------------------------