icanboogie/module

Provides module support to ICanBoogie.

v3.0.0 2017-03-05 15:39 UTC

README

Release Build Status HHVM Code Quality Code Coverage Packagist

This package provides support for modules to the framework ICanBoogie.

A module is like a tiny application inside your application. It provides its own configurations, routes, operations, views, templates… it contains everything it needs to execute a desired functionality.

Defining a module

It takes at least two files to define a module: a descriptor and another where the class used to instantiate the module is defined. The descriptor is a descriptor.php file that must be located at the root of the module directory. The class is usually located in the file lib/module.php. The module directory is usually named with the identifier of the module.

The following directory structure demonstrates how a very basic nodes module could be defined:

nodes
├─ lib
│  └─ Module.php
└─ descriptor.php

The following directory structure demonstrates a more advanced module:

nodes
├─ config
|  └─ <configuration files>
├─ lib
|  ├─ Operation
|  |  └─ <operation classes>
|  └─ Module.php
├─ locale
|  └─ <message catalogs>
├─ public
|  └─ <public assets>
├─ tests
|  └─ <tests>
├─ templates
|  └─ <view templates>
└─ descriptor.php

The descriptor

The descriptor is a very important file because it describes the module and its models. At the very least the descriptor must define the title and the namespace of the module (yes namespace is mandatory).

The following code is the descriptor of the Nodes module. You'll notice that the module is using the namespace Icybee\Modules\Nodes.

<?php

namespace Icybee\Modules\Nodes;

use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\Module\Descriptor;

return [

	Descriptor::CATEGORY => 'contents',
	Descriptor::DESCRIPTION => 'Centralized node system base',
	Descriptor::MODELS => [

		'primary' => [

			Model::SCHEMA => [

				'nid' => 'serial',
				'uid' => 'foreign',
				'site_id' => 'foreign',
				'native_id' => 'foreign',
				'constructor' => [ 'varchar', 64, 'indexed' => true ],
				'title' => 'varchar',
				'slug' => [ 'varchar', 80, 'indexed' => true ],
				'language' => [ 'varchar', 8, 'indexed' => true ],
				'created' => [ 'timestamp', 'default' => 'CURRENT_TIMESTAMP()' ],
				'modified' => 'timestamp',
				'is_online' => [ 'boolean', 'indexed' => true ]

			]
		]
	],

	Descriptor::NS => __NAMESPACE__,
	Descriptor::PERMISSION => false,
	Descriptor::PERMISSIONS => [

		'modify belonging site'

	],

	Descriptor::REQUIRED => true,
	Descriptor::REQUIRES => [ 'sites', 'users' ],
	Descriptor::TITLE => 'Nodes'

];

Descriptor tags

Here are the tags (Descriptor::<tag>) that can be used to define the module's descriptor.

  • CATEGORY: Defines the category for the module.
  • CLASSNAME: Defines the PHP class of the module. Defaults to <namespace>\Module.
  • DESCRIPTION: Defines a short description of the module.
  • INHERITS: Defines the module that the module extends.
  • ID: Defines the identifier of the module. Defaults to its directory name.
  • REQUIRED: Defines that the module is required and cannot be disabled.
  • REQUIRES: Defines the modules required, used to compute modules weight.
  • MODELS: Defines the models of the module. Take a look at the ActiveRecord package for more information about ActiveRecords and models.
  • NS: Defines the namespace of the module.
  • PERMISSION: Defines the general permission required to use this module.
  • PERMISSIONS: Defines module specific permissions.
  • TITLE: Defines the title of the module.

Operations

Module operations are defined in the "lib/Operation" directory. For example a save operation would be a class named SaveOperation declared within the namespace <module namespace>\Operation, located in a "SaveOperation.php" file.

Operations are considered inherited. If the save operation is requested on a News module, the framework tries to locate the best matching operation class, according to the modules it extends:

  • ..\News\Operation\SaveOperation
  • ..\Contents\Operation\SaveOperation
  • ..\Nodes\Operation\SaveOperation

Module collection

Modules are accessed and managed through a module collection. The collection indexes modules and the resources they provide. It is used to enabled/disable and load modules. The collection also takes care of the dependencies between the modules and sorts them accordingly.

A module collection is represented by a ModuleCollection instance, constructed from an array of paths and an optional cache. The paths array defined where the modules are located, while the cache is used to store and retrieve the collection index.

The following example demonstrates how a module collection can be created from two separate module locations and the single location of a module, a cache is also provided:

<?php

use ICanBoogie\ModuleCollection;
use ICanBoogie\Storage\FileStorage;

$vars = new FileStorage(__DIR__ . '/repository/vars');

$modules = new ModuleCollection([

	__DIR__ . '/vendor/icanboogie-modules',
	__DIR__ . '/protected/modules',
	__DIR__ . '/path/to/my/module'

], $vars);

Accessing a module

Modules are accessed by using the collection as an array. The following example demonstrates how the nodes module is accessed.

<?php

$node_module = $modules['nodes'];

The ModuleNotDefined exception is thrown if the module is not defined. The ModuleIsDisabled exception is thrown if the module is disabled. Finally, the ModuleConstructorMissing is thrown when the class used to instantiate the module is missing.

