📜 ⬆️ ⬇️

An example of developing a blog on Zend Framework 2. Part 3. Working with users

This is the third (last?) Part of an article devoted to the development of a simple application using Zend Framework 2. In the first part I reviewed the structure of ZendSkeletonApplication, in the second part I gave an example of developing a simple module. This part is dedicated to working with users, and I will also bolt the Twig template engine to the project.

Work with users


The code written in the previous sections allows you to create, edit and delete blog posts for all site visitors. Such an approach is unacceptable for any working site, so now is the time to resolve the issues of registration / authorization and distribution of access rights to the various features of the application.

Zf commons


For Zend framework, quite a few modules are written that solve standard tasks, you can find them on a special website: modules.zendframework.com . Instead of developing my bikes for solving standard tasks, I consider it more correct to use / adapt ready-made solutions for myself (at least you need to study ready-made solutions before starting to develop a bicycle).
')
Among the many developers of modules, the ZF Commons team stands out, the guys from this team have developed a number of very useful modules that we will use in this project: github.com/ZF-Commons . Consider some of them that we need at this stage.

Zfcbase


The kernel on which other ZF Commons modules depend ( https://github.com/ZF-Commons/ZfcBase ).

ZfcUser


A module that implements user registration / authorization mechanisms, a user profile and View helpers for use in templates ( https://github.com/ZF-Commons/ZfcUser ).

ZfcUserDoctrineORM


By default, ZfcUser works with the standard framework for working with the database, since our project uses Doctrine ORM, we also need the ZfcUserDoctrineORM module ( https://github.com/ZF-Commons/ZfcUserDoctrineORM ).

ZfcTwig


Module for integration with Twig template engine ( https://github.com/ZF-Commons/ZfcTwig ).

BjyAuthorize


In addition to the modules from ZfCommons, I will use the BjyAuthorize module, which provides a convenient mechanism for distributing access rights. The logic of the module is simple and common among other frameworks. The module uses the following concepts: user, role and guard.

The user can be authorized and not authorized. An authorized user can have one or more roles. Guard in this context is the controller / action to which we configure access rights for different roles.

Preparing to set up users


Before setting up work with users, you must create an entity for the user and the roles that will be used by the Doctrine. Included with the BjyAuthorize module are examples of such entities, on the basis of which I created the MyUser module.

The module does not contain anything original, you can see its code here: github.com/romka/zend-blog-example/tree/master/module/MyUser , in its structure it does not differ from the above Application and MyBlog modules: it contains the config and 2 entity.

Attention should be paid only to its config ( https://github.com/romka/zend-blog-example/blob/master/module/MyUser/config/module.config.php ):
return array( 'doctrine' => array( 'driver' => array( 'zfcuser_entity' => array( 'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver', 'paths' => array(__DIR__ . '/../src/MyUser/Entity') ), 'orm_default' => array( 'drivers' => array( 'MyUser\Entity' => 'zfcuser_entity', ) ) ) ), 'zfcuser' => array( // telling ZfcUser to use our own class 'user_entity_class' => 'MyUser\Entity\User', // telling ZfcUserDoctrineORM to skip the entities it defines 'enable_default_entities' => false, ), 'bjyauthorize' => array( // Using the authentication identity provider, which basically reads the roles from the auth service's identity 'identity_provider' => 'BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider', 'role_providers' => array( // using an object repository (entity repository) to load all roles into our ACL 'BjyAuthorize\Provider\Role\ObjectRepositoryProvider' => array( 'object_manager' => 'doctrine.entity_manager.orm_default', 'role_entity_class' => 'MyUser\Entity\Role', ), ), ), ); 

In this config, we replace the zfcuser entity with our own, which is responsible for working with the user, and indicate to the BjyAuthorize module the entity responsible for working with roles.

The MyUser module needs to be added to application.config.php and then in the console execute the following commands:
 ./vendor/bin/doctrine-module orm:schema-tool:update --force ./vendor/bin/doctrine-module orm:validate-schema 

The first is to create tables for the entities created by the MyUser module in the database, the second is to make sure that the first command has worked correctly.

The final preparatory action will be to execute the query, which will create the appropriate roles:
 INSERT INTO `role` (`id`, `parent_id`, `roleId`) VALUES (1, NULL, 'guest'), (2, 1, 'user'), (3, 2, 'moderator'), (4, 3, 'administrator'); 


Configuring ZfcUser, ZfcUserDoctrineORM and BjyAuthorize


First of all, it is necessary to register new modules in the settings of Composer:
 "zf-commons/zfc-base": "v0.1.2", "zf-commons/zfc-user": "dev-master", "zf-commons/zfc-user-doctrine-orm": "dev-master", "doctrine/doctrine-orm-module": "0.7.*", "bjyoungblood/bjy-authorize": "1.4.*" 

update php composer.phar update and add new modules to application.config.php :
 'ZfcBase', 'ZfcUser', 'ZfcUserDoctrineORM', 'BjyAuthorize', 

Attention! The settings of some of these modules will be overridden by the settings of the handwritten modules, so these modules must be added to the top of the list.

Now you need to copy the file zfcuser.global.php.dist from the directory vendor / zf-commons / zfc-user / config in config / autoload and rename it to zfcuser.global.php . In this configuration file you need to set the value:
 'table_name' => 'users', 

as the default user table is used for working with users.

Even in the same directory, you need to create a bjyauth.global.php configuration file containing access rights settings for various roles. You can see the full version of this file on Github github.com/romka/zend-blog-example/blob/master/config/autoload/bjyauth.global.php , the most interesting part of it, which is responsible for the distribution of access rights to different controllers, below:
 'guards' => array( /* If this guard is specified here (ie it is enabled), it will block * access to all controllers and actions unless they are specified here. * You may omit the 'action' index to allow access to the entire controller */ 'BjyAuthorize\Guard\Controller' => array( array( 'controller' => 'zfcuser', 'action' => array('index', 'login', 'authenticate', 'register'), 'roles' => array('guest'), ), array( 'controller' => 'zfcuser', 'action' => array('logout'), 'roles' => array('user'), ), array('controller' => 'Application\Controller\Index', 'roles' => array()), array( 'controller' => 'MyBlog\Controller\BlogPost', 'action' => array('index', 'view'), 'roles' => array('guest', 'user'), ), array( 'controller' => 'MyBlog\Controller\BlogPost', 'action' => array('add', 'edit', 'delete'), 'roles' => array('administrator'), ), ), ), 

From the config, it is clear that access to the index and view actions was done for all users, and to add / edit / delete actions, only to users with the role “administrator”. Now it is easy to verify this by clicking on the link / blog / add - error 403 will be returned.

Now we can register using the / user / register link and assign our administrator rights as an SQL query to our user:
 INSERT INTO user_role_linker (user_id, role_id) VALUES (1, 4); 

(yes, the ZfcUser module does not provide the admin panel for managing user roles).

After authorization at the bottom of the page, the developer toolbar will display information about the role of the current user and the add / edit / delete actions will no longer return a 403 error.

A noticeable disadvantage of the current state of the project is that links to edit / delete blogposts are displayed to all users, despite the fact that anonymous users do not have the right to perform such actions. The BjyAuthorize module contains a view isAllowed plugin that makes it easy to fix the problem. Add the following lines to the templates:
 if ($this->isAllowed('controller/MyBlog\Controller\BlogPost:edit')) { // some code here } 

where it is necessary to check the availability of access rights to the corresponding controller / action, this will allow not to display in the template links that cannot be viewed by the current user.

In a similar method, it is possible in the indexAction () action for admins to display the full list of blog posts, and not just those published:
 if ($this->isAllowed('controller/MyBlog\Controller\BlogPost:edit')) { $posts = $objectManager ->getRepository('\MyBlog\Entity\BlogPost') ->findBy(array(), array('created' => 'DESC')); } else { $posts = $objectManager ->getRepository('\MyBlog\Entity\BlogPost') ->findBy(array('state' => 1), array('created' => 'DESC')); } 

The project in its current form is available in the repository on Github with the configured_user tag: github.com/romka/zend-blog-example/tree/configured_user .

Twig


In my practice, I used several different template engines and I consider the Piton Jinja 2 to be the most convenient of those with which I had to work. The Twig PHP templating engine was originally developed by Armin Ronacher, the author of Jinja 2, and then Fabien Potencier, the developer of the Symfony framework, took on his support and development.

One of the key differences between Twig and the template maker embedded in the Zend Framework is that you cannot use PHP code in Twig templates; instead, the template engine has its own syntax for implementing loops, conditional statements, and so on. Twig templates are compiled into PHP code and, as a result, do not lose in performance to the PHP code.

Due to such features as template inheritance, macros, filter systems, etc. Twig patterns are compact and easy to read.

Installation


To install Twig, just follow the standard steps: add a line to composer.json , run php composer.phar update and add a module to application.config.php .

Now to the modules that will use this template engine, you need to add the lines to the configuration file in the view_manager section:
 'strategies' => array( 'ZfcTwigViewStrategy', ), 

and Twig will be ready for use. Moreover, both template engines (Twig and Default) can be used together, that is, part of the templates can be implemented on one template engine, and some on the other.

Twig patterns


The template inheritance mentioned above means that we can create a default layout.twig template with something like this:
 <html> <head> <title> {% block title %}Default title{% endblock title %} </title> {% block script %} <script type="text/javascript" src="/js/jquery.min.js"></script> {% endblock script %} </head> <body> <div class="content"> {% block content %}{{ content|raw }}{% endblock content %} </div> <div class="sidebar"> {% block sidebar %}{{ sidebar|raw }}{% endblock sidebar %} </div> </body> </html> 

Next, we can create a template that will be inherited from layout.twig, in which we only redefine the changed parts of the template:
 {% extends 'layout/layout.twig' %} {% block script %} {{ parent() }} <script type="text/javascript" src="some-additional-file.js"></script> {% endblock script %} {% block content %} Custom content {% endblock content %} 

By default, the block redefined in the template-heir replaces the block in the parent template, but pay attention to the {{parent ()}} line in the script block, its use means that the contents of the same block of the parent template will be loaded into this block.

Now let's rewrite the templates using the new template engine. I started with the standard layout.phtml template from Zend Skeleton Application, you can find it in the MyBlog module in the view / layout directory github.com/romka/zend-blog-example/blob/master/module/MyBlog/view/layout/layout. twig .

Pay attention to how much smaller, for example, the use of view-helpers has become, now instead of:
 <?php echo $this->url('blog', array('action' => 'edit')); ?> 

you can call:
 {{ url('blog', {'action': 'edit'}) }} 

instead of:
 <?php echo $this->showMessages(); ?> 

simply:
 {{ showMessages() }} 

After processing the main template we will deal with forms. First of all, in the view directory of the module, create a macros subdirectory and in it the forms.twig file with the following contents:
 {% macro input(name, value, type, label, size, messages) %} {% if type != 'hidden' %} <div class="form-element-{{ name }}"> {% endif %} {% if label %} {{ label }}: {% endif %} {% if type == 'textarea' %} <textarea name="{{ name }}" size="{{ size|default(20) }}" {% if messages|length > 0 %}class="error"{% endif %}/>{{ value|e }}</textarea> {% elseif type == 'checkbox' %} <input type="{{ type }}" name="{{ name }}" value="1"{% if value == true %} checked="checked"{% endif %} {% if messages|length > 0 %}class="error"{% endif %}/> {% else %} <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" {% if messages|length > 0 %}class="error"{% endif %}/> {% endif %} {% if type != 'hidden' %} </div> {% endif %} {% if messages|length > 0 %} <ul> {% for m in messages %} <li>{{ m }}</li> {% endfor %} </ul> {% endif %} {% endmacro %} 

This macro will be used to display form fields. At the input he gets the field parameters, the output returns html-markup.

Now you can delete the existing add.phtml template and replace it with a new add.twig with the following content:
 {% extends 'layout/layout.twig' %} {% import 'macros/forms.twig' as forms %} {% block content %} <h1>{{ title }}</h1> <form method="{{ form.attributes.method }}" action="{{ url('blog', {'action': 'add'}) }}"> {% for element in form %} {{ forms.input(element.attributes.name, element.value, element.attributes.type, element.label, 20, element.messages) }} {% endfor %} </form> {% endblock content %} 

Similarly, I redid the rest of the templates and deleted the now * .phtml-templates of the module: github.com/romka/zend-blog-example/tree/master/module/MyBlog/view/my-blog/blog .

Conclusion


On this I would like to finish. I did not touch upon a lot of important points, such as logging, caching, Dependency Injection, writing tests, etc, etc., but all these questions are beyond the scope of the introductory article. But I hope that for beginner developers to learn about the Zend Framework 2, this article will help become a useful starting point.

I wrote all 3 parts of this article before the publication of the first part and at the time of completing the text I planned to finish it. After reading the comments, I decided to improve the application a bit:

The preparation of these changes will take some time, I hope to publish the fourth part of the article soon.

Source: https://habr.com/ru/post/192726/


All Articles