Skip to content

Commit

Permalink
Merge 1b3d417 into 3f62454
Browse files Browse the repository at this point in the history
  • Loading branch information
Ocramius committed Nov 30, 2013
2 parents 3f62454 + 1b3d417 commit 07067c6
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 13 deletions.
48 changes: 39 additions & 9 deletions src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class PublicScopeSimulator
const OPERATION_UNSET = 'unset';

/**
* Generates code for simulating access to a property from the scope that is accessing a proxy.
* This is done by introspecting `debug_backtrace()` and then binding a closure to the scope
* of the parent caller.
*
* @param string $operationType operation to execute: one of 'get', 'set', 'isset' or 'unset'
* @param string $nameParameter name of the `name` parameter of the magic method
* @param string|null $valueParameter name of the `value` parameter of the magic method
Expand All @@ -55,15 +59,41 @@ public static function getPublicAccessSimulationCode(
PropertyGenerator $valueHolder = null,
$returnPropertyName = null
) {
$byRef = self::getByRefReturnValue($operationType);
$value = static::OPERATION_SET === $operationType ? ', $value' : '';
$byRef = self::getByRefReturnValue($operationType);
$value = static::OPERATION_SET === $operationType ? ', $value' : '';
$target = '$this';
$notice = '';

if ($valueHolder) {
$target = '$this->' . $valueHolder->getName();
}

if (static::OPERATION_GET === $operationType) {
// This will just trigger a notice if `__get` is called against a non-existing property
$notice = ' $backtrace = debug_backtrace(false);' . "\n"
. ' trigger_error(\'Undefined property: \' . get_parent_class($this) . \'::$\' . $'
. $nameParameter
. ' . \' in \' . $backtrace[0][\'file\'] . \' on line \' . $backtrace[0][\'line\'], \E_USER_NOTICE);'
. "\n";
}

return '$targetObject = ' . self::getTargetObject($valueHolder) . ";\n"
. ' $accessor = function ' . $byRef . '() use ($targetObject, $name' . $value . ') {' . "\n"
. ' ' . self::getOperation($operationType, $nameParameter, $valueParameter) . ';' . "\n"
. " };\n"
return '$realInstanceReflection = new \\ReflectionClass(get_parent_class($this));' . "\n\n"
. 'if (! $realInstanceReflection->hasProperty($' . $nameParameter . ')) {' . "\n"
. ' $targetObject = ' . $target . ';' . "\n\n"
. $notice
. ' ' . self::getOperation($operationType, $nameParameter, $valueParameter) . ";\n"
. " return;\n"
. '}' . "\n\n"
. '$targetObject = ' . self::getTargetObject($valueHolder) . ";\n"
. '$accessor = function ' . $byRef . '() use ($targetObject, $name' . $value . ') {' . "\n"
. ' ' . self::getOperation($operationType, $nameParameter, $valueParameter) . ';' . "\n"
. "};\n"
. self::getScopeReBind()
. ' ' . ($returnPropertyName ? '$' . $returnPropertyName . ' =' : 'return') . ' $accessor();';
. (
$returnPropertyName
? '$' . $returnPropertyName . ' = & $accessor();'
: '$returnValue = & $accessor();' . "\n\n" . 'return $returnValue;'
);
}

/**
Expand Down Expand Up @@ -95,7 +125,7 @@ private static function getTargetObject(PropertyGenerator $valueHolder = null)
return '$this->' . $valueHolder->getName();
}

return 'unserialize(sprintf(\'O:%d:"%s":0:{}\', strlen(get_parent_class($this)), get_parent_class($this)));';
return 'unserialize(sprintf(\'O:%d:"%s":0:{}\', strlen(get_parent_class($this)), get_parent_class($this)))';
}

/**
Expand Down Expand Up @@ -140,7 +170,7 @@ private static function getScopeReBind()
// @codeCoverageIgnoreEnd
}

return ' $backtrace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT);' . "\n"
return ' $backtrace = debug_backtrace(true);' . "\n"
. ' $scopeObject = isset($backtrace[1][\'object\'])'
. ' ? $backtrace[1][\'object\'] : new \stdClass();' . "\n"
. ' $accessor = $accessor->bindTo($scopeObject, get_class($scopeObject));' . "\n";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function testBodyStructure()

$this->assertSame('__get', $magicGet->getName());
$this->assertCount(1, $magicGet->getParameters());
$this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody());
$this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function testBodyStructure()

$this->assertSame('__isset', $magicGet->getName());
$this->assertCount(1, $magicGet->getParameters());
$this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody());
$this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function testBodyStructure()

$this->assertSame('__set', $magicGet->getName());
$this->assertCount(2, $magicGet->getParameters());
$this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody());
$this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function testBodyStructure()

$this->assertSame('__unset', $magicGet->getName());
$this->assertCount(1, $magicGet->getParameters());
$this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody());
$this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read
--FILE--
<?php

require_once __DIR__ . '/init.php';

class Kitchen
{
private $sweets;

public function & __get($name)
{
return $name;
}
}

$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory($configuration);

$proxy = $factory->createProxy('Kitchen', function () {});

echo $proxy->nonExisting;
?>
--EXPECTF--
nonExisting
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read
--FILE--
<?php

require_once __DIR__ . '/init.php';

class Kitchen
{
private $sweets;
}

$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory($configuration);

$proxy = $factory->createProxy('Kitchen', function () {});

$proxy->nonExisting = 'I do not exist';
echo $proxy->nonExisting;
?>
--EXPECTF--
I do not exist
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read
--FILE--
<?php

require_once __DIR__ . '/init.php';

class Kitchen
{
private $sweets;
}

$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory($configuration);

$proxy = $factory->createProxy('Kitchen', function () {});

$proxy->nonExisting;
?>
--EXPECTF--
Notice: Undefined property: Kitchen::$nonExisting in %s on line %d

0 comments on commit 07067c6

Please sign in to comment.