Skip to content

Commit

Permalink
Merge pull request #64 from visto9259/master
Browse files Browse the repository at this point in the history
Updated the "Getting Started with Laminas" section
  • Loading branch information
froschdesign committed Mar 21, 2024
2 parents 97e7320 + 0ae3ae5 commit 90f925d
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .gitignore
@@ -1,4 +1,4 @@
/composer.lock
/doc/html/
/docs/html/
/laminas-mkdoc-theme.tgz
/laminas-mkdoc-theme/
167 changes: 77 additions & 90 deletions docs/book/getting-started/database-and-models.md
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,74 +199,91 @@ 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 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-6,13-30"><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' => [
// ...
],

'router' => [
// ..
],
'view_manager' => [
// ...
],
'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\ConfigProviderInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;

class Module implements ConfigProviderInterface
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 @@ -360,64 +377,34 @@ class AlbumController extends AbstractActionController
</code></pre>
<!-- 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()`:

<!-- markdownlint-disable MD033 -->
<pre class="language-php" data-line="12-24"><code>
namespace Album;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface
{
// getConfig() and getServiceConfig() methods are here
Our controller now depends on `AlbumTable`, so we will need to update the factory
for the controller so that it will inject the `AlbumTable`.

// Add this method:
public function getControllerConfig()
{
return [
'factories' => [
Controller\AlbumController::class => function($container) {
return new Controller\AlbumController(
$container->get(Model\AlbumTable::class)
);
},
],
];
}
}
</code></pre>
<!-- markdownlint-enable MD033 -->
We will use the `ReflectionBasedAbstractFactory` factory to build the `AlbumController`.
`ReflectionBasedAbstractFactory` provides a reflection-based approach to instantiation, resolving constructor dependencies to the relevant services. Since the `AlbumController` constructor has an `AlbumTable` parameter, the factory will instantiate an `AlbumTable` instance and pass it to the `AlbumController`constructor.

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 `ReflectionBasedAbstractFactory`:

<!-- 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 Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;
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 => ReflectionBasedAbstractFactory::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
15 changes: 9 additions & 6 deletions docs/book/getting-started/forms-and-actions.md
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 Expand Up @@ -340,8 +343,8 @@ element, and error view helpers for each element, but you still have to wrap
complexity of your view script in situations where the default HTML rendering of
the form is acceptable.

You should now be able to use the "Add new album" link on the home page of the
application to add a new album record, resulting in something like the
You should now be able to use the "Add new album" page of the
application at `http://localhost:8080/album/add` to add a new album record, resulting in something like the
following:

![Add Album Form](../images/user-guide.forms-and-actions.album-form-add-original.png)
Expand Down
10 changes: 6 additions & 4 deletions docs/book/getting-started/routing-and-controllers.md
Expand Up @@ -151,10 +151,12 @@ until we set up the views. The URLs for each action are:

URL | Method called
---------------------------------------------------- | -------------
`http://laminas-mvc-tutorial.localhost/album` | `Album\Controller\AlbumController::indexAction`
`http://laminas-mvc-tutorial.localhost/album/add` | `Album\Controller\AlbumController::addAction`
`http://laminas-mvc-tutorial.localhost/album/edit` | `Album\Controller\AlbumController::editAction`
`http://laminas-mvc-tutorial.localhost/album/delete` | `Album\Controller\AlbumController::deleteAction`
`http://localhost:8080/album` | `Album\Controller\AlbumController::indexAction`
`http://localhost:8080/album/add` | `Album\Controller\AlbumController::addAction`
`http://localhost:8080/album/edit` | `Album\Controller\AlbumController::editAction`
`http://localhost:8080/album/delete` | `Album\Controller\AlbumController::deleteAction`

NOTE: If you are using self-hosted Apache, replace `http://localhost:8080/` by `http://laminas-mvc-tutorial.localhost/`

We now have a working router and the actions are set up for each page of our
application.
Expand Down
7 changes: 3 additions & 4 deletions docs/book/getting-started/skeleton-application.md
Expand Up @@ -61,7 +61,7 @@ We *will* be using laminas-db extensively in this tutorial, so hit "y" followed
"Enter". You should see the following text appear:

```text
Will install laminas/laminas-db (^2.8.1)
Will install laminas/laminas-db (^2.17.0)
When prompted to install as a module, select application.config.php or modules.config.php
```

Expand All @@ -79,12 +79,11 @@ At this point, we can answer "n" to the remaining features:
```text
Would you like to install JSON de/serialization support? y/N
Would you like to install logging support? y/N
Would you like to install MVC-based console support? (We recommend migrating to symfony/console, or Aura.CLI) y/N
Would you like to install command-line interface support? y/N
Would you like to install i18n support? y/N
Would you like to install the official MVC plugins, including PRG support, identity, and flash messages? y/N
Would you like to use the PSR-7 middleware dispatcher? y/N
Would you like to install sessions support? y/N
Would you like to install MVC testing support? y/N
Would you like to install the laminas-di integration for laminas-servicemanager? y/N
```

Expand All @@ -101,7 +100,7 @@ Updating application configuration...
Please select which config file you wish to inject 'Laminas\Db' into:
[0] Do not inject
[1] config/modules.config.php
Make your selection (default is 0):
Make your selection (default is 1):
```

We want to enable the various selections we made in the application. As such,
Expand Down
Binary file modified docs/book/images/user-guide.database-and-models.album-list.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/book/images/user-guide.forms-and-actions.add-album-form.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/book/images/user-guide.skeleton-application.404.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 90f925d

Please sign in to comment.