#php #methods #parent-child #visibility #extends
#php #методы #родитель-потомок #видимость #расширяет
Вопрос:
У меня есть Validator
класс и UserValidator
класс, который расширяется от него.
У My Validator
есть общедоступный метод setRule(...)
с общедоступной видимостью.
Когда я расширяюсь из него, я хочу изменить видимость setRule(...)
родительского метода на закрытый / защищенный внутри дочернего элемента, чтобы он был виден только для дочернего элемента, и никто посторонний не мог вызвать этот метод из дочернего элемента.
Возможно ли это? Если да, то как я мог этого добиться?
Комментарии:
1. Используйте
protected
видимость, а неpublic
2. Но это означает, что при использовании моего
Validator
классаsetRule()
доступ к методу невозможен, потому что тогда метод будет виден только самому классу и только его дочерним элементам.3. Извините, я думал, это то, о чем вы просили
4. Нет, вы не можете произвольно изменять видимость методов в зависимости от обстоятельств… хотя отражение может имитировать это, это грязный хак, если вам нужно его использовать
Ответ №1:
С архитектурной точки зрения это не рекомендуется. Как уже говорилось в комментариях, чистым способом было бы установить для вашего метода protected
значение, чтобы к нему могли получить доступ только дочерние классы.
Я не могу придумать ни одного варианта использования, который поставил бы меня перед необходимостью вызывать открытый метод в родительском классе, но где мне не разрешено вызывать его в дочернем классе.
Это противоречит принципу Open / Closed . Классы должны быть открыты для расширения, но не для модификации.
Поскольку это был не вопрос, я расскажу, как этого можно достичь. Но обратите внимание:
- Этот метод использует дополнительный класс, который будет отвечать за создание экземпляра
- Это хак. Это решение не будет использовать возможности родного языка PHP при возникновении ошибок доступности.
Сначала давайте определим классы, которые у вас уже были
<?php
class Validator {
public function setRule()
{
echo "Hello World";
}
}
class UserValidator extends Validator {
public $prop = 'PROPERTY';
}
Здесь нет ничего особенного. Итак, давайте продолжим и создадим пользовательский класс исключений для ошибки видимости.
<?php
class MethodNotAccessibleException extends Exception {}
Это исключение будет выдаваться при попытке вызвать «псевдоприватный» метод в дочернем классе.
Теперь мы хотим создать класс, который будет отвечать за создание экземпляра вашего дочернего класса. По сути, это просто оболочка, которая определяет lock
свойство, содержащее имена методов, которые не должны быть доступны.
<?php
class PrivateInstanceCreator {
protected $reflectionClass;
protected $lock = [];
protected $instance;
public function __construct($classname, $args = [])
{
// We'll store an instance of the reflection class
// and an instance of the real class
$this->reflectionClass = new ReflectionClass($classname);
$this->instance = $this->reflectionClass->newInstanceArgs($args);
return $this;
}
// The lock method is able to make a method on the
// target class "pseudo-private"
public function lock($method)
{
$this->lock[] = $method;
return $this;
}
// Some real magic is going on here
// Remember. This class is a wrapper for the real class
// if a method is invoked we look for the method
// in the real instance and invoke it...
public function __call($method, $args)
{
// ... but as soon as this method is defined as
// locked, we'll raise an exception that the method
// is private
if(in_array($method, $this->lock))
{
$reflectionMethod = $this->reflectionClass->getMethod($method);
if($reflectionMethod->isPublic())
throw new MethodNotAccessibleException('Method: __' . $method . '__ is private and could not be invoked');
}
return call_user_func_array([$this->instance, $method], $args);
}
// The same goes for properties
// But in this case we'll do no protection
public function __get($prop)
{
return $this->instance->{$prop};
}
}
Наш последний шаг — создание экземпляра.
<?php
$userValidator = new PrivateInstanceCreator('UserValidator', []);
$userValidator->lock('setRule');
$userValidator->setRule(); //Will throw an exception
Вместо непосредственного создания экземпляра класса мы сделаем это с помощью нашего пользовательского класса-оболочки.
Конечно, вы могли бы справиться с этим в самом дочернем классе, но это способ выполнить вашу задачу, не касаясь классов напрямую.
Сказав это, это все еще грязный хак, использования которого следует избегать, если это возможно. Если вы создадите экземпляр дочернего класса напрямую, унаследованные методы все равно будут общедоступными.
Поэтому, если разработчик не имеет знаний о классе-оболочке, ему будет трудно понять, как правильно создать экземпляр дочернего класса.
Обновить:
Чтобы сделать дочерний класс деинсталлируемым напрямую, вы можете установить конструктор в private
и вызвать newInstanceWithoutConstructor()
из класса reflection, что еще более грязно, поскольку это сделало бы внедрение зависимостей для класса полностью невозможным. Я просто упоминаю об этом для полноты. Использование по-прежнему не рекомендуется