Как можно ограничить доступ к защищенным методам родительского класса, чтобы они были доступны только в классах-наследниках?
В PHP можно ограничить доступ к защищенным методам родительского класса, чтобы они были доступны только в классах-наследниках с помощью ключевого слова `protected`. Пример: ```php class ParentClass { protected function protectedMethod() { // код метода } } class ChildClass extends ParentClass { public function childMethod() { $this->protectedMethod(); // Защищенный метод родительского класса доступен в классе-наследнике } } $child = new ChildClass(); $child->childMethod(); // Вызываем метод childMethod() из класса-наследника ``` Если нужно ограничить доступ к защищенным методам только внутри класса-наследника, то необходимо создать защищенный метод с тем же именем в классе-наследнике. В этом случае защищенный метод родительского класса не будет доступен извне. Пример: ```php class ParentClass { protected function protectedMethod() { // код метода } } class ChildClass extends ParentClass { protected function protectedMethod() { // Переопределяем защищенный метод родительского класса parent::protectedMethod(); // Вызываем защищенный метод родительского класса } } $child = new ChildClass(); $child->protectedMethod(); // Ошибка, защищенный метод родительского класса не доступен извне ```
Собственно в результате общего мозгового штурма и компиляции всех предложенных идей нарисовалось вот такое решение: использование объекта обертки
abstract class Node { protected function onInit(): void { echo __METHOD__ . PHP_EOL; } } spl_autoload_register(function ($classname) { // Если $classname оканчивается на Dependency и является наследником Node, // то сгенерировать динамический класс который в конструкторе принимает исходный объект // и содержит только публичные методы этого объекта // .. }); class NodeA extends Node { public function FUNC2() { echo __METHOD__ . PHP_EOL; } protected function onInit(): void { parent::onInit(); echo __METHOD__ . PHP_EOL; } } class NodeB extends Node { // Функция инициализации protected function onInit(): void { parent::onInit(); echo __METHOD__ . PHP_EOL; // Добавить зависимость (function (NodeADependency $a) { $a->FUNC2(); // Метод успешно вызывается так как он public $a->onInit(); // Метод не получится вызвать так как его тут просто НЕТ! })(new NodeA); } public function run() { $this->onInit(); } } (new NodeB)->run();
Тут конечно есть некоторая проблема с подсказками в IDE, но можно сгенерированные классы обертки сохранять в директорию в виде класса и, как минимум VSCode, подхватит эти классы для IDE
Красиво сделать не получится. Но можно сделать так, чтобы хотя бы на тестах оно упало и показало, что нельзя так делать.
<?php class Node { protected function func1() { print "NODE PARENT; "; } } class NodeA extends Node { public function FUNC2() { print "NODE A; "; } // Используем метод родителя внутри этого класса public function func1Overrided() { print "From parent: " . parent::func1(); } // Переопределяем метод так, чтобы его нельзя было использовать protected function func1() { throw new \Exception("Нельзя вызывать этот метод из NodeB"); } } class NodeB extends Node { // Функция инициализации public function onInit(NodeA $a): void { // Сделал так, чтобы не мокать api ) (function (?NodeA $a) { $a->FUNC2(); // Метод успешно вызывется так как он public $a->func1(); // Метод теперь кидает исключение, использовать не получится })($a); } } $nodeA = new NodeA; $nodeA->func1Overrided(); // Работает вызов метода funс1 из родителя $nodeB = new NodeB; $nodeB->onInit($nodeA); // Выдаёт ошибку, нельзя использовать метод func1 из класса NodeB
Но у меня для вас совет: старайтесь как можно меньше использовать наследование. Из моего опыта могу сказать, что всякий раз, когда я с помощью наследования пытаюсь решить хоть сколько-нибудь сложную задачу, постоянно вылазят такие подводные камни, что жить не хочется. Используйте композицию и интерфейсы, и вам не придётся заниматься подобной ерундой.
Вот минимально-инвазивное решение, которое позволит и наследование сохранить (если оно прям ну вот сильно надо), и решить проблему при помощи композиции, основанной на трейте.
<?php // Делаем общий трейт для всех классов trait Func1 { private function func1() { print "FUNC1; "; } } class Node { // Включаем трейт use Func1; } class NodeA extends Node { // Включаем трейт use Func1; public function FUNC2() { print "FUNC 2 NODEA; "; } } class NodeB extends Node { // Включаем трейт use Func1; // Функция инициализации public function onInit(NodeA $a): void { // Добавить зависимость (function (?NodeA $a) { $a->FUNC2(); // Метод успешно вызывется так как он public $a->func1(); // Метод использовать не получится, т.к. он private })($a); } } $nodeA = new NodeA; $nodeB = new NodeB; $nodeB->onInit($nodeA); // Выдаёт ошибку, нельзя использовать метод func1 из класса NodeB
Как вариант можно сбрасывать скоп внутри метода$api->dependency
class Node { protected function func1() { } } class NodeA extends Node { public function FUNC2() {} } class NodeB extends Node { // Функция инициализации public function __construct() { (new Api())->dependency( function (?NodeA $a) { $a->FUNC2(); // Метод успешно вызывется так как он public $a->func1(); // Call to protected method Node::func1() from global scope } ); } } class Api { function dependency(callable $dependency) { if ($dependency instanceof Closure) { $dependency = Closure::bind($dependency, null, null); $dependency(new NodeA()); } } }
Либо можно использовать белый список и проверить стек вызовов, если вызывающего класса нет в списке, то кидать исключение.
class Node { private array $whiteList = [ NodeA::class ]; protected function func1() { $scope = debug_backtrace(2, limit: 2)[1]['class']; if (!in_array($scope, $this->whiteList)) { throw new Exception('Нельзя вызвать метод для данного класса '. $scope); } } } class NodeA extends Node { public function FUNC2() {} } class NodeB extends Node { // Функция инициализации public function __construct() { (function (?NodeA $a) { $a->FUNC2(); // Метод успешно вызывется так как он public $a->func1(); // Нельзя вызвать метод для данного класса NodeB })(new NodeA()); } }
Можно в качестве зависимости указывать не сам клас NodeA, а интерфейс NodeAInterface в котором будут описаны только нужные public методы.