Changing the visibility scope of parent methods in child classes

孤人 提交于 2019-12-25 04:24:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!