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

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

the

Working with users


Code written in the previous parts, allows you to create, edit, and delete blogpost all site visitors. Such an approach is unacceptable for any business website, so now is the time to solve the issues of registration/authorization and allocation of access rights to different features of the application.

the

Zf Commons


For Zend framework written quite a lot of modules that solve common tasks, you can find them on a special website: modules.zendframework.com. Instead of developing their bikes for the solution of standard tasks that I considered more correct to use/adapt ready-made solutions (or at least ready-made solutions need to study before embarking on the development of the bike).

Among the many developers of modules is allocated a team of ZF Commons, the boys from this team have developed a number of very useful modules that we'll use in this project: github.com/ZF-Commons. Consider some of them that we need at this stage.

ZfcBase


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

ZfcUser


This module implements the mechanisms for registration/authorization of users, user profile, and View helpers for use in templates (https://github.com/ZF-Commons/ZfcUser).

ZfcUserDoctrineORM


By default, ZfcUser works with standard for a framework mechanism to work with databases, because in our project we use Doctrine ORM, you also need a module ZfcUserDoctrineORM (https://github.com/ZF-Commons/ZfcUserDoctrineORM).

ZfcTwig


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

the

BjyAuthorize


In addition to modules from ZfCommons I will be using the BjyAuthorize module that provides a convenient mechanism for the distribution of access rights. The logic operation of the module is simple and common among other frameworks. The module operates with the 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, which we set access rights for different roles.

the

Prepare to configure users


Before you configure users, you must create an entity for user and role that will be used by Doctrine. Included with the BjyAuthorize module are examples of such entities, based on them I created a module MyUser.

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

You should only pay attention to its configuration (https://github.com/romka/zend-blog-example/blob/master/module/MyUser/config/module.config.php):
the
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


'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 configuration we model the zfcuser entity on our own, which is responsible for working with the user and specify the module BjyAuthorize the entity responsible for working with roles.

Module MyUser to add to application.config.php and then in the console run the commands:
the
./vendor/bin/doctrine-module orm:schema-tool:update --force
./vendor/bin/doctrine-module orm:validate-schema

The first is to create the database tables for entities created by the Mapping module, the second is to ensure that the first command worked correctly.

The last preparatory step is to run a query that will create the appropriate roles:
the
INSERT INTO `role` 
(`id`, `parent_id`, `roleId`) 
VALUES
(1, NULL, 'guest'),
(2, 1, 'user'),
(3, 2, 'moderator'), 
(4, 3, 'administrator');


the

setup ZfcUser, BjyAuthorize and ZfcUserDoctrineORM


The first step is to register the new modules in the settings of the Composer:
the
"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.*"

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

Attention! The settings of some of these modules will be overridden by the settings of bespoke modules, therefore these modules need to add up 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 must set:
the
'table_name' => 'users',

as default the user uses the user table.

Yet, in the same directory you need to create a configuration file bjyauth.global.php contains the access rights settings for different roles. Full version of this file you can view on Github github.com/romka/zend-blog-example/blob/master/config/autoload/bjyauth.global.phpthe most interesting part which is responsible for the allocation of rights of access to the various controllers is shown below:
the
'guards' => array(
/* If this guard is specified here (i.e. 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 config it is visible that access the index action and view we made for all users, and actions to add/edit/delete — only users with the administrator role. Now this is easily seen by clicking on the link /blog/add will return the 403 error.

Now we can register by the link /user/register and give your user administrator rights by the SQL query:
the
INSERT INTO user_role_linker (user_id, role_id) VALUES (1, 4);

(Yes, admin area to manage user roles module ZfcUser does not provide).

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

Notable drawback of the current state of the project is that the links to edit/delete blogposts displayed to all users, despite the fact that the anonymous access rights to execute such actions. The module BjyAuthorize contains View-plugin allowed, which makes it easy to fix the problem. Add in the stitch patterns like this:
// some code here }
where you need to check the access rights to the corresponding controller/action, it will not display in the template links, which is unavailable to the current user.

The same method can be used in the action maps to the indexAction() to the admin display the full list of blogposts and not just published:
the
if ($this- > allowed('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 tag configured_user: github.com/romka/zend-blog-example/tree/configured_user.

the

Twig


In my practice I have used several different engines and I think Python Jinja 2 the most comfortable that I had to work. PHP template engine Twig was originally developed Armin'Ronacher'om om — the author of Jinja 2, and then for its support and development came from Fabien Potencier — the developer of the framework Symfony.

One of the key differences Tviga from the built-in Zend Framework template engine is that Twig templates, you cannot use PHP code, instead a template engine implements its own syntax to implement loops, conditionals, etc. Twig templates are compiled into PHP code and as a result do not lose in performance of PHP code.

Thanks to such features like template inheritance, macros, filters, etc. Twig templates out compact and easily readable.

the

Installation


To install Tviga it is enough to perform standard actions: add a line in the composer.json, run php composer.phar update and add the module in application.config.php.

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

and Twigg is ready to use. Both of the templating engine (Twigg and default) can be used together, that is part of the template can be realized on one template, part on another.

the

Twig templates


The above mentioned template inheritance means that we can create a default template is layout.twig about this content
the
<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 inherit from the layout.twig, which is override only the changed parts of the template:
the
{% 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 %}

The default unit is overridden in the template-the heir to replace a block in the parent template, but note the line of {{ parent() }} in a script block, it also means that this unit will fetch the contents of the contents of the same block from the parent template.

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

Note how much smaller it became, for example, using view-helper, now it is:
the
<?php
echo $this->url('blog', array('action' => 'edit'));
?>

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

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

just:
the
{{ showMessages() }}

After processing the main template, make forms. First thing in the directory view of the module, create a subdirectory macros and file forms.twig with this content:
{% 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. On entrance it receives the field data, the output returns html markup.

It is now possible to delete an existing template, the add.phtml and replaced add.twig with the contents:
the
{% 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.the name element.value, element.attributes.type, element.label, 20, element.messages) }}
{% endfor %}
</form>
{% endblock content %}

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

the

Conclusion


I'd like to finish. I have not touched on many important points, such as logging, caching, Dependency Injection, writing tests, etc, etc, but all these issues are beyond the scope of introductory article. But I hope that developers are starting to learn Zend Framework 2 this article will help be a useful starting point.

I wrote all 3 parts of this article before the publication of the first part and at the time of finalization of the text planned out. After reading reviews I decided to improve this app:
the
    the
  • to use REST instead of checks for the request type GET/POST
  • the
  • to move part of the task on the hooks before action,
  • the
  • to move some tasks to the hook of the Doctrine,
  • the
  • get rid of magic constants,
  • the
  • to move the configs in yaml
  • the
  • to replace a part of calls for DI(?).

To prepare for these changes will take some time, I hope to publish the fourth part of article soon.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Wikia Search — first impressions

Emulator data from GNSS receiver NMEA

mSearch: search + filter for MODX Revolution