From 27c868a597a19db394a7eeac268212e3db6f01c0 Mon Sep 17 00:00:00 2001 From: laszlovl Date: Sat, 2 May 2015 06:54:38 -0400 Subject: [PATCH] Use class variables + get_class() or get_called_class() instead of static function variables The latter appears to be buggy in PHP when overriding a function and using parent::. --- README.md | 27 +++++------- benchmark/commands/BenchmarkController.php | 11 +---- benchmark/models/AnimalTrait.php | 7 +--- db/ActiveRecord.php | 48 ++++++++++++---------- tests/InheritanceTest.php | 28 +++++++++++++ tests/bootstrap.php | 1 + tests/models/ActiveRecord.php | 20 +++++++++ tests/models/Animal.php | 8 ++++ tests/models/Customer.php | 8 ++++ 9 files changed, 105 insertions(+), 53 deletions(-) create mode 100644 tests/InheritanceTest.php create mode 100644 tests/models/ActiveRecord.php create mode 100644 tests/models/Animal.php create mode 100644 tests/models/Customer.php diff --git a/README.md b/README.md index 9b386e6..0348a98 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ be **static**: either all of your Cats have a property `owner`, or none of them all other cats would have it as well. If this is indeed the case for your application, we can cache this metadata on the class level: once it's calculated for one instance of a class, for -all other instances it can be looked up instantly. This extension's `ActiveRecord` does just that, by using PHP's static function variables to cache -the result of a function between all instances of the containing class. +all other instances it can be looked up instantly. This extension's `ActiveRecord` does just that, by using a static class variable to cache the result +of a function between all instances of the containing class. This can enormously benefit your application's performance, especially for requests that handle a lot of instances of the same class, like a GridView or ListView with a large pagination size, or an API call on a resource collection. @@ -55,24 +55,19 @@ the same class are created. ~/yii2-staticactiverecord/benchmark$ ./yii benchmark Benchmarking benchmarkGetProperty... -Regular ActiveRecord: 0.0046327114105225 -Static ActiveRecord: 0.0035665512084961 -Improvement: 24% - -Benchmarking benchmarkGetRelation... -Regular ActiveRecord: 0.029149389266968 -Static ActiveRecord: 0.024874639511108 -Improvement: 15% +Regular ActiveRecord: 0.0089842319488525 +Static ActiveRecord: 0.0069756507873535 +Improvement: 23% Benchmarking benchmarkSetProperty... -Regular ActiveRecord: 0.0057635307312012 -Static ActiveRecord: 0.0044625282287598 -Improvement: 23% +Regular ActiveRecord: 0.009577751159668 +Static ActiveRecord: 0.0076509952545166 +Improvement: 21% Benchmarking benchmarkValidate... -Regular ActiveRecord: 0.022860932350159 -Static ActiveRecord: 0.017516303062439 -Improvement: 24% +Regular ActiveRecord: 0.046598815917969 +Static ActiveRecord: 0.03521466255188 +Improvement: 25% ``` The sum of the performance improvements in a single request of course highly depends on your specific application, but I've been able to reduce diff --git a/benchmark/commands/BenchmarkController.php b/benchmark/commands/BenchmarkController.php index 325784d..28c80e6 100644 --- a/benchmark/commands/BenchmarkController.php +++ b/benchmark/commands/BenchmarkController.php @@ -9,7 +9,7 @@ class BenchmarkController extends \yii\console\Controller { - const REPEAT = 10; + const REPEAT = 25; public function actionIndex() { @@ -24,8 +24,6 @@ private function benchmarks() { $this->benchmark('benchmarkGetProperty'); - $this->benchmark('benchmarkGetRelation'); - $this->benchmark('benchmarkSetProperty'); $this->benchmark('benchmarkValidate'); @@ -73,13 +71,6 @@ private function benchmarkGetProperty($class) } } - private function benchmarkGetRelation($class) - { - foreach ($class::find()->with('self')->all() as $model) { - $temp = $model->self; - } - } - private function benchmarkSetProperty($class) { foreach ($class::find()->all() as $model) { diff --git a/benchmark/models/AnimalTrait.php b/benchmark/models/AnimalTrait.php index 5f2d2d1..95e96f3 100644 --- a/benchmark/models/AnimalTrait.php +++ b/benchmark/models/AnimalTrait.php @@ -14,9 +14,4 @@ public function rules() ['name', 'string'] ]; } - - public function getSelf() - { - return $this->hasOne(static::class, ['id' => 'id']); - } -} \ No newline at end of file +} diff --git a/db/ActiveRecord.php b/db/ActiveRecord.php index 838384d..e90fd61 100644 --- a/db/ActiveRecord.php +++ b/db/ActiveRecord.php @@ -4,58 +4,64 @@ class ActiveRecord extends \yii\db\ActiveRecord { + private static $getTableSchemaCache; + private static $primaryKeyCache; + private static $attributesCache; + private static $hasAttributeCache; + private static $scenariosCache; + public static function getTableSchema() { - static $tableSchema = null; + $class = get_called_class(); - if ($tableSchema === null) { - $tableSchema = parent::getTableSchema(); + if (!isset(self::$getTableSchemaCache[$class])) { + self::$getTableSchemaCache[$class] = parent::getTableSchema(); } - return $tableSchema; + return self::$getTableSchemaCache[$class]; } public static function primaryKey() { - static $primaryKey = null; + $class = get_called_class(); - if ($primaryKey === null) { - $primaryKey = parent::primaryKey(); + if (!isset(self::$primaryKeyCache[$class])) { + self::$primaryKeyCache[$class] = parent::primaryKey(); } - return $primaryKey; + return self::$primaryKeyCache[$class]; } - final public function attributes() + public function attributes() { - static $attributes = null; + $class = get_class($this); - if ($attributes === null) { - $attributes = parent::attributes(); + if (!isset(self::$attributesCache[$class])) { + self::$attributesCache[$class] = parent::attributes(); } - return $attributes; + return self::$attributesCache[$class]; } public function hasAttribute($name) { - static $attributes = []; + $class = get_class($this); - if (!isset($attributes[$name])) { - $attributes[$name] = parent::hasAttribute($name); + if (!isset(self::$hasAttributeCache[$class][$name])) { + self::$hasAttributeCache[$class][$name] = parent::hasAttribute($name); } - return $attributes[$name]; + return self::$hasAttributeCache[$class][$name]; } public function scenarios() { - static $scenarios = null; + $class = get_class($this); - if ($scenarios === null) { - $scenarios = parent::scenarios(); + if (!isset(self::$scenariosCache[$class])) { + self::$scenariosCache[$class] = parent::scenarios(); } - return $scenarios; + return self::$scenariosCache[$class]; } } diff --git a/tests/InheritanceTest.php b/tests/InheritanceTest.php new file mode 100644 index 0000000..c55b697 --- /dev/null +++ b/tests/InheritanceTest.php @@ -0,0 +1,28 @@ +getConnection(); + } + + public function testInheritance() + { + $animal = new Animal; + $customer = new Customer; + + $animalAttr = $animal->attributes(); + $customerAttr = $customer->attributes(); + + $this->assertNotEquals($animalAttr, $customerAttr, 'Animal and Customer should have a different set of attributes'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2bd3742..be4d39f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -10,6 +10,7 @@ require_once(__DIR__ . '/../vendor/autoload.php'); require_once(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); +Yii::setAlias('@lvl/staticactiverecord/unit', __DIR__); Yii::setAlias('@yiiunit', __DIR__ . '/../vendor/yiisoft/yii2-dev/tests/unit'); Yii::$classMap['yiiunit\data\ar\ActiveRecord'] = __DIR__ . '/override/ActiveRecord.php'; \ No newline at end of file diff --git a/tests/models/ActiveRecord.php b/tests/models/ActiveRecord.php new file mode 100644 index 0000000..b98fc84 --- /dev/null +++ b/tests/models/ActiveRecord.php @@ -0,0 +1,20 @@ +