Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy-loading of ghost objects only via properties access (no method overrides) #192

Merged
merged 153 commits into from
Dec 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
9099253
POC of lazy-loading of ghost objects implemented only via properties …
Ocramius Oct 27, 2014
7efeffc
`ProxiedMethodsFilter` should be `final`
Ocramius Dec 14, 2014
d2c5104
`ProxiedMethodsFilter` should provide API to filter only for abstract…
Ocramius Dec 14, 2014
a3ba461
New test asset: class with an abstract public method
Ocramius Dec 14, 2014
a610f07
New test asset: class with all magic methods as abstracts
Ocramius Dec 14, 2014
6b4e432
Verifying filtering against the new `ClassWithAbstractMagicMethods` t…
Ocramius Dec 14, 2014
1739ad5
Verifying filtering against the new `ClassWithAbstractMagicMethods` t…
Ocramius Dec 14, 2014
b63e29f
Verifying that only abstract methods are fetched, even when magic met…
Ocramius Dec 14, 2014
88f0a18
Lazy loading ghost should only implement `abstract public` methods, a…
Ocramius Dec 14, 2014
a627262
Implementing `getAbstractProxiedMethods` as per test specification an…
Ocramius Dec 14, 2014
704a124
All properties should be unset in a lazy loading ghost: this has to h…
Ocramius Dec 14, 2014
2baf01f
Correct binding of closures that `unset()` proxy properties
Ocramius Dec 14, 2014
78d2422
Reverting changes to `ProxyManager\ProxyGenerator\LazyLoading\MethodG…
Ocramius Dec 14, 2014
bf1848a
Re-introducing `ClassWithSelfHint` test that was squashed away by a r…
Ocramius Dec 14, 2014
da0d4d9
Fixed undefined parameter
Ocramius Dec 14, 2014
a15ec12
`::class` over string reference
Ocramius Dec 14, 2014
5cd9136
`ClassWithSelfHint#selfHintMethod()` does not access any properties o…
Ocramius Dec 14, 2014
d73bf97
Short array syntax
Ocramius Dec 14, 2014
74f949c
Refactoring `generateProxy` method
Ocramius Dec 14, 2014
d51d111
CS: fixed indentation in generated code
Ocramius Dec 14, 2014
f457109
CS: fixed indentation in generated code
Ocramius Dec 14, 2014
01d3f45
Proxy should be instantiated via its static constructor
Ocramius Dec 14, 2014
f5b12ec
Moving default methods that are filtered out to a private static prop…
Ocramius Dec 14, 2014
c73beb3
Refactored `ProxiedMethodsFilter` to avoid code duplication
Ocramius Dec 14, 2014
e63ea9a
CS: no need for multiline method definition
Ocramius Dec 14, 2014
a4f7679
Removing fixed `@todo`
Ocramius Dec 14, 2014
c7e28a4
Refactored test now that `ProxiedMethodsFilter::get*ProxiedMethods()`…
Ocramius Dec 14, 2014
8c8f6a2
Quick implementation of access protection for the new property-based …
Ocramius Dec 15, 2014
51b59fd
Swapping private/protected properties map parameters
Ocramius Dec 15, 2014
4599b68
Protecting `isset()` access to private/protected properties of ghost …
Ocramius Dec 15, 2014
561da4e
Protecting `unset()` access to private/protected properties of ghost …
Ocramius Dec 15, 2014
1c2bf2b
s/isset/unset
Ocramius Dec 15, 2014
e120095
s/isset/unset
Ocramius Dec 15, 2014
60fa1cd
Typo fixes (wrong array keys used)
Ocramius Dec 15, 2014
a5b1a1d
The proxy itself must have privileged access to properties of the obj…
Ocramius Dec 15, 2014
3fd68cd
Correcting parameters passed to `MagicSet` constructor
Ocramius Dec 15, 2014
60b854b
Syntax error fix, using closure to access the private property
Ocramius Dec 15, 2014
2d6df0f
Minor performance optimization
Ocramius Dec 15, 2014
df7a303
Private properties are now indexed by property name first
Ocramius Dec 15, 2014
92864d8
Re-implemented initializer call so that it fetches all reflection pro…
Ocramius Dec 15, 2014
5d5050f
Reverting PoC change
Ocramius Dec 15, 2014
ede012d
Fixed prefix for the `ProtectedPropertiesMap`
Ocramius Dec 15, 2014
ea366ca
Rewrote initialization to allow fetching all properties in a by-ref a…
Ocramius Dec 15, 2014
cdac575
Magic getter should always bind closures to a valid class
Ocramius Dec 15, 2014
b35abcd
Always bind to correct classes, never to the current proxy class or j…
Ocramius Dec 15, 2014
f85b73f
Test asset for classes with colliding inherited properties
Ocramius Dec 15, 2014
2533a6a
Verifying that inheritance default values are respected
Ocramius Dec 15, 2014
7d6d980
Testing new by-ref property initialization feature
Ocramius Dec 15, 2014
ef98b18
Verifying by-ref initialization
Ocramius Dec 15, 2014
2295470
Simplifying performance test now that reflection is not needed anymore
Ocramius Dec 15, 2014
30f69ea
All generator must be able to handle multiple inheritance with same p…
Ocramius Dec 15, 2014
6a4ef6e
Verifying multiple inheritance proxy private property access
Ocramius Dec 15, 2014
2a934bf
Refactored `StaticProxyConstructor` to group together closure calls a…
Ocramius Dec 15, 2014
bda3bb1
Optimized imports
Ocramius Dec 15, 2014
6c930bc
Fixing constructor call to `StaticProxyConstructor`
Ocramius Dec 15, 2014
1134d1c
Fixing constructor call to `StaticProxyConstructor`
Ocramius Dec 15, 2014
fb0965a
s/$this/$instance
Ocramius Dec 15, 2014
4670f9e
CS (generated code spacing)
Ocramius Dec 15, 2014
b2f5e5f
Aligning expected code structure to new generated code
Ocramius Dec 15, 2014
e89dbe5
Closures calling `unset()` should be cached
Ocramius Dec 15, 2014
5af812b
Closures calling `unset()` are cached
Ocramius Dec 15, 2014
4abc9c8
Closures setting private properties defaults should be cached
Ocramius Dec 15, 2014
e5ad212
Fixing coverage annotation
Ocramius Dec 15, 2014
73206a2
Caching property default value writer closures
Ocramius Dec 15, 2014
3481d05
`Properties` utility now provides also private properties grouped by …
Ocramius Dec 15, 2014
eeddabf
De-duplicating code by using `Properties#getGroupedPrivateProperties()`
Ocramius Dec 15, 2014
7b613f0
Closures using for getting references to private properties should be…
Ocramius Dec 15, 2014
2a845c3
Closures used for getting references to private properties are now ca…
Ocramius Dec 15, 2014
c256ab6
Optimized imports
Ocramius Dec 15, 2014
bf599a5
IDE hints for the type-checker
Ocramius Dec 15, 2014
0de2f05
Refactoring test logic and fixing constructor args
Ocramius Dec 15, 2014
b840344
Aligning test logic to current generated code
Ocramius Dec 15, 2014
b5cea35
Minor CS fix in generated code (spacing)
Ocramius Dec 15, 2014
f77a1f7
Private properties of the same class should cause a single (cached) c…
Ocramius Dec 15, 2014
f864b55
Typo fix
Ocramius Dec 15, 2014
415c59e
Caching by-ref private properties accessors by class name
Ocramius Dec 15, 2014
0b4293b
Remove resolved todo
Ocramius Dec 15, 2014
c07b287
Avoiding static cache key collisions
Ocramius Dec 15, 2014
76fbc26
Avoiding static cache key collisions in initializer logic
Ocramius Dec 15, 2014
a47906b
Missing type-hint in test
Ocramius Dec 15, 2014
1e9ad8c
Adding test for private property `isset()` checks in private scope
Ocramius Dec 15, 2014
9edbb95
Testing also repeated access to the same private properties
Ocramius Dec 15, 2014
2c3ec8d
`isset()` private property accessors are now cached per class property
Ocramius Dec 15, 2014
a0c64dc
s/magicGet/magicIsset
Ocramius Dec 15, 2014
78611cb
Adapting `MagicIssetTest` to new code generation
Ocramius Dec 15, 2014
602f7cf
Testing `MagicIsset` when `__isset` is implemented in original classes
Ocramius Dec 15, 2014
04bc62a
Adding property docblock
Ocramius Dec 15, 2014
37c3065
Refactoring `MagicGet` tests to expect the code in the template in an…
Ocramius Dec 15, 2014
5f834c9
Refactoring `MagicGet` tests, removing useless test
Ocramius Dec 15, 2014
7ed8bd5
Adding missing `@group` annotations
Ocramius Dec 15, 2014
5c4b7c5
Proxies should handle properties separately for every proxy instance
Ocramius Dec 15, 2014
6166c27
Proxies should handle properties separately for every proxy instance
Ocramius Dec 15, 2014
92801aa
Proxies should handle properties separately for every proxy instance
Ocramius Dec 15, 2014
e8c9eb0
Accessors should be cached
Ocramius Dec 15, 2014
2ce80d1
Caching individual accessors in `__get` logic: accessors are now inst…
Ocramius Dec 15, 2014
881d9f9
Cached accessors should be instance-agnostic
Ocramius Dec 15, 2014
59c8def
No need to pass parameter `$name` to cached `__get` accessors
Ocramius Dec 15, 2014
65dfd76
No need to pass parameter `$name` to cached `__get` accessors
Ocramius Dec 15, 2014
5f05fa4
No need to pass parameter `$name` to cached `__isset` accessors
Ocramius Dec 15, 2014
07f4125
No need to pass parameter `$name` to cached `__isset` accessors, maki…
Ocramius Dec 15, 2014
7541588
New test asset: class that provides means to use `isset()` in private…
Ocramius Dec 15, 2014
761dc2f
Renaming: making the class a multi-purpose property-state check class
Ocramius Dec 15, 2014
407e624
Adding pseudo-magic methods for private access
Ocramius Dec 15, 2014
8da734e
Test private property write access on different proxy instances
Ocramius Dec 15, 2014
cc4f9ce
Default values for properties
Ocramius Dec 15, 2014
e1ca231
Removing useless initialization code
Ocramius Dec 15, 2014
b98f513
Verifying that private property access does not leak across instances…
Ocramius Dec 15, 2014
9cad99f
Verifying that private property `unset` does not leak across instance…
Ocramius Dec 15, 2014
754dfa8
Fixing constructor parameters mismatch in tests
Ocramius Dec 15, 2014
dd50f5b
Test should expect the magic `__set` to interact with private propert…
Ocramius Dec 15, 2014
9ac39c7
Corrected expected parameters passed to the initializer method
Ocramius Dec 15, 2014
39d2d10
Fixing test inconsistencies caused by copying the boilerplate code fr…
Ocramius Dec 15, 2014
bbfd1df
Removing useless test, asserting structure also in cases where a magi…
Ocramius Dec 15, 2014
a0f18cf
Fixed coverage annotations
Ocramius Dec 15, 2014
2682110
Syntax fix (consistency with implementation)
Ocramius Dec 15, 2014
8dd4fd9
Removing useless test
Ocramius Dec 15, 2014
9dd35c6
PSR-2 compliance (closing brace on newline)
Ocramius Dec 15, 2014
2855bd7
Simplified test for cases where a parent `__set()` method is provided
Ocramius Dec 15, 2014
dd1f369
Implementation as per test specification
Ocramius Dec 15, 2014
10b89be
Aligning test implementation to other magic method generator tests
Ocramius Dec 15, 2014
4c14dac
Fixing wrong assertion: should `isset()` instead of directly returnin…
Ocramius Dec 15, 2014
1040ee6
Verifying that protected properties `null` and `false` values are cor…
Ocramius Dec 15, 2014
62e6dfa
s/__isset/__unset
Ocramius Dec 15, 2014
bfe7d68
Adjusting test to match generated code pattern, fixing minor inconsis…
Ocramius Dec 15, 2014
a9b6994
Caching accessors used to `unset()` properties
Ocramius Dec 15, 2014
6283ac0
Fixing getter: should be an issetter instead
Ocramius Dec 15, 2014
2f04e22
Fixed docblock parameter
Ocramius Dec 15, 2014
15fd51f
Removing tests for unused class
Ocramius Dec 15, 2014
f47898b
Removing unused class
Ocramius Dec 15, 2014
3be82a1
`$allProperties` is not needed
Ocramius Dec 15, 2014
c173fd1
Removing unused class
Ocramius Dec 15, 2014
2c9cd43
Skip static properties in the `Properties` utility
Ocramius Dec 15, 2014
6292bf0
Avoiding code duplication by using an utility class
Ocramius Dec 15, 2014
0cc3861
Importing used class
Ocramius Dec 15, 2014
d36425f
Avoiding code duplication by using an utility class
Ocramius Dec 15, 2014
66cb4ae
Public properties default values map is not needed at all
Ocramius Dec 15, 2014
9e1b75b
Removing tests for unused class
Ocramius Dec 15, 2014
ad277dd
Removing unused class
Ocramius Dec 15, 2014
8e8da0a
Basic tests for `ProxyManager\ProxyGenerator\LazyLoadingGhost\Propert…
Ocramius Dec 15, 2014
930ccf5
`ProtectedPropertiesMap` should ignore abstract methods
Ocramius Dec 15, 2014
3d8150f
Assertions on `ProtectedPropertiesMap` visibility
Ocramius Dec 15, 2014
992cb71
Fixed class docblock description
Ocramius Dec 15, 2014
70f98f3
Basic tests for `ProxyManager\ProxyGenerator\LazyLoadingGhost\Propert…
Ocramius Dec 15, 2014
8e7aa78
Wrong coverage annotation fix
Ocramius Dec 15, 2014
b8e1a56
Basic tests for `ProxyManager\ProxyGenerator\Util\Properties`
Ocramius Dec 15, 2014
3d7100c
Basic tests for `ProxyManager\ProxyGenerator\Util\Properties#getProte…
Ocramius Dec 15, 2014
a0bcea3
`ProxyManager\ProxyGenerator\Util\Properties#getProtectedProperties()…
Ocramius Dec 15, 2014
ba17e21
Basic tests for `ProxyManager\ProxyGenerator\Util\Properties#getPriva…
Ocramius Dec 15, 2014
e1fc98d
`ProxyManager\ProxyGenerator\Util\Properties#getPrivateProperties()` …
Ocramius Dec 15, 2014
3717ea2
`ProxyManager\ProxyGenerator\Util\Properties#getAccessibleProperties(…
Ocramius Dec 15, 2014
d14bdfb
`ProxyManager\ProxyGenerator\Util\Properties#getGroupedPrivatePropert…
Ocramius Dec 15, 2014
3f5b99a
`ProxyManager\ProxyGenerator\Util\Properties#getGroupedPrivatePropert…
Ocramius Dec 15, 2014
a418c4a
Verifying `MagicGet` generated code even without parent `__set()` met…
Ocramius Dec 15, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\ParameterGenerator;
use ReflectionClass;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionProperty;
use Zend\Code\Generator\PropertyGenerator;

Expand All @@ -33,33 +33,70 @@
class StaticProxyConstructor extends MethodGenerator
{
/**
* Constructor
* Static constructor
*
* @param ReflectionClass $originalClass
* @param PropertyGenerator $initializerProperty
* @param Properties $properties
*/
public function __construct(ReflectionClass $originalClass, PropertyGenerator $initializerProperty)
public function __construct(PropertyGenerator $initializerProperty, Properties $properties)
{
parent::__construct('staticProxyConstructor', [], static::FLAG_PUBLIC | static::FLAG_STATIC);

$this->setParameter(new ParameterGenerator('initializer'));

/* @var $publicProperties \ReflectionProperty[] */
$publicProperties = $originalClass->getProperties(ReflectionProperty::IS_PUBLIC);
$unsetProperties = [];

foreach ($publicProperties as $publicProperty) {
$unsetProperties[] = '$instance->' . $publicProperty->getName();
}

$this->setDocblock("Constructor for lazy initialization\n\n@param \\Closure|null \$initializer");
$this->setBody(
'static $reflection;' . "\n\n"
. '$reflection = $reflection ?: $reflection = new \ReflectionClass(__CLASS__);' . "\n"
. '$instance = (new \ReflectionClass(get_class()))->newInstanceWithoutConstructor();' . "\n\n"
. ($unsetProperties ? 'unset(' . implode(', ', $unsetProperties) . ");\n\n" : '')
. $this->generateUnsetPropertiesCode($properties)
. '$instance->' . $initializerProperty->getName() . ' = $initializer;' . "\n\n"
. 'return $instance;'
);
}

/**
* @param Properties $properties
*
* @return string
*/
private function generateUnsetPropertiesCode(Properties $properties)
{
$code = '';

if ($accessibleProperties = $properties->getAccessibleProperties()) {
$code .= $this->getUnsetPropertiesGroupCode($accessibleProperties) . "\n";
}

foreach ($properties->getGroupedPrivateProperties() as $className => $privateProperties) {
$cacheKey = 'cache' . str_replace('\\', '_', $className);
$code .= 'static $' . $cacheKey . ";\n\n"
. '$' . $cacheKey . ' ?: $' . $cacheKey . " = \\Closure::bind(function (\$instance) {\n"
. ' ' . $this->getUnsetPropertiesGroupCode($privateProperties)
. '}, null, ' . var_export($className, true) . ");\n\n"
. '$' . $cacheKey . "(\$instance);\n\n";
}

return $code;
}

/**
* @param ReflectionProperty[] $properties
*
* @return string
*/
private function getUnsetPropertiesGroupCode(array $properties)
{
return 'unset('
. implode(
', ',
array_map(
function (ReflectionProperty $property) {
return '$instance->' . $property->getName();
},
$properties
)
)
. ");\n";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\ParameterGenerator;
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionProperty;
use Zend\Code\Generator\PropertyGenerator;

/**
Expand All @@ -36,13 +38,13 @@ class CallInitializer extends MethodGenerator
* Constructor
*
* @param PropertyGenerator $initializerProperty
* @param PropertyGenerator $publicPropsDefaults
* @param PropertyGenerator $initTracker
* @param Properties $properties
*/
public function __construct(
PropertyGenerator $initializerProperty,
PropertyGenerator $publicPropsDefaults,
PropertyGenerator $initTracker
PropertyGenerator $initTracker,
Properties $properties
) {
parent::__construct(UniqueIdentifierGenerator::getIdentifier('callInitializer'));
$this->setDocblock("Triggers initialization logic for this ghost object");
Expand All @@ -57,15 +59,141 @@ public function __construct(
$initializer = $initializerProperty->getName();
$initialization = $initTracker->getName();

$this->setBody(
'if ($this->' . $initialization . ' || ! $this->' . $initializer . ') {' . "\n return;\n}\n\n"
. "\$this->" . $initialization . " = true;\n\n"
. "foreach (self::\$" . $publicPropsDefaults->getName() . " as \$key => \$default) {\n"
. " \$this->\$key = \$default;\n"
. "}\n\n"
. '$this->' . $initializer . '->__invoke'
. '($this, $methodName, $parameters, $this->' . $initializer . ');' . "\n\n"
. "\$this->" . $initialization . " = false;"
$bodyTemplate = <<<'PHP'
if ($this->%s || ! $this->%s) {
return;
}

$this->%s = true;

%s
%s

$this->%s->__invoke($this, $methodName, $parameters, $this->%s, $properties);
$this->%s = false;
PHP;

$this->setBody(sprintf(
$bodyTemplate,
$initialization,
$initializer,
$initialization,
$this->propertiesInitializationCode($properties),
$this->propertiesReferenceArrayCode($properties),
$initializer,
$initializer,
$initialization
));
}

/**
* @param Properties $properties
*
* @return string
*/
private function propertiesInitializationCode(Properties $properties)
{
$assignments = [];

foreach ($properties->getAccessibleProperties() as $property) {
$assignments[] = '$this->'
. $property->getName()
. ' = ' . $this->getExportedPropertyDefaultValue($property)
. ';';
}


foreach ($properties->getGroupedPrivateProperties() as $className => $privateProperties) {
$cacheKey = 'cache' . str_replace('\\', '_', $className);
$assignments[] = 'static $' . $cacheKey . ";\n\n"
. '$' . $cacheKey . ' ?: $' . $cacheKey . " = \\Closure::bind(function (\$instance) {\n"
. $this->getPropertyDefaultsAssignments($privateProperties) . "\n"
. '}, null, ' . var_export($className, true) . ");\n\n"
. '$' . $cacheKey . "(\$this);\n\n";
}

return implode("\n", $assignments) . "\n\n";
}

/**
* @param ReflectionProperty[] $properties
*
* @return string
*/
private function getPropertyDefaultsAssignments(array $properties)
{
return implode(
"\n",
array_map(
function (ReflectionProperty $property) {
return ' $instance->' . $property->getName()
. ' = ' . $this->getExportedPropertyDefaultValue($property) . ';';
},
$properties
)
);
}

/**
* @param Properties $properties
*
* @return string
*/
private function propertiesReferenceArrayCode(Properties $properties)
{
$assignments = [];

foreach ($properties->getAccessibleProperties() as $propertyInternalName => $property) {
$assignments[] = ' '
. var_export($propertyInternalName, true) . ' => & $this->' . $property->getName()
. ',';
}

$code = "\$properties = [\n" . implode("\n", $assignments) . "\n];\n\n";

// must use assignments, as direct reference during array definition causes a fatal error (not sure why)
foreach ($properties->getGroupedPrivateProperties() as $className => $classPrivateProperties) {
$cacheKey = 'cacheFetch' . str_replace('\\', '_', $className);

$code .= 'static $' . $cacheKey . ";\n\n"
. '$' . $cacheKey . ' ?: $' . $cacheKey
. " = \\Closure::bind(function (\$instance, array & \$properties) {\n"
. $this->generatePrivatePropertiesAssignmentsCode($classPrivateProperties)
. "}, \$this, " . var_export($className, true) . ");\n\n"
. '$' . $cacheKey . "(\$this, \$properties);";
}

return $code;
}

/**
* @param ReflectionProperty[] $properties indexed by internal name
*
* @return string
*/
private function generatePrivatePropertiesAssignmentsCode(array $properties)
{
$code = '';

foreach ($properties as $property) {
$key = "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName();
$code .= ' $properties[' . var_export($key, true) . '] = '
. '& $instance->' . $property->getName() . ";\n";
}

return $code;
}

/**
* @param ReflectionProperty $property
*
* @return string
*/
private function getExportedPropertyDefaultValue(ReflectionProperty $property)
{
$name = $property->getName();
$defaults = $property->getDeclaringClass()->getDefaultProperties();

return var_export(isset($defaults[$name]) ? $defaults[$name] : null, true);
}
}
Loading