View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFPROP:Proposal Zone Template}

{zone-data:component-name}
Zend_Acl dynamic loading
{zone-data}

{zone-data:proposer-list}
[Aldemar Bernal|mailto:aldemar.bernal@gmail.com]
{zone-data}

{zone-data:liaison}
[~ralph]
{zone-data}

{zone-data:revision}
1.0 - 17 November 2007: Proporsal created.
1.1 - 21 January 2008: UCs Created.
{zone-data}

{zone-data:overview}
Zend_Acl dynamic loading isn't meant to be a replacement of the current good implementation of Zend_Acl, it is just an update proporsal based on get better performance in the component in medium/large ACL implementations as well as a base to allow the load of persistenced ACLs (from a database or a file or ..).
{zone-data}

{zone-data:references}
* [Zend_Acl online documentation|http://framework.zend.com/manual/en/zend.acl.html]
* [Zend_Acl API|http://framework.zend.com/apidoc/core/]
{zone-data}

{zone-data:requirements}
* This update *will* boost performance in medium/large Acl Implementations.
* This update *will* allow the component to load only the needed Roles/Resources.
* This update *will* allow the component to load data from datasources.
{zone-data}

{zone-data:dependencies}
* Zend_Acl
* Zend_Acl_Role
* Zend_Acl_Resource
* Zend_Acl_Exception
{zone-data}

{zone-data:operation}
This update doesn't make any change of current Zend_Acl implementations, it includes only a new method called registerLoader in the Zend_Acl component and a new abstract class called Zend_Acl_Loader_Abstract. The main goal of this new feature is to be able to load dynamically Roles and Resources using an instance of the abstract class. Here an example is given to make clearer the new functionality and its use:

My/AclLoader.php:
{code}
class My_AclLoader extends Zend_Acl_Loader_Abstract
{
protected function _init(Zend_Acl $acl)
{
/** This method is called after you registered your Loader, so you can load default configuration here. */
$this->getAcl()->addRole(new Zend_Acl_Role('admin'));

/**
* This will reroute the method call, if 'configurationstuff' is not found in the Acl, the Loader would
* by default try to call 'setupConfigurationstuffResource'
**/
$this->setResourceRoute('configurationstuff', '_loadConfigResources');
}

protected function _loadGuestRole(Zend_Acl $acl)
{
/** This method is called when the guest Role isn't found in the Zend_Acl Roles */
$this->getAcl()->addRole(new Zend_Acl_Role('guest'));
}

protected function _loadCartResource(Zend_Acl $acl)
{
/** This method is called when the cart Resource isn't found in the Zend_Acl Resources */
$this->getAcl()->add(new Zend_Acl_Resource('cart'));

/** Setup privileges */
$this->getAcl()->allow('guest', 'cart', 'index')
->allow('guest', 'cart', 'additem')
->allow('guest', 'cart', 'removeitem');
}

protected function _loadConfigResources(Zend_Acl $acl)
{
/** This method is called when the configurationstuff Resource isn't found in the Zend_Acl Resources */
$this->getAcl()->add(new Zend_Acl_Resource('configurationstuff'))
->add(new Zend_Acl_Resource('orders'));

/** This will do recursive call to setupNewsResource */
$this->getAcl()->allow('admin', 'news', 'add');
}

protected function _loadNewsResource(Zend_Acl $acl)
{
/** This method was called by loadConfigResources and not from the bootstrap file */
$this->getAcl()->add(new Zend_Acl_Resource('news'));
}
}
{code}

At bootstrap:
{code}
/** Create Acl */
$acl = new Zend_Acl();

/** Register Loader */
$acl->registerLoader(new My_AclLoader());

/**
* After registered, the method _init of My_AclLoader will
* be invoked what will load 'admin' role.
*
* Ask for any other Zend_Acl Role/Resource would throw an
* exception since no Role/Resource have been created (only 'admin' Role).
* But since we registered My_AclLoaded, Zend_Acl will use that
* class to find a way to load them.
**/

/**
* After the following call, the roles 'guest' as well as 'cart'
* resource will be loaded in the Zend_Acl component.
**/
echo $acl->isAllowed('guest', 'cart', 'additem');

/**
* After the following call, also the resources 'configurationstuff', 'news' and 'orders'
* will be loaded into the Zend_Acl object.
**/
$acl->allow('admin', 'configurationstuff');

/**
* Finally, if a role/resource is required but it is not
* defined in the Zend_Acl object either in the Loader class
* the default exception will be thrown
**/
$acl->deny('unknown', 'someresource');
{code}


So, as you could see in the example above, the Loader class will allow to avoid the problem of loading all ACL at once in bootstrap, and will help to only load the data needed, the results are better response times and less memory usage.
{zone-data}

{zone-data:milestones}
* Milestone 1: \[DONE\] Create ZF Wiki info.
* Milestone 2: \[DONE\] Working prototype and some examples. *UCs implemented click [here|http://framework.zend.com/wiki/download/attachments/39164/AclDynamic.zip] to download.*
* Milestone 3: Working prototype checked into the incubator supporting use cases.
* Milestone 4: Unit tests exist, work, and are checked into SVN.
* Milestone 5: Initial documentation exists.
{zone-data}

{zone-data:class-list}
* Zend_Acl
* Zend_Acl_Loader_Abstract
{zone-data}

{zone-data:use-cases}
I recommend you to download [this|http://framework.zend.com/wiki/download/attachments/39164/AclDynamic.zip] file where you can find all UCs implemented for better comprehension of the component use.
{composition-setup}
{deck:id=Usecases}
{card:label=UC-01 - Using _init}
This example shows the simple use of the _init() function, this function is called after the loader is registered in the Acl component.

My/AclLoader.php
{code}
<?php
require_once 'Zend/Acl/Loader/Abstract.php';

class My_AclLoader extends Zend_Acl_Loader_Abstract
{
protected function _init()
{
require_once 'Zend/Acl/Role.php';
require_once 'Zend/Acl/Resource.php';
$this->getAcl()->addRole(new Zend_Acl_Role('president'))
->addRole(new Zend_Acl_Role('commonCitizen'))
->add(new Zend_Acl_Resource('secretArea'))
->allow('president', 'secretArea');
}
}
{code}
index.php
{code}
<?php
/** Creating the ACL object */
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();

/** Creating and registering the My_AclLoader instance */
require_once 'My/AclLoader.php';
$acl->registerLoader(new My_AclLoader());

/** Doing some tests */
echo 'Common citizen has access to secret area? ' . ($acl->isAllowed('commonCitizen', 'secretArea') ? 'yes' : 'no') . '<br />';
echo 'President has access to secret area?' . ($acl->isAllowed('president', 'secretArea') ? 'yes' : 'no') . '<br />';
{code}
Expected result:
{code}
Common citizen has access to secret area? no
President has access to secret area? yes
{code}
{card}
{card:label=UC-02 - Autoloading functions}
This example shows the use of functions naming conventions in the loader (_load*Role() and _load*Resource()).

My/AclLoader.php
{code}
<?php
require_once 'Zend/Acl/Loader/Abstract.php';

class My_AclLoader extends Zend_Acl_Loader_Abstract
{
protected function _init()
{
require_once 'Zend/Acl/Role.php';
$this->getAcl()->addRole(new Zend_Acl_Role('user'));
}

protected function _loadBossRole()
{
require_once 'Zend/Acl/Role.php';
$this->getAcl()->addRole(new Zend_Acl_Role('boss'), 'user')
->allow('boss', 'report');
}

protected function _loadSalesmanRole()
{
require_once 'Zend/Acl/Role.php';
$this->getAcl()->addRole(new Zend_Acl_Role('salesman'), 'user')
->allow('salesman', 'sale');
}

protected function _loadReportResource()
{
require_once 'Zend/Acl/Resource.php';
$this->getAcl()->add(new Zend_Acl_Resource('report'))
->add(new Zend_Acl_Resource('dailyReport'), 'report')
->add(new Zend_Acl_Resource('weeklyReport'), 'report')
->add(new Zend_Acl_Resource('monthlyReport'), 'report');
}

protected function _loadSaleResource()
{
require_once 'Zend/Acl/Resource.php';
$this->getAcl()->add(new Zend_Acl_Resource('sale'))
->add(new Zend_Acl_Resource('registerSale'), 'sale');
}
}
{code}
index.php
{code}
<?php
/** Creating the ACL object */
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();

/** Creating and registering the My_AclLoader instance */
require_once 'My/AclLoader.php';
$acl->registerLoader(new My_AclLoader());

/** Once My_AclLoader is registered in $acl, the role 'user' is loaded */

/** Doing some tests */
/** When the 'salesman' role is queried, it is loaded in the acl and it also loads the 'sale' resource */
echo 'The salesman can register sales in the system? ' . ($acl->isAllowed('salesman', 'registerSale') ? 'yes' : 'no') . '<br />';

/** When the 'report' report is queried, it is loaded in the acl and it also loads 'dailyReport', 'weeklyReport' and 'monthlyReport' resources */
echo 'The salesman can view the reports? ' . ($acl->isAllowed('salesman', 'report') ? 'yes' : 'no') . '<br />';

/** When the 'boss' role is queried, it is loaded in the acl */
echo 'Does the boss? ' . ($acl->isAllowed('boss', 'report') ? 'yes' : 'no') . '<br />';
{code}
Expected result:
{code}
The salesman can register sales in the system? yes
The salesman can view the reports? no
Does the boss? yes
{code}
{card}
{card:label=UC-03 - Custom function routes}
This example shows how to redirect the function defaults calls, this can be useful when the name of a role or a resource is long and you don't want to have a long named function.

My/AclLoader.php
{code}
<?php
require_once 'Zend/Acl/Loader/Abstract.php';

class My_AclLoader extends Zend_Acl_Loader_Abstract
{
protected function _init()
{
$this->setRoleRoute('somelongnameduser', '_loadThatUser');
$this->setResourceRoute('somelongnamedarea', '_loadThatArea');
}

protected function _loadThatUser()
{
require_once 'Zend/Acl/Role.php';
$this->getAcl()->addRole(new Zend_Acl_Role('somelongnameduser'));
}

protected function _loadThatArea()
{
require_once 'Zend/Acl/Resource.php';
$this->getAcl()->add(new Zend_Acl_Resource('somelongnamedarea'))
->allow('somelongnameduser', 'somelongnamedarea');
}
}
{code}
index.php
{code}
<?php
/** Creating the ACL object */
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();

/** Creating and registering the My_AclLoader instance */
require_once 'My/AclLoader.php';
$acl->registerLoader(new My_AclLoader());

/** Doing some tests */
echo 'User somelongnameduser has access to somelongnamedarea? ' . ($acl->isAllowed('somelongnameduser', 'somelongnamedarea') ? 'yes' : 'no') . '<br />';
{code}
Expected result:
{code}
User somelongnameduser has access to somelongnamedarea? yes
{code}
{card}
{card:label=UC-04 - Advanced Use: Loading from datasources}
This example shows the most complex use of the loader component. Sometimes you need to store permissions in file or database and to be able to load those datasources into Acl can be time consuming, this component helps you to do it easily.

Here is a photo gallery example, the following is the data used:

* Users
** Matthew (matt)
** Mark (mark)
** Luke (luke)
** John (johnny)

* Photos
** 1 (created by matt, public, deny access to luke and johnny)
** 2 (created by mark, public, deny access to matt)
** 3 (created by luke, public, deny access to mark)
** 4 (created by johnny, private, allow access to luke)

My/AclLoader.php
{code}
<?php
require_once 'Zend/Acl/Loader/Abstract.php';

class My_AclLoader extends Zend_Acl_Loader_Abstract
{
protected function _loadRole($role)
{
require_once 'User.php';
$user = new User($role);

require_once 'Zend/Acl/Role.php';
$this->getAcl()->addRole(new Zend_Acl_Role($user->userName));
}

protected function _loadResource($resource)
{
require_once 'Photo.php';
$photo = new Photo($resource);

require_once 'Zend/Acl/Resource.php';
$this->getAcl()->add(new Zend_Acl_Resource($resource))
->allow($photo->postBy, $resource);

if (!$photo->privatePhoto) {
$this->getAcl()->allow(null, $resource);
}

require_once 'PhotoUserPermission.php';
$photoPermissions = PhotoUserPermission::getPermissionsByPhoto($resource);

foreach ($photoPermissions as $userName => $permission) {
if ($permission) {
$this->getAcl()->allow($userName, $resource);
} else {
$this->getAcl()->deny($userName, $resource);
}
}
}
}
{code}
index.php
{code}
<?php
/** Creating the ACL object */
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();

/** Creating and registering the My_AclLoader instance */
require_once 'My/AclLoader.php';
$acl->registerLoader(new My_AclLoader());

/** Doing some tests */
echo 'Can Luke view photo #1? ' . ($acl->isAllowed('luke', '1') ? 'yes' : 'no') . '<br />';
echo 'Can Matthew view photo #1? ' . ($acl->isAllowed('matt', '1') ? 'yes' : 'no') . '<br />';
echo 'Can Matthew view photo #2? ' . ($acl->isAllowed('matt', '2') ? 'yes' : 'no') . '<br />';
echo 'Can Mark view photo #3? ' . ($acl->isAllowed('mark', '3') ? 'yes' : 'no') . '<br />';
echo 'Can John view photo #4? ' . ($acl->isAllowed('johnny', '4') ? 'yes' : 'no') . '<br />';
echo 'Can Luke view photo #4? ' . ($acl->isAllowed('luke', '4') ? 'yes' : 'no') . '<br />';
echo 'Can Mark view photo #4? ' . ($acl->isAllowed('mark', '4') ? 'yes' : 'no') . '<br />';
{code}
Expected result:
{code}
Can Luke view photo #1? no
Can Matthew view photo #1? yes
Can Matthew view photo #2? no
Can Mark view photo #3? no
Can John view photo #4? yes
Can Luke view photo #4? yes
Can Mark view photo #4? no
{code}
{card}
{deck}
{zone-data}

{zone-data:skeletons}
{composition-setup}
{deck:id=Classes}
{card:label=Zend_Acl}
{code}
class Zend_Acl
{
/**
* @var Zend_Acl_Loader_Abstract
**/
protected $_loader;

/**
* Registers the loader to use
*
* @param Zend_Acl_Loader_Abstract $loader
* @return void
**/
public function registerLoader(Zend_Acl_Loader_Abstract $loader)
{
}

/**
* Loads the specified roles if not loaded before
*
* @param string|array $roles
* @return void
**/
protected function _invokeRolesLoader($roles = null)
{
}

/**
* Loads the specified resources if not loaded before
*
* @param string|array $resources
* @return void
**/
protected function _invokeResourcesLoader($resources = null)
{
}

/**
* Checks if the resource is already loaded into the Acl
*
* @param mixed $resource
* @return bool
*/
public function isLoaded($resource)
{
}

/**
* Checks if the role is already loaded into the Acl
*
* @param mixed $role
* @return bool
*/
public function isRoleLoaded($role)
{
}

...same Zend_Acl functions
}
{code}
{card}
{card:label=Zend_Acl_Loader_Abstract}
{code}
abstract class Zend_Acl_Loader_Abstract
{
/**
* @var Zend_Acl
**/
protected $_acl;

/**
* @var array
**/
protected $_rolesRoutes;

/**
* @var array
**/
protected $_resourcesRoutes;

/**
* @var array
**/
protected $_loadedRoles;

/**
* @var array
**/
protected $_loadedResources;

/**
* Initializes the component
*
* @param Zend_Acl $acl
* @return void
**/
public function init(Zend_Acl $acl)
{
}

/**
* Return the Acl object
*
* @return Zion_Acl
*/
public function getAcl()
{
}

/**
* Loads the specified role if not loaded before
*
* @param string $role
* @return void
**/
public function loadRole($role)
{
}

/**
* Loads the specified resource if not loaded before
*
* @param string $resource
* @return void
**/
public function loadResource($resource)
{
}

/**
* Returns the method route for a role
*
* @param string $role
* @return string
**/
public function getRoleRoute($role)
{
}

/**
* Sets a custom method route for a role
*
* @param string $role
* @param string $methodName
* @return void
**/
public function setRoleRoute($role, $methodName)
{
}

/**
* Returns the method route for a resource
*
* @param string $resource
* @return string
**/
public function getResourceRoute($resource)
{
}

/**
* Sets a custom method route for a resource
*
* @param string $resource
* @param string $methodName
* @return void
**/
public function setResourceRoute($resource, $methodName)
{
}
}
{code}
{card}
{deck}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>