从架构的角度来看,不建议这样做。如 cmets 中所述,干净的方法是将您的方法设置为 protected,以便只有孩子可以访问它。
我想不出一个用例会让我需要在父类上调用公共方法,但不允许我在子类上调用它。
这违反了开放/封闭原则。类应该对扩展开放,而不是对修改开放。
既然这不是问题,我将提供一种方法来实现这一点。但请注意:
- 此方法使用了一个负责实例化的额外类
- 这是一个黑客。此解决方案在引发可访问性错误时不会使用 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(),这更加肮脏,因为这将使类的Dependency Injection 完全不可能。我只是为了完整起见而提到它。 仍然不推荐使用