Introduce IsDeprecated: PHP7+ Helper for E_USER_DEPRECATED and E_DEPRECATED Detection
Before I continue, allow me to give you my suggestions:
Live with deprecated function is bad, you may can’t upgrade to newer PHP version or newer library as the function that was deprecated already removed in next major/minor version.
Use same environment or at least same major and minor version between local dev and production environment when possible, your life will be easier.
When reality is not always what you want
You may found a a reality when you work at different version of PHP version or library that rely on specific function which already deprecated in some environment, the IsDeprecated may be solution at that time that can verify E_USER_DEPRECATED
or E_DEPRECATED
trigger error. It utilize jeremeamia/FunctionParser for user defined function check, and zendframework/zend-stdlib’s ErrorHandler for E_DEPRECATED
function check.
When you found the passed function is deprecated, you can use alternative function.
Installation
This helper can be installed via composer:
composer require samsonasik/is-deprecated
This helper have features:
1. Detect E_USER_DEPRECATED
- At independent function
- At function inside class
You can use IsDeprecated\isDeprecatedUser
function with signature:
/** * @param string|array $function the "functionName" or ["ClassName" or object, "functionName"] or "ClassName::functionName" * @throws InvalidArgumentException when trigger_error found but the error is not E_USER_DEPRECATED * @throws InvalidArgumentException when trigger_error and E_USER_DEPRECATED found but misplaced * @return bool */ function isDeprecatedUser($function): bool
Note: when trigger_error E_USER_DEPRECATED inside condition, you need to use
actual
call with signature:
/** * @param callable $function callable function * @return bool */ function isDeprecatedWithActualCall(callable $function)
1a. Independent Function
The usage is like the following:
use function IsDeprecated\isDeprecatedUser; function foo() { trigger_error('this method has been deprecated.', E_USER_DEPRECATED); } if (isDeprecatedUser('foo')) { // apply alternative/new function to call... } else { foo(); }
1.b Function Inside Class
The usage is like the following:
use function IsDeprecated\isDeprecatedUser; class AClass { public function foo() { trigger_error('this method has been deprecated.', E_USER_DEPRECATED); } // check inside with $this public function execute() { if (isDeprecatedUser([$this, 'foo'])) { // apply alternative/new function to call... return; } $this->foo(); } } // you may call after instantiation $object = new \AClass(); if (isDeprecatedUser([$object, 'foo'])) { // apply alternative/new function to call... } else { $object->foo(); } // or if (isDeprecatedUser(['AClass', 'foo'])) { // apply alternative/new function to call... } else { (new \AClass())->foo(); }
2. Detect E_DEPRECATED
E_DEPRECATED can be triggered on Core PHP function call.
You can use IsDeprecated\isDeprecatedCore
function with signature:
/** * @param callable $function callable function * @return bool */ function isDeprecatedCore(callable $function): bool
The usage is like the following:
use function IsDeprecated\isDeprecatedCore; $function = function () { mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); }; if (isDeprecatedCore($function)) { // alternative function, eg: openssl ... } else { $function(); }
Limitation
For Core PHP Functions or user function with condition (T_IF or T_SWITCH token), the function passed actually need to be called. It ensure that we don’t get error during call deprecated function, and we can use alternative function if the isDeprecatedCore()
returns true with call of isDeprecatedWithActualCall
.
You want to use it? You can check my repository https://github.com/samsonasik/IsDeprecated
That’s it ;).
Create Authorization functionality in Expressive 3
So, yesterday, I already posted about Authentication part in “Create Login functionality in Expressive 3” post, so, it’s time for authorization part. If you didn’t read that, please read first.
We will use role
of user when deciding what access right of the user for accessed page. For example, we define role
field in users table with the following SQL:
expressive=# ALTER TABLE users ADD COLUMN role character varying(255) NOT NULL DEFAULT 'user'; ALTER TABLE
Ok, we have new column named role
with default value = ‘user’. So, we have existing data with role = ‘user’ :
expressive=# SELECT * FROM users; username | password | role ------------+--------------------------------------------------------------+------ samsonasik | $2a$06$uPvOqYT7fQFP5EYR2jzVrOefwU03GltjAHt.q8l1vWXmkTIbeBcHe | user
Let’s add another user with different role, eg: ‘admin’, as follows:
expressive=# INSERT INTO users(username, password, role) VALUES('admin', crypt('123456', gen_salt('bf')), 'admin'); INSERT 0 1 expressive=# SELECT * FROM users; username | password | role ------------+--------------------------------------------------------------+------- samsonasik | $2a$06$uPvOqYT7fQFP5EYR2jzVrOefwU03GltjAHt.q8l1vWXmkTIbeBcHe | user admin | $2a$06$0pLYG/GVQOL6v9tLmjBB..cvUIk0vBdcDM8aV373AVO3ve9MdSbom | admin (2 rows)
Perfect, now, we need to add sql_get_roles
config under [‘authentication’][‘pdo’] to get role from users table, we can add at our config/autoload/local.php
:
<?php // config/autoload/local.php return [ 'authentication' => [ 'pdo' => [ // ... 'sql_get_roles' => 'SELECT role FROM users WHERE username = :identity' ], // ... ], ];
When we login and var_dump the session data, we will get the following array value:
// var_dump($session->get(UserInterface::class)); array (size=2) 'username' => string 'samsonasik' (length=10) 'roles' => array (size=1) 0 => string 'user' (length=4)
Until here we are doing great!
To differentiate access page, let’s create a different page for admin
only, for example: AdminPageHandler
:
<?php declare(strict_types=1); namespace App\Handler; use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Zend\Diactoros\Response\HtmlResponse; use Zend\Expressive\Template\TemplateRendererInterface; class AdminPageHandler implements RequestHandlerInterface { private $template; public function __construct(TemplateRendererInterface $template) { $this->template = $template; } public function handle(ServerRequestInterface $request) : ResponseInterface { return new HtmlResponse($this->template->render('app::admin-page', [])); } }
with factory as follows:
<?php declare(strict_types=1); namespace App\Handler; use Psr\Container\ContainerInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Expressive\Template\TemplateRendererInterface; class AdminPageFactory { public function __invoke(ContainerInterface $container) : RequestHandlerInterface { $template = $container->get(TemplateRendererInterface::class); return new AdminPageHandler($template); } }
We can register the AdminPageHandler
middleware at App\ConfigProvider::getDependencies()
:
<?php class ConfigProvider { public function getDependencies() : array { return [ 'invokables' => [ /**/ ], 'factories' => [ // ... Handler\AdminPageHandler::class => Handler\AdminPageFactory::class, ], ]; } }
Then let’s define route for it, eg: ‘/admin’:
// config/routes.php $app->route('/admin', [ \Zend\Expressive\Authentication\AuthenticationMiddleware::class, App\Handler\AdminPageHandler::class, ], ['GET'], 'admin');
The view can just show it that it is currently at admin
page:
<?php // templates/app/admin-page.phtml ?> Admin Page
So, if we logged in as role = user, and access `/admin’ page, we still can see the page.
Let’s authorize it!
First, we can add components for it, for example, we are going to use ACL, we can install expressive component for it via command:
$ composer require \ zendframework/zend-expressive-authorization:^1.0 \ zendframework/zend-expressive-authorization-acl:^1.0
It will install the following components:
* zendframework/zend-expressive-authorization
* zendframework/zend-permissions-acl
* zendframework/zend-expressive-authorization-acl
After they installed, ensure our config/config.php
has registered the following ConfigProvider
classes:
<?php // config/config.php $aggregator = new ConfigAggregator([ // ... \Zend\Expressive\Authorization\Acl\ConfigProvider::class, \Zend\Expressive\Authorization\ConfigProvider::class, // ... ]);
Then, we can map Zend\Expressive\Authorization\AuthorizationInterface::class
to Zend\Expressive\Authorization\Acl\ZendAcl::class
at config/autoload/dependencies.global.php
under alias
to use the ZendAcl
service :
<?php // config/autoload/dependencies.global.php return [ 'dependencies' => [ 'aliases' => [ // ... Zend\Expressive\Authorization\AuthorizationInterface::class => Zend\Expressive\Authorization\Acl\ZendAcl::class ], ], ];
Roles, Resources, and Rights definitions
We can define roles, resources, and rights under [‘zend-expressive-authorization-acl’] config, for example, at config/autoload/zend-expressive.global.php
:
<?php // config/autoload/zend-expressive.global.php return [ // ... 'zend-expressive-authorization-acl' => [ 'roles' => [ 'guest' => [], 'user' => ['guest'], 'admin' => ['user'], ], 'resources' => [ 'home', 'admin', 'login', 'logout', ], 'allow' => [ 'guest' => [ 'login', ], 'user' => [ 'logout', 'home', ], 'admin' => [ 'admin', ], ], ], // ... ];
I’m going to mark non-logged user with role = “guest”, “user” role will inherit all guest rights, and “admin” role inherit all user rights, that mean, admin can access what user can access, but not opposite.
The resources are route names that registered at config/routes.php
.
Authorization Process
To get ‘roles’ value, we have Zend\Expressive\Authorization\AuthorizationMiddleware
that checks from request attribute named Zend\Expressive\Authentication\UserInterface::class
, we can define at config/pipeline.php
before $app->pipe(DispatchMiddleware::class); with continue pipe the Zend\Expressive\Authorization\AuthorizationMiddleware
after it, as follow:
// config/pipeline.php $app->pipe(new class implements Psr\Http\Server\MiddlewareInterface{ public function process( Psr\Http\Message\ServerRequestInterface $request, Psr\Http\Server\RequestHandlerInterface $handler ) : Psr\Http\Message\ResponseInterface { $session = $request->getAttribute( Zend\Expressive\Session\SessionMiddleware::SESSION_ATTRIBUTE ); // No Session data if (! $session->has(Zend\Expressive\Authentication\UserInterface::class)) { $user = ''; $roles = ['guest']; $request = $request->withAttribute( Zend\Expressive\Authentication\UserInterface::class, new Zend\Expressive\Authentication\DefaultUser( $user, $roles ) ); $response = $handler->handle($request); if ($request->getUri()->getPath() === '/login' || $response->getStatusCode() !== 403) { return $response; } return new Zend\Diactoros\Response\RedirectResponse('/login'); } // at /login page, redirect to authenticated page if ($request->getUri()->getPath() === '/login') { return new Zend\Diactoros\Response\RedirectResponse('/'); } // define roles from DB $sessionData = $session->get(Zend\Expressive\Authentication\UserInterface::class); $request = $request->withAttribute( Zend\Expressive\Authentication\UserInterface::class, new Zend\Expressive\Authentication\DefaultUser( $sessionData['username'], $sessionData['roles'] ) ); return $handler->handle($request); } }); $app->pipe(\Zend\Expressive\Authorization\AuthorizationMiddleware::class); $app->pipe(DispatchMiddleware::class);
When we logged as user, but want to access “admin” resource, eg: “/admin”, we will get “403 Forbidden” :
How about integration with “404” page?
With current setup, when we go to 404 page, eg: “/404”, we will get the “Resource ” not found”. To make it work with the NotFoundHandler, we need to inject NotFoundHandler to our authorization check. Let’s make above authorization check to a dedicated class with inject the NotFoundHandler, as follow:
<?php namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Response\RedirectResponse; use Zend\Expressive\Authentication\DefaultUser; use Zend\Expressive\Authentication\UserInterface; use Zend\Expressive\Handler\NotFoundHandler; use Zend\Expressive\Router\RouteResult; use Zend\Expressive\Session\SessionMiddleware; class AuthorizationMiddleware implements MiddlewareInterface { private $notFoundHandler; private $redirect; public function __construct(NotFoundHandler $notFoundHandler, string $redirect) { $this->notFoundHandler = $notFoundHandler; $this->redirect = $redirect; } public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface { // 404 check early $routeResult = $request->getAttribute(RouteResult::class); if ($routeResult->isFailure()) { return $this->notFoundHandler->handle($request, $handler); } $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); // No Session data if (! $session->has(UserInterface::class)) { $user = ''; $roles = ['guest']; $request = $request->withAttribute( UserInterface::class, new DefaultUser( $user, $roles ) ); $response = $handler->handle($request); if ($request->getUri()->getPath() === $this->redirect || $response->getStatusCode() !== 403) { return $response; } return new RedirectResponse($this->redirect); } // at /login page, redirect to authenticated page if ($request->getUri()->getPath() === $this->redirect) { return new RedirectResponse('/'); } // define roles from DB $sessionData = $session->get(UserInterface::class); $request = $request->withAttribute( UserInterface::class, new DefaultUser( $sessionData['username'], $sessionData['roles'] ) ); return $handler->handle($request); } }
With above “$routeResult->isFailure()” check in line 30 above, we return 404 page early before check against authorization.
For service creation, we can create a AuthorizationMiddlewareFactory
for it:
<?php declare(strict_types=1); namespace App\Middleware; use Psr\Container\ContainerInterface; use Psr\Http\Server\MiddlewareInterface; use Zend\Expressive\Handler\NotFoundHandler; class AuthorizationMiddlewareFactory { public function __invoke(ContainerInterface $container) : MiddlewareInterface { $notFoundHandler = $container->get(NotFoundHandler::class); $redirect = $container->get('config')['authentication']['redirect']; return new AuthorizationMiddleware($notFoundHandler, $redirect); } }
Now, we can register as a service for the AuthorizationMiddleware
in the ConfigProvider class:
<?php class ConfigProvider { public function getDependencies() : array { return [ 'invokables' => [ /**/ ], 'factories' => [ // ... Middleware\AuthorizationMiddleware::class => Middleware\AuthorizationMiddlewareFactory::class, ], ]; } }
Now, in the pipeline, we have a cleaner, and with 404 handler integration:
$app->pipe(\Zend\Expressive\Session\SessionMiddleware::class); $app->pipe(RouteMiddleware::class); // ... $app->pipe(\App\Middleware\AuthorizationMiddleware::class); $app->pipe(\Zend\Expressive\Authorization\AuthorizationMiddleware::class); $app->pipe(DispatchMiddleware::class)
That’s it ;).
Create Login functionality in Expressive 3
Zend Expressive 3 is not released yet released, and expressive session related components are in active development ready to use. However, we already can give them a try.
Use case
For example, we need simple login functionalities:
- Login Form
- Authentication process, read from DB
- Save authenticated value to Session
Setup
First, we can install the Zend Expressive 3 skeleton with the following command:
$ composer create-project "zendframework/zend-expressive-skeleton:^3.0.0" expressive3
Important Notes:
When prompted with service container requirement, choose zend-servicemanager
When prompted with template engine requirement, choose zend-view
There are components that can be installed via command:
$ cd expressive3 $ composer require \ zendframework/zend-form:^2.11 \ zendframework/zend-i18n:^2.7 \ zendframework/zend-expressive-authentication:^1.0 \ zendframework/zend-expressive-authentication-session:^1.0 \ zendframework/zend-expressive-session:^1.0 \ zendframework/zend-expressive-session-ext:^1.0
After above components installed, ensure that your config/config.php
injected with ConfigProvider
like below:
<?php // config/config.php $aggregator = new ConfigAggregator([ // ... form requirements \Zend\I18n\ConfigProvider::class, \Zend\Form\ConfigProvider::class, \Zend\InputFilter\ConfigProvider::class, \Zend\Filter\ConfigProvider::class, \Zend\Hydrator\ConfigProvider::class, // ... // ... auth requirements \Zend\Expressive\Authentication\ConfigProvider::class, \Zend\Expressive\Authentication\Session\ConfigProvider::class, \Zend\Expressive\Session\ConfigProvider::class, \Zend\Expressive\Session\Ext\ConfigProvider::class, // ... ];
we can first setup database data, in this case, I tried with Postgresql:
$ createdb -Udeveloper expressive Password: $ psql -Udeveloper expressive Password for user developer: psql (10.1) Type "help" for help. expressive=# CREATE TABLE users(username character varying(255) PRIMARY KEY NOT NULL, password text NOT NULL); CREATE TABLE expressive=# CREATE EXTENSION pgcrypto; CREATE EXTENSION expressive=# INSERT INTO users(username, password) VALUES('samsonasik', crypt('123456', gen_salt('bf'))); INSERT 0 1
Above, I create database named “expressive”, create table named “users” with username and password field, insert sample data with pgcrypto extension for create hashed password of 123456 using blowfish.
Now, we can setup the authentication configuration at config/autoload/local.php
as follows:
<?php // config/autoload/local.php return [ 'authentication' => [ 'pdo' => [ 'dsn' => 'pgsql:host=localhost;port=5432;dbname=expressive;user=developer;password=xxxxx', 'table' => 'users', 'field' => [ 'identity' => 'username', 'password' => 'password', ], ], 'username' => 'username', 'password' => 'password', 'redirect' => '/login', ], ];
Then, we can map Zend\Expressive\Authentication\UserRepositoryInterface::class
to Zend\Expressive\Authentication\UserRepository\PdoDatabase::class
under alias
and register Zend\Expressive\Authentication\AuthenticationInterface::class
under factories
config at config/autoload/dependencies.global.php
:
<?php // config/autoload/dependencies.global.php return [ 'dependencies' => [ 'aliases' => [ // ... Zend\Expressive\Authentication\UserRepositoryInterface::class => Zend\Expressive\Authentication\UserRepository\PdoDatabase::class ], 'factories' => [ // ... Zend\Expressive\Authentication\AuthenticationInterface::class => Zend\Expressive\Authentication\Session\PhpSessionFactory::class, ], // ... ], ];
For Session operations, we need Zend\Expressive\Session\SessionMiddleware
middleware before routing middleware, so, in config/pipeline.php
, we call pipe on it before $app->pipe(RouteMiddleware::class);:
// ... $app->pipe(\Zend\Expressive\Session\SessionMiddleware::class); // Register the routing middleware in the middleware pipeline $app->pipe(RouteMiddleware::class); // ...
as example: we want to redirect non-logged user to /login
page, eg: at home page (/), we can register “home” routes config:
<?php // config/routes.php $app->route('/', [ \Zend\Expressive\Authentication\AuthenticationMiddleware::class, App\Handler\HomePageHandler::class, ], ['GET'], 'home');
Now run the php -S command:
$ php -S localhost:8080 -t public
When access ‘/’ page via localhost:8080, we should be redirected to /login
page which currently a 404 page, nice!
Login Page
First, we create a LoginForm
with username
and password
field like the following:
<?php // src/App/Form/LoginForm.php declare(strict_types=1); namespace App\Form; use Zend\Form\Element\Password; use Zend\Form\Element\Text; use Zend\Form\Form; use Zend\InputFilter\InputFilterProviderInterface; class LoginForm extends Form implements InputFilterProviderInterface { public function __construct() { parent::__construct('login-form'); } public function init() { $this->add([ 'type' => Text::class, 'name' => 'username', 'options' => [ 'label' => 'Username', ], ]); $this->add([ 'type' => Password::class, 'name' => 'password', 'options' => [ 'label' => 'Password', ], ]); $this->add([ 'name' => 'Login', 'type' => 'submit', 'attributes' => [ 'value' => 'Login', ], ]); } public function getInputFilterSpecification() { return [ [ 'name' => 'username', 'required' => true, 'filters' => [ ['name' => 'StripTags'], ['name' => 'StringTrim'], ], ], [ 'name' => 'password', 'required' => true, 'filters' => [ ['name' => 'StripTags'], ['name' => 'StringTrim'], ], ], ]; } }
We then can create a login page handler with inject it with login form with the following factory:
<?php // src/App/Handler/LoginPageFactory.php declare(strict_types=1); namespace App\Handler; use App\Form\LoginForm; use Psr\Http\Server\MiddlewareInterface; use Psr\Container\ContainerInterface; use Zend\Expressive\Template\TemplateRendererInterface; use Zend\Form\FormElementManager; class LoginPageFactory { public function __invoke(ContainerInterface $container) : MiddlewareInterface { $template = $container->get(TemplateRendererInterface::class); $loginForm = $container->get(FormElementManager::class) ->get(LoginForm::class); return new LoginPageHandler($template, $loginForm); } }
The LoginPageHandler
itself can be initialized with :
<?php // src/App/Handler/LoginPageHandler.php declare(strict_types=1); namespace App\Handler; use App\Form\LoginForm; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Zend\Diactoros\Response\HtmlResponse; use Zend\Diactoros\Response\RedirectResponse; use Zend\Expressive\Authentication\UserInterface; use Zend\Expressive\Session\SessionMiddleware; use Zend\Expressive\Template\TemplateRendererInterface; class LoginPageHandler implements MiddlewareInterface { private $template; private $loginForm; public function __construct( TemplateRendererInterface $template, LoginForm $loginForm ) { $this->template = $template; $this->loginForm = $loginForm; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); if ($session->has(UserInterface::class)) { return new RedirectResponse('/'); } $error = ''; // handle authentication here Next return new HtmlResponse( $this->template->render('app::login-page', [ 'form' => $this->loginForm, 'error' => $error, ]) ); } }
Above, we redirect to ‘/’ page when there is a session data as it already authenticated check. We are going to add authentication process next.
The Login form can be as simple as the following:
<?php // templates/app/login-page.phtml echo $error; $form->prepare(); echo $this->form($form);
We can register the LoginPageHandler
at App\ConfigProvider::getDependencies()
config:
<?php // src/App/ConfigProvider.php class ConfigProvider { public function getDependencies() : array { return [ 'invokables' => [ /**/ ], 'factories' => [ // ... Handler\LoginPageHandler::class => Handler\LoginPageFactory::class, ], ]; } }
The routing can be registered as follows with add \Zend\Expressive\Authentication\AuthenticationMiddleware::class
for next middleware:
// config/routes.php // ... $app->route('/login', [ App\Handler\LoginPageHandler::class, // for authentication next handling \Zend\Expressive\Authentication\AuthenticationMiddleware::class, ], ['GET', 'POST'],'login');
Above, we allow ‘GET’ and ‘POST’ in same ‘/login’ page.
Authentication process
Time for authentication process, we utilize Zend\Expressive\Authentication\AuthenticationMiddleware
class that registered at the last entry at the /login
route, we can accomodate it after check of form is valid
<?php // src/App/Handler/LoginPageHandler.php class LoginPageHandler implements MiddlewareInterface { // ... public function __construct( TemplateRendererInterface $template, LoginForm $loginForm) { /* */ } public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface { // ....... $error = ''; if ($request->getMethod() === 'POST') { $this->loginForm->setData($request->getParsedBody()); if ($this->loginForm->isValid()) { $response = $handler->handle($request); if ($response->getStatusCode() !== 302) { return new RedirectResponse('/'); } $error = 'Login Failure, please try again'; } } // ... return new HtmlResponse( $this->template->render('app::login-page', [ 'form' => $this->loginForm, 'error' => $error, ]) ); }
We call handle($request)
for next Zend\Expressive\Authentication\AuthenticationMiddleware
with:
$response = $handler->handle($request); if ($response->getStatusCode() !== 302) { return new RedirectResponse('/'); }
When status code is not 301 302, it authenticated and session filled, we can then redirect to page that need to be authenticated to be access. Failure authentication default behaviour has 301 302 status code which we can set config “redirect” in “authentication” config, on above code, I just want to show it in the login form that the login failure, so I set the $error variable value to “Login Failure, please try again”, so when login failure, it will got the error like the following:
That’s it ;).
How about logout ? We can use clear()
method from SessionMiddleware::SESSION_ATTRIBUTE
attribute like the following:
use Zend\Expressive\Authentication\UserInterface; use Zend\Expressive\Session\SessionMiddleware; class LogoutPageHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request) : ResponseInterface { $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); if ($session->has(UserInterface::class)) { $session->clear(); } // ... } }
How about authorization part? You can read my next post about create authorization functionality in zend expressive 3
Functional Test Symfony 4 with Kahlan 4
Yes, there is a bundle for it, but currently not fully work well with kahlan 4 yet. However, we can still use kahlan 4 for it. The simplest way is define Symfony 4 skeleton bootstrap in kahlan config, and use its property at specs, for example, we configure config at kahlan-config.php
as follows:
<?php // kahlan-config.php use App\Kernel; use Kahlan\Filter\Filters; use Symfony\Component\Debug\Debug; use Symfony\Component\HttpFoundation\Request; Filters::apply($this, 'bootstrap', function($next) { require __DIR__.'/config/bootstrap.php'; umask(0000); Debug::enable(); $root = $this->suite()->root(); $root->beforeAll(function () { $this->request = Request::createFromGlobals(); $this->kernel = new Kernel('test', false); }); return $next(); });
Above settings are minimal, if you need more setup, you can define there. If you didn’t require kahlan/kahlan:^4.0
, you can require via composer:
$ composer require --dev kahlan/kahlan:^4.0
Give a try
Let’s try testing a famous /lucky/number
from LuckyController
. We have the following controller:
<?php // src/Controller/LuckyController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Routing\Annotation\Route; class LuckyController extends Controller { /** * @Route("/lucky/number", name="lucky_number") */ public function number() { $number = mt_rand(0, 100); return $this->render('lucky/number.html.twig', [ 'number' => $number, ]); } }
And our twig file is:
{# templates/lucky/number.html.twig #} <h1>Your lucky number is {{ number }}</h1>
We can place test under spec
directory at root directory, for its test, we can create a spec/Controller
directory:
kahlan.config.php ├── spec │ └── Controller
Now, we can create the test as follows with make request to the ‘/lucky/number’ page and get its response. We can use toMatchEcho
matcher provided with regex to get match random number of mt_rand(0, 100)
that printed inside a response html content:
<?php // spec/Controller/LuckyControllerSpec.php namespace App\Spec\Controller; describe('LuckyController', function () { describe('/lucky/number', function () { it('shows lucky number', function () { $request = $this->request->create('/lucky/number', 'GET'); $response = $this->kernel->handle($request); expect(function () use ($response) { $response->send(); })->toMatchEcho( "#Your lucky number is ([0-9]|[1-8][0-9]|9[0-9]|100)#" ); }); }); });
Time to run it with command:
$ vendor/bin/kahlan
We will get the success output:
That’s it 😉
leave a comment