mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #10457 from creative-commoners/pulls/5/rescue-master-extensions-expose-public
API Rescue Master Branch PR: Only expose public extension methods
This commit is contained in:
commit
250a75b233
@ -4,18 +4,20 @@ namespace SilverStripe\Core;
|
||||
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Allows an object to declare a set of custom methods
|
||||
*/
|
||||
trait CustomMethods
|
||||
{
|
||||
|
||||
/**
|
||||
* Custom method sources
|
||||
*
|
||||
* @var array
|
||||
* @var array Array of class names (lowercase) to list of methods.
|
||||
* The list of methods will have lowercase keys. Each value in this array
|
||||
* can be a callable, array, or string callback
|
||||
*/
|
||||
protected static $extra_methods = [];
|
||||
|
||||
@ -27,9 +29,10 @@ trait CustomMethods
|
||||
protected $extra_method_registers = [];
|
||||
|
||||
/**
|
||||
* Non-custom methods
|
||||
* Non-custom public methods.
|
||||
*
|
||||
* @var array
|
||||
* @var array Array of class names (lowercase) to list of methods.
|
||||
* The list of methods will have lowercase keys and correct-case values.
|
||||
*/
|
||||
protected static $built_in_methods = [];
|
||||
|
||||
@ -74,17 +77,16 @@ trait CustomMethods
|
||||
);
|
||||
}
|
||||
|
||||
// Call without setOwner
|
||||
if (empty($config['callSetOwnerFirst'])) {
|
||||
return $obj->$method(...$arguments);
|
||||
}
|
||||
|
||||
/** @var Extension $obj */
|
||||
// Call on object
|
||||
try {
|
||||
$obj->setOwner($this);
|
||||
if ($obj instanceof Extension) {
|
||||
$obj->setOwner($this);
|
||||
}
|
||||
return $obj->$method(...$arguments);
|
||||
} finally {
|
||||
$obj->clearOwner();
|
||||
if ($obj instanceof Extension) {
|
||||
$obj->clearOwner();
|
||||
}
|
||||
}
|
||||
}
|
||||
case isset($config['wrap']): {
|
||||
@ -160,66 +162,87 @@ trait CustomMethods
|
||||
return null;
|
||||
}
|
||||
// Lazy define methods
|
||||
if (!isset(self::$extra_methods[static::class])) {
|
||||
$lowerClass = strtolower(static::class);
|
||||
if (!isset(self::$extra_methods[$lowerClass])) {
|
||||
$this->defineMethods();
|
||||
}
|
||||
|
||||
if (isset(self::$extra_methods[static::class][strtolower($method)])) {
|
||||
return self::$extra_methods[static::class][strtolower($method)];
|
||||
}
|
||||
return null;
|
||||
return self::$extra_methods[$lowerClass][strtolower($method)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of all the methods available on this object
|
||||
*
|
||||
* @param bool $custom include methods added dynamically at runtime
|
||||
* @return array
|
||||
* @return array Map of method names with lowercase keys
|
||||
*/
|
||||
public function allMethodNames($custom = false)
|
||||
{
|
||||
$class = static::class;
|
||||
if (!isset(self::$built_in_methods[$class])) {
|
||||
self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($this ?? ''));
|
||||
}
|
||||
$methods = static::findBuiltInMethods();
|
||||
|
||||
if ($custom && isset(self::$extra_methods[$class])) {
|
||||
return array_merge(self::$built_in_methods[$class], array_keys(self::$extra_methods[$class] ?? []));
|
||||
} else {
|
||||
return self::$built_in_methods[$class];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $extension
|
||||
* @return array
|
||||
*/
|
||||
protected function findMethodsFromExtension($extension)
|
||||
{
|
||||
if (method_exists($extension, 'allMethodNames')) {
|
||||
if ($extension instanceof Extension) {
|
||||
try {
|
||||
$extension->setOwner($this);
|
||||
$methods = $extension->allMethodNames(true);
|
||||
} finally {
|
||||
$extension->clearOwner();
|
||||
}
|
||||
} else {
|
||||
$methods = $extension->allMethodNames(true);
|
||||
}
|
||||
} else {
|
||||
$class = get_class($extension);
|
||||
if (!isset(self::$built_in_methods[$class])) {
|
||||
self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($extension ?? ''));
|
||||
}
|
||||
$methods = self::$built_in_methods[$class];
|
||||
// Query extra methods
|
||||
$lowerClass = strtolower(static::class);
|
||||
if ($custom && isset(self::$extra_methods[$lowerClass])) {
|
||||
$methods = array_merge(self::$extra_methods[$lowerClass], $methods);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the methods from an object property (which is an {@link Extension}) to this object.
|
||||
* Get all public built in methods for this class
|
||||
*
|
||||
* @param string|object $class Class or instance to query methods from (defaults to static::class)
|
||||
* @return array Map of methods with lowercase key name
|
||||
*/
|
||||
protected static function findBuiltInMethods($class = null)
|
||||
{
|
||||
$class = is_object($class) ? get_class($class) : ($class ?: static::class);
|
||||
$lowerClass = strtolower($class);
|
||||
if (isset(self::$built_in_methods[$lowerClass])) {
|
||||
return self::$built_in_methods[$lowerClass];
|
||||
}
|
||||
|
||||
// Build new list
|
||||
$reflection = new ReflectionClass($class);
|
||||
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
self::$built_in_methods[$lowerClass] = [];
|
||||
foreach ($methods as $method) {
|
||||
$name = $method->getName();
|
||||
self::$built_in_methods[$lowerClass][strtolower($name)] = $name;
|
||||
}
|
||||
return self::$built_in_methods[$lowerClass];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all methods on the given object.
|
||||
*
|
||||
* @param object $object
|
||||
* @return array
|
||||
*/
|
||||
protected function findMethodsFrom($object)
|
||||
{
|
||||
// Respect "allMethodNames"
|
||||
if (method_exists($object, 'allMethodNames')) {
|
||||
if ($object instanceof Extension) {
|
||||
try {
|
||||
$object->setOwner($this);
|
||||
$methods = $object->allMethodNames(true);
|
||||
} finally {
|
||||
$object->clearOwner();
|
||||
}
|
||||
} else {
|
||||
$methods = $object->allMethodNames(true);
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
// Get methods
|
||||
return static::findBuiltInMethods($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the methods from an object property.
|
||||
*
|
||||
* @param string $property the property name
|
||||
* @param string|int $index an index to use if the property is an array
|
||||
@ -228,37 +251,31 @@ trait CustomMethods
|
||||
protected function addMethodsFrom($property, $index = null)
|
||||
{
|
||||
$class = static::class;
|
||||
$extension = ($index !== null) ? $this->{$property}[$index] : $this->$property;
|
||||
$object = ($index !== null) ? $this->{$property}[$index] : $this->$property;
|
||||
|
||||
if (!$extension) {
|
||||
if (!$object) {
|
||||
throw new InvalidArgumentException(
|
||||
"Object->addMethodsFrom(): could not add methods from {$class}->{$property}[$index]"
|
||||
);
|
||||
}
|
||||
|
||||
$methods = $this->findMethodsFromExtension($extension);
|
||||
if ($methods) {
|
||||
if ($extension instanceof Extension) {
|
||||
Deprecation::notice(
|
||||
'5.0',
|
||||
'Register custom methods from extensions with addCallbackMethod.'
|
||||
. ' callSetOwnerFirst will be removed in 5.0'
|
||||
);
|
||||
}
|
||||
$methodInfo = [
|
||||
'property' => $property,
|
||||
'index' => $index,
|
||||
'callSetOwnerFirst' => $extension instanceof Extension,
|
||||
];
|
||||
$methods = $this->findMethodsFrom($object);
|
||||
if (!$methods) {
|
||||
return;
|
||||
}
|
||||
$methodInfo = [
|
||||
'property' => $property,
|
||||
'index' => $index,
|
||||
];
|
||||
|
||||
$newMethods = array_fill_keys($methods ?? [], $methodInfo);
|
||||
$newMethods = array_fill_keys(array_keys($methods), $methodInfo);
|
||||
|
||||
if (isset(self::$extra_methods[$class])) {
|
||||
self::$extra_methods[$class] =
|
||||
array_merge(self::$extra_methods[$class], $newMethods);
|
||||
} else {
|
||||
self::$extra_methods[$class] = $newMethods;
|
||||
}
|
||||
// Merge with extra_methods
|
||||
$lowerClass = strtolower($class);
|
||||
if (isset(self::$extra_methods[$lowerClass])) {
|
||||
self::$extra_methods[$lowerClass] = array_merge(self::$extra_methods[$lowerClass], $newMethods);
|
||||
} else {
|
||||
self::$extra_methods[$lowerClass] = $newMethods;
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,26 +296,18 @@ trait CustomMethods
|
||||
);
|
||||
}
|
||||
|
||||
$methods = $this->findMethodsFromExtension($extension);
|
||||
if ($methods) {
|
||||
foreach ($methods as $method) {
|
||||
if (!isset(self::$extra_methods[$class][$method])) {
|
||||
continue;
|
||||
}
|
||||
$lowerClass = strtolower($class);
|
||||
if (!isset(self::$extra_methods[$lowerClass])) {
|
||||
return;
|
||||
}
|
||||
$methods = $this->findMethodsFrom($extension);
|
||||
|
||||
$methodInfo = self::$extra_methods[$class][$method];
|
||||
// Unset by key
|
||||
self::$extra_methods[$lowerClass] = array_diff_key(self::$extra_methods[$lowerClass], $methods);
|
||||
|
||||
// always check for property, AND
|
||||
// check for index only if provided
|
||||
if ((isset($methodInfo['property']) && $methodInfo['property'] === $property) &&
|
||||
(!$index || ($index && isset($methodInfo['index']) && $methodInfo['index'] === $index))) {
|
||||
unset(self::$extra_methods[$class][$method]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(self::$extra_methods[$class])) {
|
||||
unset(self::$extra_methods[$class]);
|
||||
}
|
||||
// Clear empty list
|
||||
if (empty(self::$extra_methods[$lowerClass])) {
|
||||
unset(self::$extra_methods[$lowerClass]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,7 +320,7 @@ trait CustomMethods
|
||||
*/
|
||||
protected function addWrapperMethod($method, $wrap)
|
||||
{
|
||||
self::$extra_methods[static::class][strtolower($method)] = [
|
||||
self::$extra_methods[strtolower(static::class)][strtolower($method)] = [
|
||||
'wrap' => $wrap,
|
||||
'method' => $method
|
||||
];
|
||||
@ -326,7 +335,7 @@ trait CustomMethods
|
||||
*/
|
||||
protected function addCallbackMethod($method, $callback)
|
||||
{
|
||||
self::$extra_methods[static::class][strtolower($method)] = [
|
||||
self::$extra_methods[strtolower(static::class)][strtolower($method)] = [
|
||||
'callback' => $callback,
|
||||
];
|
||||
}
|
||||
|
@ -104,14 +104,6 @@ trait Extensible
|
||||
$this->afterExtendCallbacks[$method][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0.0:5.0.0 Extensions and methods are now lazy-loaded
|
||||
*/
|
||||
protected function constructExtensions()
|
||||
{
|
||||
Deprecation::notice('5.0', 'constructExtensions does not need to be invoked and will be removed in 5.0');
|
||||
}
|
||||
|
||||
protected function defineMethods()
|
||||
{
|
||||
$this->defineMethodsCustom();
|
||||
@ -131,7 +123,7 @@ trait Extensible
|
||||
{
|
||||
$extensions = $this->getExtensionInstances();
|
||||
foreach ($extensions as $extensionClass => $extensionInstance) {
|
||||
foreach ($this->findMethodsFromExtension($extensionInstance) as $method) {
|
||||
foreach ($this->findMethodsFrom($extensionInstance) as $method) {
|
||||
$this->addCallbackMethod($method, function ($inst, $args) use ($method, $extensionClass) {
|
||||
/** @var Extensible $inst */
|
||||
$extension = $inst->getExtensionInstance($extensionClass);
|
||||
@ -199,11 +191,8 @@ trait Extensible
|
||||
// unset some caches
|
||||
$subclasses = ClassInfo::subclassesFor($class);
|
||||
$subclasses[] = $class;
|
||||
|
||||
if ($subclasses) {
|
||||
foreach ($subclasses as $subclass) {
|
||||
unset(self::$extra_methods[$subclass]);
|
||||
}
|
||||
foreach ($subclasses as $subclass) {
|
||||
unset(self::$extra_methods[strtolower($subclass)]);
|
||||
}
|
||||
|
||||
Config::modify()
|
||||
@ -261,10 +250,8 @@ trait Extensible
|
||||
// unset some caches
|
||||
$subclasses = ClassInfo::subclassesFor($class);
|
||||
$subclasses[] = $class;
|
||||
if ($subclasses) {
|
||||
foreach ($subclasses as $subclass) {
|
||||
unset(self::$extra_methods[$subclass]);
|
||||
}
|
||||
foreach ($subclasses as $subclass) {
|
||||
unset(self::$extra_methods[strtolower($subclass)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,25 +390,19 @@ trait Extensible
|
||||
* all results into an array
|
||||
*
|
||||
* @param string $method the method name to call
|
||||
* @param mixed $a1
|
||||
* @param mixed $a2
|
||||
* @param mixed $a3
|
||||
* @param mixed $a4
|
||||
* @param mixed $a5
|
||||
* @param mixed $a6
|
||||
* @param mixed $a7
|
||||
* @param mixed ...$arguments List of arguments
|
||||
* @return array List of results with nulls filtered out
|
||||
*/
|
||||
public function invokeWithExtensions($method, &$a1 = null, &$a2 = null, &$a3 = null, &$a4 = null, &$a5 = null, &$a6 = null, &$a7 = null)
|
||||
public function invokeWithExtensions($method, &...$arguments)
|
||||
{
|
||||
$result = [];
|
||||
if (method_exists($this, $method ?? '')) {
|
||||
$thisResult = $this->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||
$thisResult = $this->$method(...$arguments);
|
||||
if ($thisResult !== null) {
|
||||
$result[] = $thisResult;
|
||||
}
|
||||
}
|
||||
$extras = $this->extend($method, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||
$extras = $this->extend($method, ...$arguments);
|
||||
|
||||
return $extras ? array_merge($result, $extras) : $result;
|
||||
}
|
||||
@ -438,22 +419,16 @@ trait Extensible
|
||||
* The extension methods are defined during {@link __construct()} in {@link defineMethods()}.
|
||||
*
|
||||
* @param string $method the name of the method to call on each extension
|
||||
* @param mixed $a1
|
||||
* @param mixed $a2
|
||||
* @param mixed $a3
|
||||
* @param mixed $a4
|
||||
* @param mixed $a5
|
||||
* @param mixed $a6
|
||||
* @param mixed $a7
|
||||
* @param mixed &...$arguments
|
||||
* @return array
|
||||
*/
|
||||
public function extend($method, &$a1 = null, &$a2 = null, &$a3 = null, &$a4 = null, &$a5 = null, &$a6 = null, &$a7 = null)
|
||||
public function extend($method, &...$arguments)
|
||||
{
|
||||
$values = [];
|
||||
|
||||
if (!empty($this->beforeExtendCallbacks[$method])) {
|
||||
foreach (array_reverse($this->beforeExtendCallbacks[$method] ?? []) as $callback) {
|
||||
$value = call_user_func_array($callback, [&$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7]);
|
||||
foreach (array_reverse($this->beforeExtendCallbacks[$method ?? '']) as $callback) {
|
||||
$value = call_user_func_array($callback, $arguments);
|
||||
if ($value !== null) {
|
||||
$values[] = $value;
|
||||
}
|
||||
@ -462,22 +437,16 @@ trait Extensible
|
||||
}
|
||||
|
||||
foreach ($this->getExtensionInstances() as $instance) {
|
||||
if (method_exists($instance, $method ?? '')) {
|
||||
try {
|
||||
$instance->setOwner($this);
|
||||
$value = $instance->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||
} finally {
|
||||
$instance->clearOwner();
|
||||
}
|
||||
if ($value !== null) {
|
||||
$values[] = $value;
|
||||
}
|
||||
// Prefer `extend` prefixed methods
|
||||
$value = $instance->invokeExtension($this, $method, ...$arguments);
|
||||
if ($value !== null) {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->afterExtendCallbacks[$method])) {
|
||||
foreach (array_reverse($this->afterExtendCallbacks[$method] ?? []) as $callback) {
|
||||
$value = call_user_func_array($callback, [&$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7]);
|
||||
foreach (array_reverse($this->afterExtendCallbacks[$method ?? '']) as $callback) {
|
||||
$value = call_user_func_array($callback, $arguments);
|
||||
if ($value !== null) {
|
||||
$values[] = $value;
|
||||
}
|
||||
|
@ -115,4 +115,31 @@ abstract class Extension
|
||||
// Split out both args and service name
|
||||
return strtok(strtok($extensionStr ?? '', '(') ?? '', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke extension point. This will prefer explicit `extend` prefixed
|
||||
* methods.
|
||||
*
|
||||
* @param object $owner
|
||||
* @param string $method
|
||||
* @param array &...$arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function invokeExtension($owner, $method, &...$arguments)
|
||||
{
|
||||
// Prefer `extend` prefixed methods
|
||||
$instanceMethod = method_exists($this, "extend{$method}")
|
||||
? "extend{$method}"
|
||||
: (method_exists($this, $method) ? $method : null);
|
||||
if (!$instanceMethod) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->setOwner($owner);
|
||||
return $this->$instanceMethod(...$arguments);
|
||||
} finally {
|
||||
$this->clearOwner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user