In-Depth Tutorial
In This Article
Introducing the Blog Module
Now that we know about the basics of the zend-mvc skeleton application,let's continue and create our very own module. We will create a module named"Blog". This module will display a list of database entries that represent asingle blog post. Each post will have three properties:id,text, andtitle. We will create forms to enter new posts into our database and to editexisting posts. Furthermore we will do so by using best-practices throughout thewhole tutorial.
Writing a new Module
Let's start by creating a new folder under the/module directory calledBlog, with the following stucture:
module/ Blog/ config/ src/ view/To be recognized as a module by theModuleManager,we need to do three things:
- Tell Composer how to autoload classes from our new module.
- Create a
Moduleclass in theBlognamespace. - Notify the application of the new module.
Let's tell Composer about our new module. Open thecomposer.json file in theproject root, and edit theautoload section to add a new PSR-4 entry for theBlog module; when you're done, it should read:
"autoload": { "psr-4": { "Application\\": "module/Application/src/", "Album\\": "module/Album/src/", "Blog\\": "module/Blog/src/" }}Once you're done, tell Composer to update its autoloading definitions:
$ composer dump-autoloadNext, we will create aModule class under theBlog namespace. Create thefilemodule/Blog/src/Module.php with the following contents:
<?phpnamespace Blog;class Module{}We now have a module that can be detected by theModuleManager.Let's add this module to our application. Although our module doesn't doanything yet, just having theModule.php class allows it to be loaded by theModuleManager. To do this, add an entry forBlog to the modules array insideconfig/modules.config.php:
<?php// In config/modules.config.php:return [ /* ... */ 'Application', 'Album', 'Blog',];If you refresh your application you should see no change at all (but also noerrors).
At this point it's worth taking a step back to discuss what modules are for. Inshort, a module is an encapsulated set of features for your application. Amodule might add features to the application that you can see, like ourBlogmodule; or it might provide background functionality for other modules in theapplication to use, such as interacting with a third party API.
Organizing your code into modules makes it easier for you to reuse functionalityin other applications, or to use modules written by the community.
Configuring the Module
The next thing we're going to do is add a route to our application so that ourmodule can be accessed through the URLlocalhost:8080/blog. We do this byadding router configuration to our module, but first we need to let theModuleManager know that our module has configuration that it needs to load.
This is done by adding agetConfig() method to theModule class thatreturns the configuration. (This method is defined in theConfigProviderInterface, although explicitly implementing this interface in themodule class is optional.) This method should return either anarray or aTraversable object. Continue by editingmodule/Blog/src/Module.php:
// In /module/Blog/Module.php:class Module{ public function getConfig() { return []; }}With this, our module is now able to be configured. Configuration files canbecome quite big, though, and keeping everything inside thegetConfig() methodwon't be optimal. To help keep our project organized, we're going to put ourarray configuration in a separate file. Go ahead and create this file atmodule/Blog/config/module.config.php:
<?phpreturn [];Now rewrite thegetConfig() function to include this newly createdfile instead of directly returning the array:
<?php// In /module/Blog/Module.php:public function getConfig(){ return include __DIR__ . '/../config/module.config.php';}Reload your application and you'll see that nothing changes. Creating,registering, and adding empty configuration for a new module has no visibleeffect on the application. Next we add the new route to our configuration file:
// In /module/Blog/config/module.config.php:namespace Blog;use Zend\Router\Http\Literal;return [ // This lines opens the configuration for the RouteManager 'router' => [ // Open configuration for all possible routes 'routes' => [ // Define a new route called "blog" 'blog' => [ // Define a "literal" route type: 'type' => Literal::class, // Configure the route itself 'options' => [ // Listen to "/blog" as uri: 'route' => '/blog', // Define default controller and action to be called when // this route is matched 'defaults' => [ 'controller' => Controller\ListController::class, 'action' => 'index', ], ], ], ], ],];We've now created a route calledblog that listens to the URLlocalhost:8080/blog. Whenever someone accesses this route, theindexAction()function of the classBlog\Controller\ListController will be executed.However, this controller does not exist yet, so if you reload the page you willsee this error message:
A 404 error occurredPage not found.The requested controller could not be mapped by routing.Controller:Blog\Controller\ListController(resolves to invalid controller class or alias: Blog\Controller\ListController)We now need to tell our module where to find this controller namedBlog\Controller\ListController. To achieve this we have to add this key to thecontrollers configuration key inside yourmodule/Blog/config/module.config.php.
namespace Blog;use Zend\ServiceManager\Factory\InvokableFactory;return [ 'controllers' => [ 'factories' => [ Controller\ListController::class => InvokableFactory::class, ], ], /* ... */];This configuration defines a factory for the controller classBlog\Controller\ListController, using the zend-servicemanagerInvokableFactory (which, internally, instantiates the class with noarguments). Reloading the page should then give you:
Fatal error: Class 'Blog\Controller\ListController' not found in {projectPath}/vendor/zendframework/zend-servicemanager/src/Factory/InvokableFactory.php on line 32This error tells us that the application knows what class to load, but was notable to autoload it. In our case, we've already setup autoloading, but have notyet defined the controller class!
Create the filemodule/Blog/src/Controller/ListController.php with thefollowing contents:
<?phpnamespace Blog\Controller;class ListController{}Reloading the page now will finally result into a new screen. The new error message looks like this:
A 404 error occurredPage not found.The requested controller was not dispatchable.Controller:Blog\Controller\List(resolves to invalid controller class or alias: Blog\Controller\List)Additional information:Zend\ServiceManager\Exception\InvalidServiceExceptionFile:{projectPath}/vendor/zendframework/zend-mvc/src/Controller/ControllerManager.php:{lineNumber}Message:Plugin of type "Blog\Controller\ListController" is invalid; must implement Zend\Stdlib\DispatchableInterfaceThis happens because our controller must implementDispatchableInterfacein order to be 'dispatched' (or run) by zend-mvc. zend-mvc provides a basecontroller implementation of it withAbstractActionController,which we are going to use. Let's modify our controller now:
// In /module/Blog/src/Blog/Controller/ListController.php:namespace Blog\Controller;use Zend\Mvc\Controller\AbstractActionController;class ListController extends AbstractActionController{}It's now time for another refresh of the site. You should now see a new error message:
An error occurredAn error occurred during execution; please try again later.Additional information:Zend\View\Exception\RuntimeExceptionFile:{projectPath}/vendor/zendframework/zend-view/src/Renderer/PhpRenderer.php:{lineNumber}Message:Zend\View\Renderer\PhpRenderer::render: Unable to render template "blog/list/index"; resolver could not resolve to a fileNow the application tells you that a view template-file cannot be rendered,which is to be expected as we've not created it yet. The application isexpecting it to be atmodule/Blog/view/blog/list/index.phtml. Create thisfile and add some dummy content to it:
<!-- Filename: module/Blog/view/blog/list/index.phtml --><h1>Blog\Controller\ListController::indexAction()</h1>Before we continue let us quickly take a look at where we placed this file. Notethat view files are found within the/view subdirectory, not/src as theyare not PHP class files, but template files for rendering HTML. The path,however, deserves some explanation. First we have the lowercased namespaceblog,followed by the lowercased controller namelist (without the suffix 'controller'),and lastly comes the name of the action that we are accessing,index (again without thesuffix 'action'). As a templated string, you can think of it as:view/{namespace}/{controller}/{action}.phtml. This has become a communitystandard but you have the freedom to specify custom paths if desired.
However creating this file alone is not enough and this brings as to the finaltopic of this part of the tutorial. We need to let the application know whereto look for view files. We do this within our module's configuration file,module.config.php.
// In module/Blog/config/module.config.php:return [ 'controllers' => [ /** Controller Configuration */ ], 'router' => [ /** Route Configuration */ ] 'view_manager' => [ 'template_path_stack' => [ __DIR__ . '/../view', ], ],];The above configuration tells the application that the foldermodule/Blog/view/ has view files in it that match the standard path format:view/{namespace}/{controller}/{action}.phtml. It is important to note that theview_manager configuration not only allows you to ship view files for yourmodule, but also to overwrite view files from other modules.
Reload your site now. Finally we are at a point where we see something differentthan an error being displayed! You should see the standard ZF SkeletonApplication template page withBlog\Controller\ListController::indexAction() as theheader.
Congratulations, not only have you created a simple "Hello World" style module,you also learned about many error messages and their causes. If we didn'texhaust you too much, continue with our tutorial, and let's create a module thatactually does something.
Found a mistake or want to contribute to the documentation? Edit this page on GitHub!