问题
I've got a Validator
class and a UserValidator
class which extends from it.
My Validator
has a public method setRule(...)
with public visibility.
When I extend from it I want to change the visibility of the setRule(...)
parent method to private/protected within the child so that it's only visible for the child and no outsiders can call this method from the child.
Is that possible? If so, how could I achieve it?
回答1:
From an architectural point of view this is not recommended. As already stated in the comments the clean way would be to set your method to protected
so only children can access it.
I cannot think of a single use case that would put me in the need to call a public method on a parent class but where I am not allowed to call it on the child class.
That's against the Open/Closed principle. Classes should be open for extension, but not for modification.
Since that was not the question I'll provide a way how that can be achieved though. But note:
- This method makes use of an extra class which will be responsible for the instantiation
- It's a hack. This solution will not make use of PHP's native language features when throwing accessibility errors.
First let's define the classes you already had
<?php
class Validator {
public function setRule()
{
echo "Hello World";
}
}
class UserValidator extends Validator {
public $prop = 'PROPERTY';
}
There's nothing special here. So let's go on and create a custom exception class for the visibility error.
<?php
class MethodNotAccessibleException extends Exception {}
This exception will be thrown when we try to invoke a "pseudo-private" method on the child class.
Now we want to create the Class that will be responsible for instantiating your child class. It is basically just a wrapper that defines a lock
property which holds method names that should not be accessible.
<?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};
}
}
Our final step is the instantiation.
<?php
$userValidator = new PrivateInstanceCreator('UserValidator', []);
$userValidator->lock('setRule');
$userValidator->setRule(); //Will throw an exception
Instead of instantiating the class directly we'll do it by using our custom wrapper class. Of course you could handle it in the child class itself, but that's a way to accomplish your task without touching the classes directly.
Having said that, it is still a dirty hack whose usage should be avoided if possible. If you would instantiate the child class directly the inherited methods would still be public.
So if a developer has no knowlege about the wrapper class he'll have a hard time to figure out how to instantiate the child class properly.
Update:
To make the child class uninstantiable directly you can set the constructor to private
and call newInstanceWithoutConstructor()
from the reflection class, which is even dirtier, since that would make Dependency Injection for the class completely impossible. I'm just mentioning it for completenesses sake. Usage is still not recommended
来源:https://stackoverflow.com/questions/24426136/changing-the-visibility-scope-of-parent-methods-in-child-classes