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