Skip to content

Commit

Permalink
Merge pull request #38 from xp-framework/refactor/context
Browse files Browse the repository at this point in the history
Remove context from properties, methods, constructors and initializers
  • Loading branch information
thekid committed Mar 23, 2024
2 parents 7987576 + abe7c6f commit 88bee04
Show file tree
Hide file tree
Showing 9 changed files with 39 additions and 140 deletions.
25 changes: 9 additions & 16 deletions src/main/php/lang/reflection/Constructor.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,25 @@ public function toString() {
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null) {
public function newInstance(array $args= []) {
try {
$pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args;

// Workaround for non-public constructors: Set accessible, then manually
// invoke after creating an instance without invoking the constructor.
if ($context && !$this->reflect->isPublic()) {
if (Reflection::of($context)->is($this->class->name)) {
$instance= $this->class->newInstanceWithoutConstructor();
$this->reflect->setAccessible(true);
$this->reflect->invokeArgs($instance, $pass);
return $instance;
}
if (!$this->reflect->isPublic()) {
$instance= $this->class->newInstanceWithoutConstructor();
$this->reflect->setAccessible(true);
$this->reflect->invokeArgs($instance, $pass);
return $instance;
} else {
return $this->class->newInstanceArgs($pass);
}

return $this->class->newInstanceArgs($pass);
} catch (ArgumentCountError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (ReflectionException $e) {
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (Throwable $e) {

Expand Down
15 changes: 5 additions & 10 deletions src/main/php/lang/reflection/Initializer.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php namespace lang\reflection;

use ArgumentCountError, Error, ReflectionException, Throwable, TypeError;
use ArgumentCountError, Error, ReflectionException, ReflectionFunction, Throwable, TypeError;
use lang\Reflection;

/**
Expand All @@ -13,7 +13,7 @@ class Initializer extends Routine implements Instantiation {
private $class, $function;

static function __static() {
self::$NOOP= new \ReflectionFunction(function() { });
self::$NOOP= new ReflectionFunction(function() { });
}

/**
Expand Down Expand Up @@ -41,12 +41,11 @@ public function compoundName(): string { return strtr($this->class->name, '\\',
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null) {
public function newInstance(array $args= []) {
try {
$instance= $this->class->newInstanceWithoutConstructor();
} catch (ReflectionException $e) {
Expand All @@ -57,13 +56,9 @@ public function newInstance(array $args= [], $context= null) {

try {
$pass= PHP_VERSION_ID < 80000 && $args ? Routine::pass($this->reflect, $args) : $args;
$this->function->__invoke($instance, $pass, $context);
$this->function->__invoke($instance, $pass);
return $instance;
} catch (ArgumentCountError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (ReflectionException $e) {
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (Throwable $e) {

Expand Down
3 changes: 1 addition & 2 deletions src/main/php/lang/reflection/Instantiation.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ interface Instantiation {
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null);
public function newInstance(array $args= []);
}
14 changes: 2 additions & 12 deletions src/main/php/lang/reflection/Method.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,14 @@ public function closure($instance= null) {
*
* @param ?object $instance
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var
* @throws lang.reflection.CannotInvoke if prerequisites to the invocation fail
* @throws lang.reflection.InvocationFailed if invocation raises an exception
*/
public function invoke($instance, $args= [], $context= null) {

// Only allow invoking non-public methods when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::type($context)->is($this->reflect->class)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotInvoke($this, new ReflectionException('Trying to invoke non-public method'));
}
}

public function invoke(?object $instance, $args= []) {
try {
$pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args;
$this->reflect->setAccessible(true);
return $this->reflect->invokeArgs($instance, $pass);
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInvoke($this, $e);
Expand Down
33 changes: 8 additions & 25 deletions src/main/php/lang/reflection/Property.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,18 @@ public function constraint() {
* Gets this property's value
*
* @param ?object $instance
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var
* @throws lang.reflection.CannotAccess
* @throws lang.reflection.AccessingFailed if getting raises an exception
*/
public function get($instance, $context= null) {

// Only allow reading non-public properties when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::of($context)->is($this->reflect->getDeclaringClass()->name)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotAccess($this, new ReflectionException('Trying to read non-public property'));
}
}

public function get(?object $instance) {
try {
$this->reflect->setAccessible(true);
return $this->reflect->getValue($instance);
} catch (ReflectionException $e) {
throw new CannotAccess($this, $e);
} catch (Throwable $e) {
throw new AccessingFailed($this, $e);
}
}

Expand All @@ -69,23 +62,13 @@ public function get($instance, $context= null) {
*
* @param ?object $instance
* @param var $value
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var The given value
* @throws lang.reflection.CannotAccess
* @throws lang.reflection.AccessFailed if setting raises an exception
* @throws lang.reflection.AccessingFailed if setting raises an exception
*/
public function set($instance, $value, $context= null) {

// Only allow reading non-public properties when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::of($context)->is($this->reflect->getDeclaringClass()->name)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotAccess($this, new ReflectionException('Trying to write non-public property'));
}
}

public function set(?object $instance, $value) {
try {
$this->reflect->setAccessible(true);
$this->reflect->setValue($instance, $value);
return $value;
} catch (ReflectionException $e) {
Expand Down
8 changes: 3 additions & 5 deletions src/main/php/lang/reflection/Type.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,13 @@ public function initializer($function) {
return new Initializer($this->reflect);
} else if ($function instanceof \Closure) {
$reflect= new ReflectionFunction($function);
return new Initializer($this->reflect, $reflect, function($instance, $args, $context) use($function) {
return new Initializer($this->reflect, $reflect, function($instance, $args) use($function) {
return $function->call($instance, ...$args);
});
} else if ($this->reflect->hasMethod($function)) {
$reflect= $this->reflect->getMethod($function);
return new Initializer($this->reflect, $reflect, function($instance, $args, $context) use($reflect) {
if ($context && !$reflect->isPublic() && Reflection::of($context)->isInstance($instance)) {
$reflect->setAccessible(true);
}
return new Initializer($this->reflect, $reflect, function($instance, $args) use($reflect) {
$reflect->setAccessible(true);
return $reflect->invokeArgs($instance, $args);
});
}
Expand Down
12 changes: 0 additions & 12 deletions src/test/php/lang/reflection/unittest/InstantiationTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ public function newInstance_cannot_instantiate_using_non_public_constructor($mod
$t->newInstance();
}

#[Test, Expect(CannotInstantiate::class), Values(['private', 'protected'])]
public function constructor_cannot_instantiate_using_non_public_constructor($modifier) {
$t= $this->declare('{ '.$modifier.' function __construct() { } }');
$t->constructor()->newInstance();
}

#[Test, Values(['private', 'protected'])]
public function instantiate_with_non_public_constructor_in_context($modifier) {
$t= $this->declare('{
Expand All @@ -113,12 +107,6 @@ public function instantiate_with_constructor_promotion() {
Assert::equals($this, $t->constructor()->newInstance([$this])->value);
}

#[Test, Expect(CannotInstantiate::class)]
public function cannot_instantiate_with_private_constructor_in_incorrect_context() {
$t= $this->declare('{ private function __construct() { } }');
$t->constructor()->newInstance([], typeof($this));
}

#[Test, Expect(CannotInstantiate::class)]
public function interfaces_cannot_be_instantiated() {
Reflection::type(Runnable::class)->newInstance();
Expand Down
22 changes: 5 additions & 17 deletions src/test/php/lang/reflection/unittest/InvocationTest.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php namespace lang\reflection\unittest;

use lang\reflection\{CannotInvoke, InvocationFailed};
use lang\{CommandLine, IllegalAccessException, Reflection, Runnable};
use lang\{CommandLine, IllegalAccessException, Reflection, Runnable, IllegalStateException};
use test\{Assert, AssertionFailedError, Before, Expect, Test, Values};

class InvocationTest {
Expand Down Expand Up @@ -32,22 +32,10 @@ public function invoke_instance_method_from($context) {
Assert::equals('External', $method->invoke($this->fixtures[$context]->newInstance(), []));
}

#[Test, Values([['friend'], ['internal']]), Expect(CannotInvoke::class)]
public function cannot_invoke_non_public_method_by_default($method) {
$method= $this->fixtures['parent']->method($method);
$method->invoke($this->fixtures['parent']->newInstance(), []);
}

#[Test, Values([['parent', 'parent'], ['child', 'parent'], ['parent', 'child']])]
public function invoke_private_method_in_context($instance, $context) {
$method= $this->fixtures['parent']->method('internal');
Assert::equals('Internal', $method->invoke($this->fixtures[$instance]->newInstance(), [], $this->fixtures[$context]));
}

#[Test, Expect(CannotInvoke::class)]
public function cannot_invoke_private_method_in_incorrect_context() {
$method= $this->fixtures['parent']->method('internal');
$method->invoke($this->fixtures['parent']->newInstance(), [], typeof($this));
Assert::equals('Internal', $method->invoke($this->fixtures[$instance]->newInstance(), []));
}

#[Test, Expect(CannotInvoke::class)]
Expand Down Expand Up @@ -137,11 +125,11 @@ public function invocation_failed_target() {
}

#[Test]
public function cannot_invoke_target() {
$t= $this->declare('{ private static function fixture() { } }');
public function cannot_invoke_exceptions_target_member() {
$t= $this->declare('{ private static function fixture($a) { } }');
try {
$t->method('fixture')->invoke(null, []);
throw new AssertionFailedError('No exception was raised');
throw new IllegalStateException('No exception was raised');
} catch (CannotInvoke $expected) {
Assert::equals($t->method('fixture'), $expected->target());
}
Expand Down
47 changes: 6 additions & 41 deletions src/test/php/lang/reflection/unittest/PropertiesTest.class.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?php namespace lang\reflection\unittest;

use lang\reflection\{AccessingFailed, CannotAccess, Constraint, Modifiers};
use lang\{Primitive, Type, TypeIntersection, TypeUnion, XPClass};
use lang\{Primitive, Type, TypeIntersection, TypeUnion, XPClass, IllegalStateException};
use test\verify\Runtime;
use test\{Action, Assert, AssertionFailedError, Expect, Test, Values};
use test\{Action, Assert, Expect, Test, Values};

class PropertiesTest {
use TypeDefinition;
Expand Down Expand Up @@ -79,44 +79,20 @@ public function set_static() {
Assert::equals('Modified', $class::$fixture);
}

#[Test, Expect(CannotAccess::class)]
public function cannot_read_private_by_default() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->get(null);
}

#[Test, Expect(CannotAccess::class)]
public function cannot_write_private_by_default() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->set(null, 'Modified');
}

#[Test, Expect(CannotAccess::class)]
public function cannot_read_private_with_incorrect_context() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->get(null, typeof($this));
}

#[Test, Expect(CannotAccess::class)]
public function cannot_write_private_with_incorrect_context() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->set(null, 'Modified', typeof($this));
}

#[Test, Expect(AccessingFailed::class)]
public function type_mismatch() {
$type= $this->declare('{ private static array $fixture; }');
$type->property('fixture')->set(null, 1, $type);
$type->property('fixture')->set(null, 1);
}

#[Test]
public function private_instance_roundtrip() {
$type= $this->declare('{ private $fixture = "Test"; }');
$instance= $type->newInstance();
$property= $type->properties()->named('fixture');
$property->set($instance, 'Modified', $type);
$property->set($instance, 'Modified');

Assert::equals('Modified', $property->get($instance, $type));
Assert::equals('Modified', $property->get($instance));
}

#[Test]
Expand Down Expand Up @@ -242,7 +218,7 @@ public function string_representation_with_union_type_declaration() {
}

#[Test]
public function accessing_failed_target() {
public function set_accessing_failed_exceptions_target_member() {
$t= $this->declare('{ public static array $fixture; }');
try {
$t->property('fixture')->set(null, 1);
Expand All @@ -251,15 +227,4 @@ public function accessing_failed_target() {
Assert::equals($t->property('fixture'), $expected->target());
}
}

#[Test]
public function cannot_access_target() {
$t= $this->declare('{ private static $fixture; }');
try {
$t->property('fixture')->get(null);
throw new AssertionFailedError('No exception was raised');
} catch (CannotAccess $expected) {
Assert::equals($t->property('fixture'), $expected->target());
}
}
}

0 comments on commit 88bee04

Please sign in to comment.