Modules are considered accessible when their descriptor is registered and they are not disabled. isset() is used to check if a module is accessible:

<?php

isset($modules['nodes']); // true

$modules->disable('nodes');

isset($modules['nodes']); // false
isset($modules['undefined_module']); // false

Installing and uninstalling modules

Modules are installed using the install() method, and uninstalled using the uninstall() method. The is_installed() method returns the installation state of the module and also collects the reasons why the module is not installed.

<?php

use ICanBoogie\Errors;

$nodes = $modules['nodes'];
$errors = new Errors;

if (!$nodes->is_installed($errors))
{
	#
	# $errors might contain messages about why the module is not installed
	#

	$errors->clean();

	if (!$nodes->install($errors))
	{
		#
		# $errors might contain the reasons why the module failed to install
		#
	}
}

$nodes->uninstall();

Enabled modules can be installed at once using a ModuleCollection instance. The ModuleCollectionInstallFailed exception is thrown with all the errors and exceptions collected in a Errors instance if the installation fails.

<?php

use ICanBoogie\Module\ModuleCollectionInstallFailed;

try
{
	$modules->install();
}
catch (ModuleCollectionInstallFailed $e)
{
	echo get_class($e->errors); // ICanBoogie\Errors
}

Autoconfig

The package supports the autoconfig feature of ICanBoogie and provides the following:

  • A lazy getter for the ICanBoogie\Core::$modules property, that returns a ModuleCollection instance configured to provide the modules of the application.
  • A lazy getter for the ICanBoogie\Core::$models property, that returns a ModelCollection instance configured to provide the models defined by the modules.
<?php

$app->modules['nodes'];
$app->models['nodes'];

An enhanced model provider

The package provides an enhanced model provider, which extends the model provider that comes with the ActiveRecord package to add support for models defined by modules.

For instance, using this provider you can access the primary model defined by the Nodes module as follows:

<?php

$nodes_model = $app->models['nodes'];

And if the Nodes module was defining an attachments model:

<?php

$nodes_attachments_model = $app->models['nodes/attachments'];

Enhanced routing

The package adds the following getters to Controller instances:

  • module: Returns the Module instance of the module associated with a route.
  • model: Returns the primary model of module.
  • records_fetcher: Returns a Fetcher instance associated with model.

The following method is also added:

  • fetch_records: Fetches records matching specified conditions using fetcher.
  • fetch_record: Fetches a record matching specified conditions using fetcher.

The route needs to define a module property in order for these getters to work, which the package does automatically for routes defined in the "config" directory of modules.

The following example demonstrates how a module can define a route, remember that the module options is automatically added, so it doesn't need to be defined:

<?php

// modules/articles/config/routes.php

return [

	'articles/show' => [
	
		'pattern' => '/<year:\d{4}>-<month:\d{2}>-:slug.html',
		'controller' => "ArticlesController#show"
	
	]

];

The following example demonstrates how the fetch_records method can be used to fetch records matching some conditions, and how the model property can be used to obtain the primary model of the module associated with a route:

<?php

use ICanBoogie\Routing\ActionController;

class ArticlesController extends ActionController
{
	protected function any_index()
	{
		$this->view->content = $this->fetch_records([ 'limit' => 10 ] + $this->request->params);
	}

	protected function any_show($year, $month, $slug)
	{
		$this->view->content = $this->model->filter_by_year_and_month_and_slug($year, $month, $slug)->one;
	}
}

Note: records_fetcher and fetch_records require the [icanboogie\facets] package.

Template resolver decorator

A ModuleTemplateResolver instance is used to decorate the template resolver instance defined when the ICanBoogie\Render\BasicTemplateResolver::alter event of class TemplateResolver\AlterEvent is fired, adding support for module defined templates. When the path part of the template name matches an activated module identifier, the template pathname is resolved using the module and its parents.

Event hooks

  • ICanBoogie\Core::boot: Boot enabled modules. Before the modules are actually booted up, their index is used to alter the I18n load paths (if the icanboogie/i18n package is available) and the config paths. Note that prototypes are reset and the Events instance associated with the core revoked.

  • ICanBoogie\View\View::alter: If the view renders a module's route, the "template" directory of that module is added to the list of template locations.

  • routing.collect_routes:before: Alter routes defined by modules by adding a module key that holds the identifier of the module that defines the route.

Requirement

The package requires PHP 5.5 or later.

Installation

The recommended way to install this package is through Composer:

$ composer require icanboogie/module

Cloning the repository

The package is available on GitHub, its repository can be cloned with the following command line:

$ git clone https://github.com/ICanBoogie/Module.git

Documentation

The package is documented as part of the ICanBoogie framework documentation. You can generate the documentation for the package and its dependencies with the make doc command. The documentation is generated in the build/docs directory. ApiGen is required. The directory can later be cleaned with the make clean command.

Testing

The test suite is ran with the make test command. PHPUnit and Composer need to be globally available to run the suite. The command installs dependencies as required. The make test-coverage command runs test suite and also creates an HTML coverage report in "build/coverage". The directory can later be cleaned with the make clean command.

The package is continuously tested by Travis CI.

Build Status Code Coverage

License

icanboogie/module is licensed under the New BSD License - See the LICENSE file for details.