diff --git a/.gitignore b/.gitignore index ec20b0f..98f388c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /composer.lock -/doc/html/ +/docs/html/ /laminas-mkdoc-theme.tgz /laminas-mkdoc-theme/ diff --git a/docs/book/getting-started/database-and-models.md b/docs/book/getting-started/database-and-models.md index 373b103..e31141e 100644 --- a/docs/book/getting-started/database-and-models.md +++ b/docs/book/getting-started/database-and-models.md @@ -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; } } ``` @@ -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`: -
+
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,
+ ],
+
+ ],
+];
+
+
+
+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);
}
}
-
-
+````
-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.
@@ -360,64 +377,34 @@ class AlbumController extends AbstractActionController
-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()`:
-
-
-
-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)
- );
- },
- ],
- ];
- }
-}
-
-
+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`:
-
+
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
];
+
-
We can now access the property `$table` from within our controller whenever we
need to interact with our model.
diff --git a/docs/book/getting-started/forms-and-actions.md b/docs/book/getting-started/forms-and-actions.md
index 6c44c99..703a4a3 100644
--- a/docs/book/getting-started/forms-and-actions.md
+++ b/docs/book/getting-started/forms-and-actions.md
@@ -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
@@ -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',
@@ -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)
diff --git a/docs/book/getting-started/routing-and-controllers.md b/docs/book/getting-started/routing-and-controllers.md
index 4b90380..e09ffc9 100644
--- a/docs/book/getting-started/routing-and-controllers.md
+++ b/docs/book/getting-started/routing-and-controllers.md
@@ -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.
diff --git a/docs/book/getting-started/skeleton-application.md b/docs/book/getting-started/skeleton-application.md
index 8cd1427..ee8b378 100644
--- a/docs/book/getting-started/skeleton-application.md
+++ b/docs/book/getting-started/skeleton-application.md
@@ -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
```
@@ -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
```
@@ -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,
diff --git a/docs/book/images/user-guide.database-and-models.album-list.png b/docs/book/images/user-guide.database-and-models.album-list.png
index 73d03a1..e420535 100644
Binary files a/docs/book/images/user-guide.database-and-models.album-list.png and b/docs/book/images/user-guide.database-and-models.album-list.png differ
diff --git a/docs/book/images/user-guide.forms-and-actions.add-album-form.png b/docs/book/images/user-guide.forms-and-actions.add-album-form.png
index 0b09518..aa91ac2 100644
Binary files a/docs/book/images/user-guide.forms-and-actions.add-album-form.png and b/docs/book/images/user-guide.forms-and-actions.add-album-form.png differ
diff --git a/docs/book/images/user-guide.forms-and-actions.album-form-add-original.png b/docs/book/images/user-guide.forms-and-actions.album-form-add-original.png
index d951982..b7dd803 100644
Binary files a/docs/book/images/user-guide.forms-and-actions.album-form-add-original.png and b/docs/book/images/user-guide.forms-and-actions.album-form-add-original.png differ
diff --git a/docs/book/images/user-guide.skeleton-application.404.png b/docs/book/images/user-guide.skeleton-application.404.png
index 508efe1..8bcc429 100644
Binary files a/docs/book/images/user-guide.skeleton-application.404.png and b/docs/book/images/user-guide.skeleton-application.404.png differ
diff --git a/docs/book/images/user-guide.skeleton-application.hello-world.png b/docs/book/images/user-guide.skeleton-application.hello-world.png
index 01c13be..7606d2c 100644
Binary files a/docs/book/images/user-guide.skeleton-application.hello-world.png and b/docs/book/images/user-guide.skeleton-application.hello-world.png differ