From eeb8b55f248cb2bf383a889df0abb25ee88dbc87 Mon Sep 17 00:00:00 2001 From: VolCh Date: Wed, 13 Jun 2018 18:48:38 +0300 Subject: [PATCH] start implement #64 --- ...leClosureAllPrivateClassHydrationBench.php | 29 +++++ ...ileClosureAllPublicClassHydrationBench.php | 29 +++++ .../FileClosureHydrationBench.php | 41 +++++++ ...meClosureAllPrivateClassHydrationBench.php | 29 +++++ ...imeClosureAllPublicClassHydrationBench.php | 29 +++++ .../RuntimeClosureHydrationBench.php | 40 +++++++ .../ClosureGenerator/GenerateExtractor.php | 10 ++ .../ClosureGenerator/GenerateFileHydrator.php | 111 ++++++++++++++++++ .../ClosureGenerator/GenerateHydrator.php | 10 ++ .../GenerateRuntimeExtractor.php | 85 ++++++++++++++ .../GenerateRuntimeHydrator.php | 86 ++++++++++++++ .../GenerateFileHydratorTest.php | 42 +++++++ .../GenerateRuntimeExtractorTest.php | 39 ++++++ 13 files changed, 580 insertions(+) create mode 100644 benchmarks/GeneratedHydratorBenchmark/FileClosureAllPrivateClassHydrationBench.php create mode 100644 benchmarks/GeneratedHydratorBenchmark/FileClosureAllPublicClassHydrationBench.php create mode 100644 benchmarks/GeneratedHydratorBenchmark/FileClosureHydrationBench.php create mode 100644 benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPrivateClassHydrationBench.php create mode 100644 benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPublicClassHydrationBench.php create mode 100644 benchmarks/GeneratedHydratorBenchmark/RuntimeClosureHydrationBench.php create mode 100644 src/GeneratedHydrator/ClosureGenerator/GenerateExtractor.php create mode 100644 src/GeneratedHydrator/ClosureGenerator/GenerateFileHydrator.php create mode 100644 src/GeneratedHydrator/ClosureGenerator/GenerateHydrator.php create mode 100644 src/GeneratedHydrator/ClosureGenerator/GenerateRuntimeExtractor.php create mode 100644 src/GeneratedHydrator/ClosureGenerator/GenerateRuntimeHydrator.php create mode 100644 tests/ClosureGenerator/GenerateFileHydratorTest.php create mode 100644 tests/ClosureGenerator/GenerateRuntimeExtractorTest.php diff --git a/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPrivateClassHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPrivateClassHydrationBench.php new file mode 100644 index 00000000..dcecb06b --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPrivateClassHydrationBench.php @@ -0,0 +1,29 @@ +createHydrator(AllPrivateClass::class); + $this->createData(); + $this->object = new AllPrivateClass(); + } + + /** + * @Revs(100) + * @Iterations(200) + */ + public function benchConsume() : void + { + ($this->hydrator)($this->object, $this->data); + } +} diff --git a/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPublicClassHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPublicClassHydrationBench.php new file mode 100644 index 00000000..bb4c4cfb --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/FileClosureAllPublicClassHydrationBench.php @@ -0,0 +1,29 @@ +createHydrator(AllPublicClass::class); + $this->createData(); + $this->object = new AllPublicClass(); + } + + /** + * @Revs(100) + * @Iterations(200) + */ + public function benchConsume() : void + { + ($this->hydrator)($this->object, $this->data); + } +} diff --git a/benchmarks/GeneratedHydratorBenchmark/FileClosureHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/FileClosureHydrationBench.php new file mode 100644 index 00000000..09388017 --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/FileClosureHydrationBench.php @@ -0,0 +1,41 @@ +hydrator = (new GenerateFileHydrator())($class); + } + + /** + * Populate test data array + */ + protected function createData() : void + { + $this->data = [ + 'foo' => 'some foo string', + 'bar' => 42, + 'baz' => new \DateTime(), + 'someFooProperty' => [12, 13, 14], + 'someBarProperty' => 12354.4578, + 'someBazProperty' => new \stdClass(), + ]; + } +} diff --git a/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPrivateClassHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPrivateClassHydrationBench.php new file mode 100644 index 00000000..25621cd3 --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPrivateClassHydrationBench.php @@ -0,0 +1,29 @@ +createHydrator(AllPrivateClass::class); + $this->createData(); + $this->object = new AllPrivateClass(); + } + + /** + * @Revs(100) + * @Iterations(200) + */ + public function benchConsume() : void + { + ($this->hydrator)($this->object, $this->data); + } +} diff --git a/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPublicClassHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPublicClassHydrationBench.php new file mode 100644 index 00000000..949cf39e --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureAllPublicClassHydrationBench.php @@ -0,0 +1,29 @@ +createHydrator(AllPublicClass::class); + $this->createData(); + $this->object = new AllPublicClass(); + } + + /** + * @Revs(100) + * @Iterations(200) + */ + public function benchConsume() : void + { + ($this->hydrator)($this->object, $this->data); + } +} diff --git a/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureHydrationBench.php b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureHydrationBench.php new file mode 100644 index 00000000..15571481 --- /dev/null +++ b/benchmarks/GeneratedHydratorBenchmark/RuntimeClosureHydrationBench.php @@ -0,0 +1,40 @@ +hydrator = (new GenerateRuntimeHydrator())($class); + } + + /** + * Populate test data array + */ + protected function createData() : void + { + $this->data = [ + 'foo' => 'some foo string', + 'bar' => 42, + 'baz' => new \DateTime(), + 'someFooProperty' => [12, 13, 14], + 'someBarProperty' => 12354.4578, + 'someBazProperty' => new \stdClass(), + ]; + } +} diff --git a/src/GeneratedHydrator/ClosureGenerator/GenerateExtractor.php b/src/GeneratedHydrator/ClosureGenerator/GenerateExtractor.php new file mode 100644 index 00000000..d2a33caf --- /dev/null +++ b/src/GeneratedHydrator/ClosureGenerator/GenerateExtractor.php @@ -0,0 +1,10 @@ +targetDir = $targetDir ?? sys_get_temp_dir() . DIRECTORY_SEPARATOR; + } + + public function __invoke(string $className): callable + { + if (isset(self::$hydratorsCache[$className])) { + return self::$hydratorsCache[$className]; + } + + $classId = str_replace('\\', '_', $className); + $filename = $this->targetDir . $classId . '.php'; + $hydratorName = "hydrator_" . $classId; + if (! file_exists($filename)) { + $content = <<getPropertyNames($className) as $propertyName) { + $content .= " \$object->{$propertyName} = \$data['{$propertyName}'];" . PHP_EOL; + } + $content .= <<getClass($className)->getParentClass(); + if ($parent === false) { + return null; + } + + return $this($parent->getName()); + } + + /** + * @return string[] + */ + private function getPropertyNames(string $className): array + { + $class = $this->getClass($className); + $propertyNames = []; + foreach ($class->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + $propertyNames[] = $property->getName(); + } + return $propertyNames; + } + + private function getClass(string $className): ReflectionClass + { + if (isset(self::$classesCache[$className])) { + return self::$classesCache[$className]; + } + + if (!class_exists($className, true)) { + throw new RuntimeException("Class $className not found"); + } + + try { + $class = new ReflectionClass($className); + } catch (ReflectionException $e) { + throw new RuntimeException("Can't reflect class $className", 0, $e); + } + + return self::$classesCache[$className] = $class; + } +} diff --git a/src/GeneratedHydrator/ClosureGenerator/GenerateHydrator.php b/src/GeneratedHydrator/ClosureGenerator/GenerateHydrator.php new file mode 100644 index 00000000..11d40992 --- /dev/null +++ b/src/GeneratedHydrator/ClosureGenerator/GenerateHydrator.php @@ -0,0 +1,10 @@ +getPropertyNames($className); + $parentExtractor = $this->generateParentExtractor($className); + $extractor = Closure::bind(function (object $object) use ($propertyNames, $parentExtractor) : array { + $data = $parentExtractor !== null ? $parentExtractor($object) : []; + foreach ($propertyNames as $propertyName) { + $data[$propertyName] = $object->{$propertyName}; + } + return $data; + }, null, $className); + + return self::$extractorsCache[$className] = $extractor; + } + + + private function generateParentExtractor($className): ?callable + { + $parent = $this->getClass($className)->getParentClass(); + if ($parent === false) { + return null; + } + + return $this($parent->getName()); + } + + /** + * @return string[] + */ + private function getPropertyNames(string $className): array + { + $class = $this->getClass($className); + $propertyNames = []; + foreach ($class->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + $propertyNames[] = $property->getName(); + } + return $propertyNames; + } + + private function getClass(string $className): ReflectionClass + { + if (isset(self::$classesCache[$className])) { + return self::$classesCache[$className]; + } + + if (!class_exists($className, true)) { + throw new RuntimeException("Class $className not found"); + } + + try { + $class = new ReflectionClass($className); + } catch (ReflectionException $e) { + throw new RuntimeException("Can't reflect class $className", 0, $e); + } + + return self::$classesCache[$className] = $class; + } +} diff --git a/src/GeneratedHydrator/ClosureGenerator/GenerateRuntimeHydrator.php b/src/GeneratedHydrator/ClosureGenerator/GenerateRuntimeHydrator.php new file mode 100644 index 00000000..ec45d14d --- /dev/null +++ b/src/GeneratedHydrator/ClosureGenerator/GenerateRuntimeHydrator.php @@ -0,0 +1,86 @@ +getPropertyNames($className); + $parentHydrator = $this->generateParentHydrator($className); + $extractor = Closure::bind(function (object $object, array $data) use ($propertyNames, $parentHydrator) : array { + if ($parentHydrator !== null) { + $parentHydrator($object, $data); + } + foreach ($propertyNames as $propertyName) { + $object->{$propertyName} = $data[$propertyName]; + } + return $data; + }, null, $className); + + return self::$hydratorsCache[$className] = $extractor; + } + + private function generateParentHydrator($className): ?callable + { + $parent = $this->getClass($className)->getParentClass(); + if ($parent === false) { + return null; + } + + return $this($parent->getName()); + } + + /** + * @return string[] + */ + private function getPropertyNames(string $className): array + { + $class = $this->getClass($className); + $propertyNames = []; + foreach ($class->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + $propertyNames[] = $property->getName(); + } + return $propertyNames; + } + + private function getClass(string $className): ReflectionClass + { + if (isset(self::$classesCache[$className])) { + return self::$classesCache[$className]; + } + + if (!class_exists($className, true)) { + throw new RuntimeException("Class $className not found"); + } + + try { + $class = new ReflectionClass($className); + } catch (ReflectionException $e) { + throw new RuntimeException("Can't reflect class $className", 0, $e); + } + + return self::$classesCache[$className] = $class; + } +} diff --git a/tests/ClosureGenerator/GenerateFileHydratorTest.php b/tests/ClosureGenerator/GenerateFileHydratorTest.php new file mode 100644 index 00000000..2eb722b8 --- /dev/null +++ b/tests/ClosureGenerator/GenerateFileHydratorTest.php @@ -0,0 +1,42 @@ + 'publicProperty', + 'protectedProperty' => 'protectedProperty', + 'privateProperty' => 'privatePropertyDefault', + ]); + + $filename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . sha1(\get_class($object)); + self::assertFileExists($filename); + +var_dump(file_get_contents($filename)); + } + public function testWithPrivatePropertiesAndParent() : void + { +// $object = new ClassWithPrivatePropertiesAndParent(); +// $extractor = (new GenerateRuntimeExtractor())(\get_class($object)); +// +// $result = $extractor($object); +// +// var_dump($result); + +// self::assertEquals([ +// 'publicProperty' => 'publicPropertyDefault', +// 'protectedProperty' => 'protectedPropertyDefault', +// 'privateProperty' => 'privatePropertyDefault', +// ], $result); + } +} diff --git a/tests/ClosureGenerator/GenerateRuntimeExtractorTest.php b/tests/ClosureGenerator/GenerateRuntimeExtractorTest.php new file mode 100644 index 00000000..985b11d5 --- /dev/null +++ b/tests/ClosureGenerator/GenerateRuntimeExtractorTest.php @@ -0,0 +1,39 @@ + 'publicPropertyDefault', + 'protectedProperty' => 'protectedPropertyDefault', + 'privateProperty' => 'privatePropertyDefault', + ], $result); + } + public function testWithPrivatePropertiesAndParent() : void + { + $object = new ClassWithPrivatePropertiesAndParent(); + $extractor = (new GenerateRuntimeExtractor())(\get_class($object)); + + $result = $extractor($object); + + var_dump($result); + +// self::assertEquals([ +// 'publicProperty' => 'publicPropertyDefault', +// 'protectedProperty' => 'protectedPropertyDefault', +// 'privateProperty' => 'privatePropertyDefault', +// ], $result); + } +}