Skip to content

Commit

Permalink
Simplified the factories. Used module config file. Replaced form elem…
Browse files Browse the repository at this point in the history
…ents types by classes

Signed-off-by: Eric Richer <[email protected]>
  • Loading branch information
visto9259 committed Mar 19, 2024
1 parent 81fd47f commit befad7a
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 91 deletions.
196 changes: 109 additions & 87 deletions docs/book/getting-started/database-and-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ class Album
public $artist;
public $title;

public function exchangeArray(array $data)
public function exchangeArray(array $array): void
{
$this->id = !empty($data['id']) ? $data['id'] : null;
$this->artist = !empty($data['artist']) ? $data['artist'] : null;
$this->title = !empty($data['title']) ? $data['title'] : null;
$this->id = ! empty($array['id']) ? $array['id'] : null;
$this->artist = ! empty($array['artist']) ? $array['artist'] : null;
$this->title = ! empty($array['title']) ? $array['title'] : null;
}
}
```
Expand Down Expand Up @@ -199,76 +199,111 @@ of these methods is, hopefully, self-explanatory.
## Using ServiceManager to configure the table gateway and inject into the AlbumTable

In order to always use the same instance of our `AlbumTable`, we will use the
`ServiceManager` to define how to create one. This is most easily done in the
`Module` class where we create a method called `getServiceConfig()` which is
automatically called by the `ModuleManager` and applied to the `ServiceManager`.
We'll then be able to retrieve when we need it.
`ServiceManager` to define how to create one.
This is most easily done by adding a `ServiceManager` configuration to the `module.config.php`
which is automatically loaded by the `ModuleManager` and applied to the `ServiceManager`.
We'll then be able to retrieve the `AlbumTable` when we need it.

To configure the `ServiceManager`, we can either supply the name of the class to
be instantiated or a factory (closure, callback, or class name of a factory
class) that instantiates the object when the `ServiceManager` needs it. We start
by implementing `getServiceConfig()` to provide a factory that creates an
`AlbumTable`. Add the `ServiceProviderInterface` to the class and add this method to the bottom of the `module/Album/src/Module.php`
file:
be instantiated and a factory (closure, callback, or class name of a factory
class) that instantiates the object when the `ServiceManager` needs it.

Add a `service_manager` configuration to `module/Album/config/module.config.php`:

<!-- markdownlint-disable MD033 -->
<pre class="language-php" data-line="3-7,10,15-32"><code>
<pre class="language-php" data-line="3,38-41"><code>
namespace Album;

// Add these import statements:
use Album\Model\AlbumTableFactory;
use Laminas\Router\Http\Segment;
use Laminas\ServiceManager\Factory\InvokableFactory;

return [
'controllers' => [
'factories' => [
Controller\AlbumController::class => InvokableFactory::class,
],
],

// The following section is new and should be added to your file:
'router' => [
'routes' => [
'album' => [
'type' => Segment::class,
'options' => [
'route' => '/album[/:action[/:id]]',
'constraints' => [
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
],
'defaults' => [
'controller' => Controller\AlbumController::class,
'action' => 'index',
],
],
],
],
],
'view_manager' => [
'template_path_stack' => [
'album' => __DIR__ . '/../view',
],
],
'service_manager' => [
'factories' => [
Model\AlbumTable::class => AlbumTableFactory::class,
],

],
];
</code></pre>
<!-- markdownlint-enable MD033 -->

This method returns an array of `factories` that are all merged together by the
`ModuleManager` before passing them to the `ServiceManager`. When requesting the `ServiceManager`
to create `Album\Model\AlbumTable`, the `ServiceManager` will invoke the `AlbumTableFactory` class, which we need to create next.

Let's create the `AlbumTableFactory.php` factory in `module/Album/src/Model`:

````php
namespace Album\Model;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Laminas\ModuleManager\Feature\ServiceProviderInterface;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;

// Add the ServiceProviderInterface
class Module implements ConfigProviderInterface, ServiceProviderInterface
class AlbumTableFactory implements FactoryInterface
{
// getConfig() method is here

// Add this method:
public function getServiceConfig()
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): AlbumTable
{
return [
'factories' => [
Model\AlbumTable::class => function($container) {
$tableGateway = $container->get(Model\AlbumTableGateway::class);
return new Model\AlbumTable($tableGateway);
},
Model\AlbumTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Album());
return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
},
],
];
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
$tableGateway = new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
return new AlbumTable($tableGateway);
}
}
</code></pre>
<!-- markdownlint-enable MD033 -->
````

This method returns an array of `factories` that are all merged together by the
`ModuleManager` before passing them to the `ServiceManager`. The factory for
`Album\Model\AlbumTable` uses the `ServiceManager` to create an
`Album\Model\AlbumTableGateway` service representing a `TableGateway` to pass to
its constructor. We also tell the `ServiceManager` that the `AlbumTableGateway`
service is created by fetching a `Laminas\Db\Adapter\AdapterInterface`
implementation (also from the `ServiceManager`) and using it to create a
The `AlbumTableFactory` factory uses the `ServiceManager` to fetch a `Laminas\Db\Adapter\AdapterInterface`
implementation (also from the `ServiceManager`) and use it to create a
`TableGateway` object. The `TableGateway` is told to use an `Album` object
whenever it creates a new result row. The `TableGateway` classes use the
prototype pattern for creation of result sets and entities. This means that
instead of instantiating when required, the system clones a previously
instantiated object. See
instantiated object. Then, finally, the factory creates a `AlbumTable` object passing it the `TableGateway` object.
See
[PHP Constructor Best Practices and the Prototype Pattern](https://dbglory.wordpress.com/2012/03/10/php-constructor-best-practices-and-the-prototype-pattern/)
for more details.

> ### Factories
>
> The above demonstrates building factories as closures within your module
> class. Another option is to build the factory as a *class*, and then map the
> class in your module configuration. This approach has a number of benefits:
> The above demonstrates building factories as a *class* and mapping the
> class factory in your module configuration. Another option would have been to use a closure that contains
> the same code a the `AlbumTableFactory`. Using a class for the factory has a number of benefits:
>
> - The code is not parsed or executed unless the factory is invoked.
> - You can easily unit test the factory to ensure it does what it should.
Expand Down Expand Up @@ -363,66 +398,53 @@ class AlbumController extends AbstractActionController
<!-- markdownlint-enable MD033 -->

Our controller now depends on `AlbumTable`, so we will need to create a factory
for the controller. Similar to how we created factories for the model, we'll
create it in our `Module` class, only this time, under a new method,
`Album\Module::getControllerConfig()`:
for the controller that will inject the `AlbumTable`.

<!-- markdownlint-disable MD033 -->
<pre class="language-php" data-line="6,10,15-27"><code>
namespace Album;
Let's create the `AlbumControllerFactory.php` in `module/Album/src/Controller`:

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Laminas\ModuleManager\Feature\ControllerProviderInterface;
use Laminas\ModuleManager\Feature\ServiceProviderInterface;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
````php
namespace Album\Controller;

use Album\Model\AlbumTable;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;

// Add the ControllerProviderInterface interface to the class
class Module implements ConfigProviderInterface, ServiceProviderInterface, ControllerProviderInterface
class AlbumControllerFactory implements FactoryInterface
{
// getConfig() and getServiceConfig() methods are here

// Add this method:
public function getControllerConfig()
/**
* @inheritDoc
*/
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): AlbumController
{
return [
'factories' => [
Controller\AlbumController::class => function($container) {
return new Controller\AlbumController(
$container->get(Model\AlbumTable::class)
);
},
],
];
return new AlbumController(
$container->get(AlbumTable::class)
);
}
}
</code></pre>
<!-- markdownlint-enable MD033 -->
````

Because we're now defining our own factory, we can modify our
`module.config.php` to remove the definition. Open
`module/Album/config/module.config.php` and remove the following lines:
Then we can modify the `controllers` section of the `module.config.php` to use the new factory:

<!-- markdownlint-disable MD033 -->
<pre class="language-php" data-line="3-4,7-12"><code>
<pre class="language-php" data-line="3,10"><code>
namespace Album;

// Remove this:
use Laminas\ServiceManager\Factory\InvokableFactory;
use Album\Controller\AlbumControllerFactory;
use Album\Model\AlbumTableFactory;
use Laminas\Router\Http\Segment;

return [
// And remove the entire "controllers" section here:
'controllers' => [
'factories' => [
Controller\AlbumController::class => InvokableFactory::class,
Controller\AlbumController::class => AlbumControllerFactory::class
],
],

/* ... */
// the rest of the code
];

</code></pre>
<!-- markdownlint-enable MD033 -->

We can now access the property `$table` from within our controller whenever we
need to interact with our model.
Expand Down
11 changes: 7 additions & 4 deletions docs/book/getting-started/forms-and-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ extending from `Laminas\Form\Form`. Create the file
```php
namespace Album\Form;

use Laminas\Form\Element\Hidden;
use Laminas\Form\Element\Submit;
use Laminas\Form\Element\Text;
use Laminas\Form\Form;

class AlbumForm extends Form
Expand All @@ -27,25 +30,25 @@ class AlbumForm extends Form

$this->add([
'name' => 'id',
'type' => 'hidden',
'type' => Hidden::class,
]);
$this->add([
'name' => 'title',
'type' => 'text',
'type' => Text::class,
'options' => [
'label' => 'Title',
],
]);
$this->add([
'name' => 'artist',
'type' => 'text',
'type' => Text::class,
'options' => [
'label' => 'Artist',
],
]);
$this->add([
'name' => 'submit',
'type' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' => 'Go',
'id' => 'submitbutton',
Expand Down

0 comments on commit befad7a

Please sign in to comment.