Backend modules
To illustrate how modules work, we're going to create a VideoChat module that makes use of dependency injection providers.
Our completed module will look something like this: Click to enlarge
Module building blocks
The Model
Core/VideoChat/VideoChat.php
- The characteristics that define a single unit of whatever content your module is going to handle (e.g. a single video chat might include an array of participantGuids, startTimestamp, endTimestamp, etc.
- You'll want to
use Minds\Traits\MagicAttributes
so you can automatically useget
,set
, andis
methods on each of your model's properties - If you'll be using an api endpoint to interact with the frontend, include a
public function export() {...}
here so it can be picked up by your controller'sFactory::exportable()
function, which transforms arrays into an exportable format - May include functions for calculated fields, e.g.
getUrn()
The Manager
Core/VideoChat/Manager.php
- Interfaces with the repository
- Hydrate the entities returned in the response here (if needed)
- Functions might include
get()
,getList()
,add()
,update()
,delete()
- A given function might query the repository, hydrate entity guids that are returned in the response, and call delegates
<?php
namespace Minds\Core\VideoChat;
class Manager
{
/** @var Repository $repository */
private $repository;
/** @var NotificationDelegate $notificationDelegate */
private $notificationDelegate;
public function __construct($repository = null, $notificationDelegate = null)
{
$this->repository = $repository ?: Di::_('Repository');
$this->notificationDelegate = $notificationDelegate ?: new Delegates/NotificationDelegate;
}
/**
* Add a model
* @param Model $model
* @return void
*/
public function add(Model $model)
{
$this->repository->add($model);
$this->notificationDelegate->onAdd($model);
}
Note how we are able to switch out repositories here without impacting the wider codebase by interacting with our Manager. During migrations, a double writing pattern could be added with a few lines.
The Repository
Core/VideoChat/Repository.php
- Interfaces with the database.
- Should only ever be referenced by its Manager. This single point of contact is essential because it makes it easy to replace legacy databases with new ones that are more suitable for our needs
- Should
use Minds\Common\Repository\Response
- Functions might include
get()
,getList()
,add()
,update()
,delete()
<?php
class Repository
{
/** @var Client $db */
private $db
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
}
public function add(Model $model)
{
...
}
...
The Provider
Core/VideoChat/Provider.php
Defines a function that registers your Manager to make it available for dependency injection.
<?php
namespace Minds\Core\VideoChat;
use Minds\Core\Di\Provider as DiProvider;
class Provider extends DiProvider
{
public function register()
{
$this->di->bind('VideoChat\Manager', function ($di) {
return new Manager();
}, ['useFactory' => true]);
}
}
The Module
Core/VideoChat/Module.php
Creates an instance of your Provider and calls its register()
function.
<?php
namespace Minds\Core\VideoChat;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
public function onInit()
{
$provider = new Provider();
$provider->register();
}
}
Modules must be registered in Core/Minds.php:
private $modules = [
...
VideoChat\Module::class,
];
Delegates
Core/VideoChat/Delegates/NotificationDelegate.php
- Used to keep the Manager clean
- Should be used to execute small, stateless outbound functions that don't return values that need further processing. (If this doesn't happen in your module, you don't need to use delegates)
- You can use any name you wish, we are using NotificationDelegate as an example
<?php
class NotificationDelegate
{
public function onAdd(Model $model)
{
...
}
...
Tests
Managers, Repositories and Delegates should have 100% spec test coverage.
Make sure you include @package Minds\Core\VideoChat
in VideoChat.php
so it can be picked up by phpspec in VideoChatSpec.php
.