Immediate remove cookie data in zend-mvc application
In zend-mvc application, we can utilize Zend\Http\Header\SetCookie
header to set cookie to be added to response header. For example, we have a setAction()
to set it, we can do:
use Zend\Http\Header\SetCookie; // ... public function setAction() { $request = $this->getRequest(); $response = $this->getResponse(); $response->getHeaders()->addHeader(new SetCookie( // name 'test', // value 'abc', // expires \time() + (60 * 60 * 24 * 365), // path '/', // domain null, // secure $request->getUri()->getScheme() === 'https', // httponly true )); return $response; } // ...
Above, we set cookie with name “test” with value “abc”, with expires 1 year. To remove it completely before 1 year hit, we can pass 8th parameter as maxAge to be 0, so, for example, we have a removeAction
to remove it, we can do:
use Zend\Http\Header\SetCookie; // ... public function removeAction() { $request = $this->getRequest(); $response = $this->getResponse(); $cookie = $request->getCookie(); if (! isset($cookie['test'])) { // already removed, return response early return $response; } $response->getHeaders()->addHeader(new SetCookie( 'test', null, // no need to set value null, // no need to set expires '/', null, $request->getUri()->getScheme() === 'https', true, 0 // set maxAge to 0 make "test" cookie gone )); return $response; } // ...
That’s it!
Using Buffer for Resultset::rewind() after Resultset::next() called in Zend\Db
In Zend\Db, there is Resultset
which can be used as result of db records as instanceof PHP Iterator. For example, we have the following table structure:
CREATE TABLE test ( id serial NOT NULL PRIMARY KEY, name character varying(255) );
and we have the following data:
To build resultset, we can use the following code:
include './vendor/autoload.php'; use Zend\Db\Adapter\Adapter; use Zend\Db\TableGateway\TableGateway; use Zend\Db\ResultSet\ResultSet; $adapter = new Adapter([ 'username' => 'developer', 'password' => '123456', 'driver' => 'pdo_pgsql', 'database' => 'learn', 'host' => 'localhost', ]); $resultSetPrototype = new ResultSet( null, new ArrayObject([], ArrayObject::ARRAY_AS_PROPS) ); $tableGateway = new TableGateway( 'test', $adapter, null, $resultSetPrototype ); $select = $tableGateway->getSql()->select(); $resultSet = $tableGateway->selectWith($select);
On getting the data, we can go to specific record position by using next()
, for example: we want to get the 2nd record of selected data, we can use the following code:
$resultSet->current(); $resultSet->next(); var_dump($resultSet->current());
and we will get the following data:
class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(2) 'name' => string(6) "test 2" } }
However, when we need to back to first position, we can’t just use rewind()
as follow:
$resultSet->rewind(); var_dump($resultSet->current());
Above code will result wrong data, which is a next record data:
class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(3) 'name' => string(6) "test 3" } }
To make that work, we need to use buffer()
method early after result set created, so, the code will need to be:
$select = $tableGateway->getSql()->select(); $resultSet = $tableGateway->selectWith($select); $resultSet->buffer(); $resultSet->current(); // ensure hit 1st record first $resultSet->next(); // next position var_dump($resultSet->current()); // get 2nd record $resultSet->rewind(); // back to position 0 var_dump($resultSet->current()); // get 1st record again
That will show the correct data:
# second record by call of next() class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(2) 'name' => string(6) "test 2" } } # first record after call of rewind() class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(1) 'name' => string(6) "test 1" } }
Programmatically add route in zend-mvc application
Yes, we usually setup routing via configuration in our module.config.php in our modules. However, in very specific condition, we may need to programmatically setting it. There is a ‘Router’ service for that, it represent Zend\Router\Http\TreeRouteStack
instance and we can use addRoute()
method against it.
For example, we need to setting it in Module::onBootstrap()
, so we can code like the following:
<?php namespace Application; use Zend\Mvc\MvcEvent; use Zend\Router\Http\Literal; class Module { public function onBootstrap(MvcEvent $e) { // if ( ... condition to be handled programmatically ... ) : $services = $e->getApplication()->getServiceManager(); $router = $services->get('Router'); $route = Literal::factory([ 'route' => '/foo', 'defaults' => [ 'controller' => Controller\FooController::class, 'action' => 'index', ], ]); $router->addRoute('foo', $route); // end if } public function getConfig() { /* ... */ } }
That’s it!
ErrorHeroModule : a Hero for Your Zend Mvc and Expressive Application
After > 1 year work with 52 releases, I think it is time to show off. Even you have 100% test coverage, error may can still happen, that’s why ErrorHeroModule was born. ErrorHeroModule is a Hero for your Zend Mvc, and zend-expressive application to trap php errors and exception with configureable options.
The logging storage is mainly a database, then can continue “log” to your email when you want it.
Features
1. Save to DB with Db Writer Adapter
We can choose using Zend\Db
or Doctrine
via DoctrineORMModule
. The error log is recorded like below:
2. Log Exception (dispatch.error and render.error) and PHP Errors in all events process
This handle all Exceptions and Errors with support PHP 7 Error during MVC process or middleware flow.
3. Support excludes PHP E_* Error (eg: exclude E_USER_DEPRECATED) in config settings
This can be used when you have a functionality which has collection of E_* errors, and you need to keep the functionality to run.
4. Support excludes PHP Exception (eg: Exception class or classes that extends it) in config settings
This can be used when you have exceptions that you want to have special treatment.
5. Handle only once log error for same error per configured time range
This can be used when on some environment, eg: in production, we don’t want to get same error repeatly reported in some periodic time while we are fixing it.
6. Set default page (web access) or default message (console access) for error if configured ‘display_errors’ = 0
This can be used to set a “nice” page on web environment:
or content on console access:
7. Set default content when request is XMLHttpRequest via ‘ajax’ configuration
This can be used to set a default content when request is an XMLHttpRequest.
8. Provide request information ( http method, raw data, query data, files data, and cookie data )
This can be used to help reproduce the error.
9. Send Mail
This has options:
– many receivers to listed configured email
– with include $_FILES into attachments on upload error.
This can be used to help reproduce the error, with include uploaded data when error happen when we just submitted a form with upload process.
Support
This module support zend-mvc:^2.5 and zend-expressive:^1.1|^2.0 with php version ^5.6|^7.0. My plan is to drop php ^5.6 in version 2.
Limitations
There are some limitations right now and I want it to be implemented in next releases:
General functionality:
- Allow custom formatter when log to email, currently, it send Json format to email.
Current Json Formatter is really stable with the following format sample data:
{ "timestamp": "2017-12-20T15:23:00+07:00", "priority": 3, "priorityName": "ERR", "message": "a sample error preview", "extra": { "url": "http://app.dev/error-preview", "file": "/var/www/app/vendor/samsonasik/error-hero-module/src/Controller/ErrorPreviewController.php", "line": 11, "error_type": "Exception", "trace": "#0 /var/www/app/vendor/zendframework/zend-mvc/src/Controller/AbstractActionController.php(78): ErrorHeroModule\\Controller\\ErrorPreviewController->exceptionAction() #1 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Zend\\Mvc\\MvcEvent)) #2 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\\EventManager\\EventManager->triggerListeners(Object(Zend\\Mvc\\MvcEvent), Object(Closure)) #3 /var/www/app/vendor/zendframework/zend-mvc/src/Controller/AbstractController.php(106): Zend\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Zend\\Mvc\\MvcEvent)) #4 /var/www/app/vendor/zendframework/zend-mvc/src/DispatchListener.php(138): Zend\\Mvc\\Controller\\AbstractController->dispatch(Object(Zend\\Http\\PhpEnvironment\\Request), Object(Zend\\Http\\PhpEnvironment\\Response)) #5 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\\Mvc\\DispatchListener->onDispatch(Object(Zend\\Mvc\\MvcEvent)) #6 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\\EventManager\\EventManager->triggerListeners(Object(Zend\\Mvc\\MvcEvent), Object(Closure)) #7 /var/www/app/vendor/zendframework/zend-mvc/src/Application.php(332): Zend\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Zend\\Mvc\\MvcEvent)) #8 /var/www/app/public/index.php(53): Zend\\Mvc\\Application->run() #9 {main}", "request_data": { "query": [], "request_method": "GET", "body_data": [], "raw_data": "", "files_data": [], "cookie_data": { "ZS6SESSID": "pbihc9ts004oq4b5alg4tg91b6", "PHPSESSID": "bkd7jaj22z936vstc9l0xuc9sr2dqp4g", } } } }
The drawback with allow custom formatter is you maintain/keep an eye yourself for the formatter you provide!
Zend Mvc application:
- Trap exception and error when they happen at
Module::init()
.
Zend Expressive application:
Make support for non zend-servicemanager for container.(support zend-servicemanager, symfony, aura, auryn, and pimple for zend-expressive application at version 2.9.0)Make support for non zend-view for custom page template engine when error happen.(supported at version 2.1.0)
That’s it for now. If you see something can be improved, please contribute! Thank you for all users that using it.
Apigility: Using zf-oauth2’s refresh_token_lifetime to create client’s remember me functionality
If you’re building client based application which require oauth authentication to apigility application which uses time based expire access token, you may want to create a remember me functionality in client.
PHP Configuration
The very first required is set the client and the server side has same timezone, eg:
# your php.ini date.timezone = "Asia/Jakarta"
This is ensure that you have actually same time for both client and api server.
DB data requirements
If you already setup the Oauth2 DB, you need to insert/update the client to support “password” and “refresh_token”, that use both means use space-separated value eg:
INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`, `grant_types`, `scope`, `user_id`) VALUES ( 'test', '$2y$10$vbuy12RNSTJ.LHDdivegwu9dqkxh8h6OS4VoIX64HQGngAqUfcSJe', '/oauth/receivecode', 'password refresh_token', NULL, NULL );
Above sql insert data to oauth_clients table with client_id valued “test” with bcrypted client_secret “test”.
You can also insert a users data, for example:
INSERT INTO `oauth_users` (`username`, `password`) VALUES ( 'test', '$2y$10$vbuy12RNSTJ.LHDdivegwu9dqkxh8h6OS4VoIX64HQGngAqUfcSJe' ),
Above sql insert data to oauth_users table with username valued “test” with bcrypted password “test”.
ZF-Oauth2 configuration
In apigility side, we can configure the “zf-oauth2” config, for example, as follows:
// config/autoload/global.php return [ // ... 'zf-oauth2' => [ 'access_lifetime' => 1800, 'options' => [ 'refresh_token_lifetime' => 604800, 'always_issue_new_refresh_token' => true, ], ], ];
The configuration above means we can have an access token lifetime in 1800 seconds, and we can re-issue the new token lifetime with existing “refresh_token” as far as the time range is not > 604800 seconds ( 1 week ). For example, we authenticate with data:
{ "grant_type": "password", "username": "test", "password": "test", "client_id": "test", "client_secret" : "test" }
have authenticated tokens data like the following:
{ "access_token": "8e4b0e5ddc874a6f1500514ef530dbea3976ae77", "expires_in": 1800, "token_type": "Bearer", "scope": null, "refresh_token": "d19b79cd376924409c14ee46e5230617482fb169" }
The “refresh_token” is the key here.
The client application
I assume you’re using Zend Framework 2/3 application for client side, which we can use Zend\Authentication\AuthenticationService
service. We can build custom Auth storage for it, eg:
namespace Application\Storage; use Zend\Authentication\Storage; class AuthStorage extends Storage\Session { public function __construct() { parent::__construct('app_client'); $sessionConfigOptions = [ 'use_cookies' => true, 'cookie_httponly' => true, 'gc_maxlifetime' => 1800, 'cookie_lifetime' => 1800, ]; $this->getSessionManager()->getConfig() ->setOptions($sessionConfigOptions); } public function rememberMe($time) { $this->getSessionManager()->rememberMe($time); } public function clear() { $this->getSessionManager()->forgetMe(); parent::clear(); } public function getSessionManager() { return $this->session->getManager(); } }
You can now create factory to build the AuthenticationService
service with the authstorage like I blog posted at Create ZF Client Authentication for Apigility Oauth with ApigilityConsumer post.
On authentication part, eg: AuthenticationController
, you can do:
$result = $this->authenticationService->authenticate(); if ($result->isValid()) { $storage = $this->authenticationService->getStorage(); // save next "expires" time to session $storage->write( $storage->read() + [ // it is better to use // api service to get the `oauth_access_tokens` real expires 'expires' => date('Y-m-d H:i:s', time() + $read['expires_in']) ] ); // for example, you have "rememberme" checkbox if (($rememberme = $request->getPost('rememberme')) == 1 ) { $storage->rememberMe(604800); $read = $storage->read(); $storage->write( compact('rememberme') + $read + [ // it is better to use // api service to get the `oauth_refresh_tokens` real expires 'refreshExpires' => date('Y-m-d H:i:s', time() + 604800) ] ); } // ... }
We are going to use “expires” as immediate check session lifetime hit the expires, and “refreshExpires” to check when it stil be able to re-issue new token.
In bootstrap, for example, in Application\Module::onBootstrap()
you can verify it to re-issue the token when access lifetime has hit.
namespace Application; use Zend\Authentication\AuthenticationService; use Zend\Mvc\MvcEvent; class Module { // ... public function onBootstrap(MvcEvent $e) { $services = $e->getApplication()->getServiceManager(); $storage = $services->get(AuthenticationService::class)->getStorage(); $read = $storage->read(); if (isset($read['access_token'])) { $timeFirst = strtotime($read['expires']); $currentTime = date('Y-m-d H:i:s'); $timeSecond = strtotime($currentTime); $counter = $timeFirst - $timeSecond; if (! empty($rememberme = $read['rememberme'])) { $storage->getSessionManager() ->getConfig() ->setStorageOption('gc_maxlifetime', 604800) ->setStorageOption('cookie_lifetime', 604800); if ($counter < 0 && $currentTime < $read['refreshExpires']) { // API CALL to apigility oauth uri // with grant_types = "refresh_token" and uses // refresh_token as the key to re-issue new token // // { // "grant_type" : "refresh_token", // "refresh_token" : $read['refresh_token'] // "client_id" : "test", // "client_secret" : "test" // } // $storage->write( [ // it is better to use // api service to get the `oauth_access_tokens` real expires 'expires' => date('Y-m-d H:i:s', time() + $read['expires_in']), // api service to get the `oauth_refresh_tokens` real expires 'refreshExpires' => date('Y-m-d H:i:s', time() + 604800), ] + compact('rememberme') + [ "access_token": "<new access token based on oauth call>", "expires_in": <new expire_in based on oauth call>, "token_type": "<new token_type based on oauth call>", "refresh_token": "<new refresh_token token based on oauth call>" ] + $read ); $read = $storage->read(); } } if ($currentTime > $read['expires'])) { // force clean up session $storage->clear(); } } } // ... }
Note
As commented in the codes sample above, in your real life application, it is beter to use real token expires instead of adding current time with expire_in time or manual fill refresh token lifetime. Do more automation yourself!
If you use Zend Framework 2/3 or Zend Expressive, you can try ApigilityConsumer for client module to consume api services. Enjoy 😉
Auto add _links property of HAL Resources into all api service in Apigility
If you want to have the _links property value to HAL Resource in apigility api service, for example:
{ "id": 1, "name": "Abdul Malik Ikhsan", "_links": { "self": { "href": "http://api.dev/user/1" } } }
you can do manually in every api service:
use ZF\ContentNegotiation\ViewModel; use ZF\Hal\Entity as HalEntity; use ZF\Hal\Link\Link; // ... public function userAction() { $halEntity = new HalEntity([ 'id' => 1, 'name' => 'Abdul Malik Ikhsan', ]); $link = $halEntity->getLinks(); $link->add(Link::factory( [ 'rel' => 'self', 'url' => $this->getRequest()->getUriString(), ] )); return new ViewModel([ 'payload' => $halEntity, ]); } // ...
You can eliminate that by apply via EventManager’s Shared Manager which attach to Zend\Mvc\Controller\AbstractActionController
on dispatch
event, like below:
namespace Application; use Zend\Mvc\Controller\AbstractActionController; use Zend\Mvc\MvcEvent; use ZF\Hal\Link\Link; use ZF\Hal\Plugin\Hal; class Module { public function onBootstrap(MvcEvent $e) { $app = $e->getApplication(); $sharedEvm = $app->getEventManager()->getSharedManager(); $sharedEvm->attach(AbstractActionController::class, 'dispatch', function($event) use ($sharedEvm) { $uri = $event->getRequest()->getUriString(); $sharedEvm->attach(Hal::class, 'renderEntity', function($event) use ($uri) { $event->getParam('entity') ->getLinks() ->add(Link::factory( [ 'rel' => 'self', 'url' => $uri, ] )); }); }, 100 ); } public function getConfig() { /* */ } }
On above code, we attach ZF\Hal\Plugin\Hal
on renderEntity
event which get the ZF\Hal\Entity
object from ZF\ContentNegotiation\ViewModel
payload property, and apply Link into it via ZF\Hal\Link\Link::factory()
.
Now, you can eliminate unneeded repetitive codes in all every api services.
Done 😉
Using Direct ArrayObject instance as ObjectPrototype in Zend\Db
When creating a table model for ZF2 or ZF3 application with Zend\DB, direct ArrayObject instance can be usefull as ResultSet object prototype. We can no longer need to create an individual class that has getArrayCopy()
or exchangeArray()
for data transformation.
For example, we have the following table model:
<?php namespace Application\Model; use Zend\Db\TableGateway\AbstractTableGateway; class CountryTable { public static $table = 'country'; private $tableGateway; public function __construct(AbstractTableGateway $tableGateway) { $this->tableGateway = $tableGateway; } public function getCountriesInAsia() { $select = $this->tableGateway->getSql()->select(); $select->where([ 'continent' => 'ASIA' ]); return $this->tableGateway->selectWith($select); } }
The ArrayObject
usage we can use is:
new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
So, we can build the factory for above table model as follows:
<?php namespace Application\Model; use ArrayObject; use Interop\Container\ContainerInterface; use Zend\ServiceManager\Factory\FactoryInterface; use Zend\Db\ResultSet\HydratingResultSet; use Zend\Db\TableGateway\TableGateway; class CountryTableFactory implements FactoryInterface { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { $resultSetPrototype = new HydratingResultSet(); $resultSetPrototype->setObjectPrototype( new ArrayObject([], ArrayObject::ARRAY_AS_PROPS) ); $tableGateway = new TableGateway( CountryTable::$table, $container->get('Zend\Db\Adapter\Adapter'), null, $resultSetPrototype ); return new CountryTable($tableGateway); } }
and register it into service_manager under factories:
<?php namespace Application; return [ // ... 'service_manager' => [ 'factories' => [ Model\CountryTable::class => Model\CountryTableFactory:class, ], ], ];
When retrieving the data, you can do the followings:
use Application\Model\CountryTable; $countryTable = $container->get(CountryTable::class); $countriesInAsia = $countryTable->getCountriesInAsia(); foreach ($countriesInAsia as $key => $row) { // dump a copy of the ArrayObject var_dump($arrayCopy = $row->getArrayCopy()); // echoed column as property echo $row->name; // with value "INA" echo $row->iso; // with value "ID" echo $row->continent; // with value "ASIA" // echoed as array with provided key echo $row['name']; // with value "INA" echo $row['iso']; // with value "ID" echo $row['continent']; // with value "ASIA" // modify data via exhangeArray $row->exchangeArray(array_merge( $arrayCopy, [ 'name' => 'INDONESIA', ] )); // or modify its data by its property $row->name = 'INDONESIA'; // or modify its data by its index array $row['name'] = 'INDONESIA'; echo $row->name; // now has value "INDONESIA" echo $row['name']; // now has value "INDONESIA" }
Bonus:
To avoid repetitive creating factory class for each table model, we can create an abstract factory for it:
<?php namespace Application\Model; use ArrayObject; use Interop\Container\ContainerInterface; use Zend\Db\ResultSet\HydratingResultSet; use Zend\Db\TableGateway\TableGateway; use Zend\ServiceManager\Factory\AbstractFactoryInterface; class CommonModelTableFactory implements AbstractFactoryInterface { public function canCreate(ContainerInterface $container, $requestedName) { return ((substr($requestedName, -5) === 'Table') && class_exists($requestedName)); } public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { $tableModel = '\\' . $requestedName; $resultSetPrototype = new HydratingResultSet(); $resultSetPrototype->setObjectPrototype( new ArrayObject([], ArrayObject::ARRAY_AS_PROPS) ); $tableGateway = new TableGateway( $tableModel::$table, $container->get('Zend\Db\Adapter\Adapter'), null, $resultSetPrototype ); return new $tableModel($tableGateway); } }
So, now, we can have 1 abstract factory for all table model services:
<?php namespace Application; return [ // ... 'service_manager' => [ 'abstract_factories' => [ Model\CommonModelTableFactory:class, ], ], ];
That’s it 😉
Create ZF Client Authentication for Apigility Oauth with ApigilityConsumer
If you have Apigility as API builder in API side, and client app that consume it using Zend Framework 2/3 or ZF Expressive, you can create an authentication from the client application that call oauth in apigility side.
Zend\Authentication
has AbstractAdapter that you can extends to create custom adapter for its need. Let’s assume the applications are like the following diagram:
[CLIENT - A ZF Application] [API - An Apigility Application] | | AuthController ZF\MvcAuth\Authentication\OAuth2Adapter | | | authenticateAction() | | ------------------------------------> | | identity json | | <------------------------------------ |
On oauth result call, you may get the following result:
{ "access_token": "8e4b0e5ddc874a6f1500514ef530dbea3976ae77", "expires_in": 3600, "token_type": "Bearer", "scope": null, "refresh_token": "d19b79cd376924409c14ee46e5230617482fb169" }
The ApigilityConsumer
ApigilityConsumer is a ZF2/ZF3 Apigility Client module (can also be used in ZF Expressive) to consume Apigility API Services.
You can install by run composer command:
composer require samsonasik/apigility-consumer
For full configurations and features, you can read at its README, for this post’s need, you can do something like this:
<?php // config/autoload/apigility-consumer.local.php return [ 'apigility-consumer' => [ // your apigility host url 'api-host-url' => 'https://your.apigilty.api.host', // your apigility oauth setting 'oauth' => [ 'grant_type' => 'password', 'client_id' => 'your client id', 'client_secret' => 'your client secret', ], ], ];
and register the module into config/application.config.php
or config/modules.config.php
:
<?php // config/application.config.php or config/modules.config.php return [ 'ApigilityConsumer', // <-- register here 'Application', ],
Create Adapter
You need to extends Zend\Authentication\Adapter\AbstractAdapter
and implements Zend\Authentication\Adapter\AdapterInterface
. So, You can have the class:
<?php namespace Application\Adapter; use ApigilityConsumer\Service\ClientAuthService; use Zend\Authentication\Adapter\AbstractAdapter; use Zend\Authentication\Adapter\AdapterInterface; use Zend\Authentication\Result; class ApigilityAuthenticationAdapter extends AbstractAdapter implements AdapterInterface { /** * @var ClientAuthService */ private $clientAuthService; /** * @param ClientAuthService $clientAuthService */ public function __construct(ClientAuthService $clientAuthService) { $this->clientAuthService = $clientAuthService; } /** * @return Result */ public function authenticate() { $clientResult = $this->clientAuthService->callAPI( [ // your oauth registered route segment in apigility. 'api-route-segment' => '/oauth', 'form-data' => [ 'username' => $this->getIdentity(), 'password' => $this->getCredential(), ], 'form-request-method' => 'POST', ] ); if (! $clientResult->success) { return new Result(Result::FAILURE, null, $clientResult::$messages); } return new Result(RESULT::SUCCESS, $clientResult->data); } }
Your can now build a factory from it:
<?php namespace Application\Adapter; use ApigilityConsumer\Service\ClientAuthService; class ApigilityAuthenticationAdapterFactory { public function __invoke($container) { return new ApigilityAuthenticationAdapter( $container->get(ClientAuthService::class) ); } }
You can then register at service_manager
:
<?php // module/Application/config/module.config.php namespace Application; 'service_manager' => [ 'factories' => [ // ... Adapter\ApigilityAuthenticationAdapter::class => Adapter\ApigilityAuthenticationAdapterFactory::class, ], ],
For ZF Expressive, you can register under ‘dependencies’ key.
Set Adapter into AuthenticationService
You need to set authentication service’s adapter with defined adapter above with factory:
<?php namespace Application\Factory; use Application\Adapter\ApigilityAuthenticationAdapter; use Zend\Authentication\AuthenticationService; use Zend\Authentication\Storage\Session; class AuthenticationServiceFactory { public function __invoke($container) { $adapter = $container->get(ApigilityAuthenticationAdapter::class); return new AuthenticationService( new Session(), // or your own storage implementing Zend\Authentication\Storage\StorageInterface $adapter ); } }
You can then register also at service_manager
:
<?php // module/Application/config/module.config.php namespace Application; use Zend\Authentication\AuthenticationService; 'service_manager' => [ 'factories' => [ // ... AuthenticationService::class => Factory\AuthenticationServiceFactory::class, ], ],
For ZF Expressive, you can register under ‘dependencies’ key.
The AuthController::authenticate()
I assume that you already inject controler with login form, use “username” and “password” as field names, and fill the data, so, your AuthController::authenticate()
can be like the following:
<?php namespace Application\Controller; use Application\Form\LoginForm; use Zend\Authentication\AuthenticationService; class AuthController { public function __construct( AuthenticationService $authenticationService, LoginForm $loginForm, ) { /* ...*/ } public function authenticateAction() { /* * check request and form validity here */ $formData = $this->loginForm->getData(); $this->authenticationService->getAdapter() ->setIdentity($formData['username']) ->setCredential($formData['password']); $result = $this->authenticationService->authenticate(); if (!$result->isValid()) { /** * For security reason, you should not show user the reason of failure, * However, if it actually needed for specific purpose, you can pull by call: * * $result->getMessages(); * */ return $this->redirect()->toRoute('/auth'); } return $this->redirect()->toRoute('/account'); } }
For ZF Expressive, you can create routed Authentication middleware.
That’s it, you’re now have successfully created a client authentication for your ZF2/ZF3 or ZF Expressive application that consume Apigility oauth.
leave a comment