From e2a804aa0d7beb9517abbce4917ad6da7aa58e29 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Wed, 25 Jan 2023 15:29:18 -0600 Subject: [PATCH 1/4] Speed improvements for Acl::setRule * Adding $resourcesById member variable so that we don't have to walk tree each time setRule is called * removed unnecessary array_merge from getChildResources() and where we call getChildResources() Signed-off-by: Jacob Brown --- src/Acl.php | 69 +++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/Acl.php b/src/Acl.php index 0409297..61332dc 100644 --- a/src/Acl.php +++ b/src/Acl.php @@ -58,6 +58,14 @@ class Acl implements AclInterface */ protected $resources = []; + /** + * Resources by resourceId plus a null element + * Used to speed up setRule() + * + * @var array + */ + protected $resourcesById = [null]; + /** @var Role\RoleInterface|null */ protected $isAllowedRole; @@ -273,7 +281,7 @@ public function addResource($resource, $parent = null) 'parent' => $resourceParent, 'children' => [], ]; - + $this->resourcesById[$resourceId] = $resource; return $this; } @@ -416,7 +424,7 @@ public function removeResourceAll() } $this->resources = []; - + $this->resourcesById = [null]; return $this; } @@ -563,35 +571,29 @@ public function setRule( } } unset($rolesTemp); - - // ensure that all specified Resources exist; normalize input to array of Resource objects or null - if (! is_array($resources)) { - if (null === $resources && $this->resources) { - $resources = array_keys($this->resources); - // Passing a null resource; make sure "global" permission is also set! - if (! in_array(null, $resources)) { - array_unshift($resources, null); - } - } else { + if (null === $resources && $this->resources) { + $resources = $this->resourcesById; + } else { + // ensure that all specified Resources exist; normalize input to array of Resource objects or null + if (!is_array($resources)) { $resources = [$resources]; + } elseif (!$resources) { + $resources = [null]; } - } elseif (! $resources) { - $resources = [null]; - } - $resourcesTemp = $resources; - $resources = []; - foreach ($resourcesTemp as $resource) { - if (null !== $resource) { - $resourceObj = $this->getResource($resource); - $resourceId = $resourceObj->getResourceId(); - $children = $this->getChildResources($resourceObj); - $resources = array_merge($resources, $children); - $resources[$resourceId] = $resourceObj; - } else { - $resources[] = null; + $resourcesTemp = $resources; + $resources = []; + foreach ($resourcesTemp as $resource) { + if (null !== $resource) { + $resourceObj = $this->getResource($resource); + $resourceId = $resourceObj->getResourceId(); + $this->getChildResources($resourceObj, $resources); + $resources[$resourceId] = $resourceObj; + } else { + $resources[] = null; + } } + unset($resourcesTemp); } - unset($resourcesTemp); // normalize privileges to array if (null === $privileges) { @@ -677,19 +679,14 @@ public function setRule( * * @return ResourceInterface[] */ - protected function getChildResources(ResourceInterface $resource) + protected function getChildResources(ResourceInterface $resource, &$return = []) { - $return = []; - $id = $resource->getResourceId(); - + $id = $resource->getResourceId(); $children = $this->resources[$id]['children']; foreach ($children as $child) { - $childReturn = $this->getChildResources($child); - $childReturn[$child->getResourceId()] = $child; - - $return = array_merge($return, $childReturn); + $this->getChildResources($child, $return); + $return[$child->getResourceId()] = $child; } - return $return; } From 15542d02767eb196f5703406c0d7e0960fa6b083 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Mon, 30 Jan 2023 20:46:07 -0600 Subject: [PATCH 2/4] fixing static test failures for pull request #44 Signed-off-by: Jacob Brown --- src/Acl.php | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/Acl.php b/src/Acl.php index 61332dc..3365034 100644 --- a/src/Acl.php +++ b/src/Acl.php @@ -13,10 +13,7 @@ use function array_key_exists; use function array_keys; -use function array_merge; use function array_pop; -use function array_unshift; -use function in_array; use function is_array; use function is_string; use function sprintf; @@ -62,7 +59,7 @@ class Acl implements AclInterface * Resources by resourceId plus a null element * Used to speed up setRule() * - * @var array + * @var (ResourceInterface|null)[] */ protected $resourcesById = [null]; @@ -276,7 +273,7 @@ public function addResource($resource, $parent = null) $this->resources[$resourceParentId]['children'][$resourceId] = $resource; } - $this->resources[$resourceId] = [ + $this->resources[$resourceId] = [ 'instance' => $resource, 'parent' => $resourceParent, 'children' => [], @@ -423,7 +420,7 @@ public function removeResourceAll() unset($this->rules['byResourceId'][$resourceId]); } - $this->resources = []; + $this->resources = []; $this->resourcesById = [null]; return $this; } @@ -571,28 +568,28 @@ public function setRule( } } unset($rolesTemp); + /** var (ResourceInterface|null)[] */ + $resourcesArray = []; if (null === $resources && $this->resources) { - $resources = $this->resourcesById; + $resourcesArray = $this->resourcesById; } else { // ensure that all specified Resources exist; normalize input to array of Resource objects or null - if (!is_array($resources)) { + if (! is_array($resources)) { $resources = [$resources]; - } elseif (!$resources) { + } elseif (! $resources) { $resources = [null]; } - $resourcesTemp = $resources; - $resources = []; - foreach ($resourcesTemp as $resource) { + foreach ($resources as $resource) { if (null !== $resource) { $resourceObj = $this->getResource($resource); - $resourceId = $resourceObj->getResourceId(); - $this->getChildResources($resourceObj, $resources); - $resources[$resourceId] = $resourceObj; + $resourceId = $resourceObj->getResourceId(); + $this->getChildResources($resourceObj, $resourcesArray); + $resourcesArray[$resourceId] = $resourceObj; } else { - $resources[] = null; + $resourcesArray[] = null; } } - unset($resourcesTemp); + unset($resources); } // normalize privileges to array @@ -605,7 +602,7 @@ public function setRule( switch ($operation) { // add to the rules case self::OP_ADD: - foreach ($resources as $resource) { + foreach ($resourcesArray as $resource) { foreach ($roles as $role) { $rules =& $this->getRules($resource, $role, true); if (! $privileges) { @@ -626,7 +623,7 @@ public function setRule( // remove from the rules case self::OP_REMOVE: - foreach ($resources as $resource) { + foreach ($resourcesArray as $resource) { foreach ($roles as $role) { $rules =& $this->getRules($resource, $role); if (null === $rules) { @@ -677,11 +674,12 @@ public function setRule( /** * Returns all child resources from the given resource. * - * @return ResourceInterface[] + * @param (ResourceInterface|null)[] $return + * @return (ResourceInterface|null)[] */ - protected function getChildResources(ResourceInterface $resource, &$return = []) + protected function getChildResources(ResourceInterface $resource, array &$return = []) { - $id = $resource->getResourceId(); + $id = $resource->getResourceId(); $children = $this->resources[$id]['children']; foreach ($children as $child) { $this->getChildResources($child, $return); From efff891f60061e98dd0c5c586e3242b2715f789c Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Tue, 31 Jan 2023 15:57:00 -0600 Subject: [PATCH 3/4] Adding phpbench benchmark for Acl Signed-off-by: Jacob Brown --- benchmarks/AclBench.php | 68 ++++ composer.json | 1 + composer.lock | 762 +++++++++++++++++++++++++++++++++++++++- phpbench.json | 6 + 4 files changed, 826 insertions(+), 11 deletions(-) create mode 100644 benchmarks/AclBench.php create mode 100644 phpbench.json diff --git a/benchmarks/AclBench.php b/benchmarks/AclBench.php new file mode 100644 index 0000000..8f92d3a --- /dev/null +++ b/benchmarks/AclBench.php @@ -0,0 +1,68 @@ +acl = new Acl(); + } + + public function benchSetService(): void + { + $acl = new Acl(); + $role = new GenericRole('A'); + $acl->addRole($role); + for ($i = 0; $i < self::NUM_ROLES_WITHOUT_PARENT; $i++) { + $acl->addRole((string) $i); + } + for ($i = 0; $i < self::NUM_ROLES_WITH_PARENT; $i++) { + $acl->addRole((string) ($i + self::NUM_ROLES_WITHOUT_PARENT), $i % self::NUM_ROLES_WITHOUT_PARENT); + } + for ($i = 0; $i < self::NUM_RESOURCES; $i++) { + if ($i === 0) { + $parent = null; + } else { + $parent = (string) (intdiv($i * 3, 2) % $i); + } + $acl->addResource((string) $i, $parent); + } + for ($i = 0; $i < self::NUM_ALLOW_CALLED; $i++) { + $role = (string) (intdiv($i * 3, 2) % (self::NUM_ROLES_WITHOUT_PARENT + self::NUM_ROLES_WITH_PARENT)); + if ($i %2 ) { + $resource = (string) (intdiv($i * 7, 5) % self::NUM_RESOURCES); + } else { + $resource = null; + } + $acl->allow($role, $resource); + } + for ($i = 0; $i < self::NUM_DENY_CALLED; $i++) { + $role = (string) (intdiv($i * 13, 11) % (self::NUM_ROLES_WITHOUT_PARENT + self::NUM_ROLES_WITH_PARENT)); + if ($i %2 ) { + $resource = (string) (intdiv($i * 19, 17) % self::NUM_RESOURCES); + } else { + $resource = null; + } + $acl->allow($role, $resource); + } + } +} diff --git a/composer.json b/composer.json index 9dcbcb2..3174317 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "require-dev": { "laminas/laminas-coding-standard": "~2.5.0", "laminas/laminas-servicemanager": "^3.19", + "phpbench/phpbench": "^1.2", "phpunit/phpunit": "^9.5.26", "psalm/plugin-phpunit": "^0.18.0", "vimeo/psalm": "^5.0" diff --git a/composer.lock b/composer.lock index cf35a01..ebc26da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2766e7b22f4b9618d8c5953fa8ed24b2", + "content-hash": "33dc66135c7bf948a3ca88cce97dda62", "packages": [], "packages-dev": [ { @@ -576,6 +576,125 @@ }, "time": "2019-12-04T15:06:13+00:00" }, + { + "name": "doctrine/annotations", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "d02c9f3742044e17d5fa8d28d8402a2d95c33302" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/d02c9f3742044e17d5fa8d28d8402a2d95c33302", + "reference": "d02c9f3742044e17d5fa8d28d8402a2d95c33302", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.0" + }, + "time": "2022-12-19T18:17:20+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" + }, { "name": "doctrine/instantiator", "version": "1.5.0", @@ -646,6 +765,84 @@ ], "time": "2022-12-30T00:15:36+00:00" }, + { + "name": "doctrine/lexer", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-12-14T08:49:07+00:00" + }, { "name": "felixfbecker/advanced-json-rpc", "version": "v3.2.1", @@ -1290,6 +1487,198 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpbench/container", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.1" + }, + "time": "2022-01-25T10:17:35+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.2" + }, + "time": "2021-09-24T15:26:07+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.8", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "3f7b3c200f86727de7a14bde94adb68a88e1bafc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3f7b3c200f86727de7a14bde94adb68a88e1bafc", + "reference": "3f7b3c200f86727de7a14bde94adb68a88e1bafc", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.13 || ^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0", + "webmozart/glob": "^4.6" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^1.0", + "phpspec/prophecy": "^1.12", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2 || ^6.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.8" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2023-01-14T13:08:42+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -1951,15 +2340,60 @@ "weirdan/codeception-psalm-module": "^0.11.0", "weirdan/prophecy-shim": "^1.0 || ^2.0" }, - "type": "psalm-plugin", + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.18.4" + }, + "time": "2022-12-03T07:47:07+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", "extra": { - "psalm": { - "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + "branch-alias": { + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psalm\\PhpUnitPlugin\\": "src" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1968,16 +2402,20 @@ ], "authors": [ { - "name": "Matt Brown", - "email": "github@muglug.com" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Psalm plugin for PHPUnit", + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], "support": { - "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", - "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.18.4" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2022-12-03T07:47:07+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", @@ -3041,6 +3479,70 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4211420d25eba80712bff236a98960ef68b866b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", + "reference": "4211420d25eba80712bff236a98960ef68b866b7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2022-04-01T13:37:23+00:00" + }, { "name": "slevomat/coding-standard", "version": "7.2.1", @@ -3447,6 +3949,134 @@ ], "time": "2022-09-21T20:25:27+00:00" }, + { + "name": "symfony/finder", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/5cc9cac6586fc0c28cd173780ca696e419fefa11", + "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-20T17:44:14+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "6a180d1c45e0d9797470ca9eb46215692de00fa3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/6a180d1c45e0d9797470ca9eb46215692de00fa3", + "reference": "6a180d1c45e0d9797470ca9eb46215692de00fa3", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-01T08:36:10+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.27.0", @@ -3860,6 +4490,67 @@ ], "time": "2022-11-03T14:55:06+00:00" }, + { + "name": "symfony/process", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "2114fd60f26a296cc403a7939ab91478475a33d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/2114fd60f26a296cc403a7939ab91478475a33d4", + "reference": "2114fd60f26a296cc403a7939ab91478475a33d4", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-01T08:36:10+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.5.2", @@ -4295,6 +4986,55 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "webmozart/glob", + "version": "4.6.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.6.0" + }, + "time": "2022-05-24T19:45:58+00:00" } ], "aliases": [], diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 0000000..f7bf94a --- /dev/null +++ b/phpbench.json @@ -0,0 +1,6 @@ +{ + "runner.bootstrap": "vendor/autoload.php", + "runner.file_pattern": "*Bench.php", + "runner.path": "benchmarks", + "runner.retry_threshold": 5 +} From 222463347e88dfef840f46273d5b7a3f3d07b085 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Tue, 31 Jan 2023 21:06:25 -0600 Subject: [PATCH 4/4] making requested changes for pull request #44 Signed-off-by: Jacob Brown --- .gitattributes | 2 ++ benchmarks/AclBench.php | 5 ++++- src/Acl.php | 42 +++++++++++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.gitattributes b/.gitattributes index 25bb072..9da8353 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,5 @@ /phpcs.xml export-ignore /phpunit.xml.dist export-ignore /test/ export-ignore +/benchmarks/ export-ignore +/phpbench.json export-ignore diff --git a/benchmarks/AclBench.php b/benchmarks/AclBench.php index 8f92d3a..b2dc93a 100644 --- a/benchmarks/AclBench.php +++ b/benchmarks/AclBench.php @@ -27,7 +27,10 @@ public function __construct() $this->acl = new Acl(); } - public function benchSetService(): void + /** + * Benchmarks setting up ACL with roles and resources, and then adding allow/deny rules to them + */ + public function benchAclRolesResourcesAllowDeny(): void { $acl = new Acl(); $role = new GenericRole('A'); diff --git a/src/Acl.php b/src/Acl.php index 3365034..1986063 100644 --- a/src/Acl.php +++ b/src/Acl.php @@ -59,9 +59,9 @@ class Acl implements AclInterface * Resources by resourceId plus a null element * Used to speed up setRule() * - * @var (ResourceInterface|null)[] + * @var array */ - protected $resourcesById = [null]; + private $resourcesById = [null]; /** @var Role\RoleInterface|null */ protected $isAllowedRole; @@ -568,10 +568,10 @@ public function setRule( } } unset($rolesTemp); - /** var (ResourceInterface|null)[] */ - $resourcesArray = []; + /** var array */ + $resourcesToApplyRules = []; if (null === $resources && $this->resources) { - $resourcesArray = $this->resourcesById; + $resourcesToApplyRules = $this->resourcesById; } else { // ensure that all specified Resources exist; normalize input to array of Resource objects or null if (! is_array($resources)) { @@ -583,10 +583,10 @@ public function setRule( if (null !== $resource) { $resourceObj = $this->getResource($resource); $resourceId = $resourceObj->getResourceId(); - $this->getChildResources($resourceObj, $resourcesArray); - $resourcesArray[$resourceId] = $resourceObj; + $this->getChildResourcesIntoReference($resourceObj, $resourcesToApplyRules); + $resourcesToApplyRules[$resourceId] = $resourceObj; } else { - $resourcesArray[] = null; + $resourcesToApplyRules[] = null; } } unset($resources); @@ -602,7 +602,7 @@ public function setRule( switch ($operation) { // add to the rules case self::OP_ADD: - foreach ($resourcesArray as $resource) { + foreach ($resourcesToApplyRules as $resource) { foreach ($roles as $role) { $rules =& $this->getRules($resource, $role, true); if (! $privileges) { @@ -623,7 +623,7 @@ public function setRule( // remove from the rules case self::OP_REMOVE: - foreach ($resourcesArray as $resource) { + foreach ($resourcesToApplyRules as $resource) { foreach ($roles as $role) { $rules =& $this->getRules($resource, $role); if (null === $rules) { @@ -674,20 +674,34 @@ public function setRule( /** * Returns all child resources from the given resource. * - * @param (ResourceInterface|null)[] $return - * @return (ResourceInterface|null)[] + * @param array $return + * @return array */ - protected function getChildResources(ResourceInterface $resource, array &$return = []) + private function getChildResourcesIntoReference(ResourceInterface $resource, array &$return = []) { $id = $resource->getResourceId(); $children = $this->resources[$id]['children']; foreach ($children as $child) { - $this->getChildResources($child, $return); + $this->getChildResourcesIntoReference($child, $return); $return[$child->getResourceId()] = $child; } return $return; } + /** + * Returns all child resources from the given resource. + * + * @deprecated + * + * @see getChildResourcesIntoReference() + * + * @return array + */ + protected function getChildResources(ResourceInterface $resource) + { + return $this->getChildResourcesIntoReference($resource); + } + /** * Returns true if and only if the Role has access to the Resource *