Как можно ограничить доступ к защищенным методам родительского класса, чтобы они были доступны только в классах-наследниках?
В 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(); // Ошибка, защищенный метод родительского класса не доступен извне
```
Собственно в результате общего мозгового штурма и компиляции всех предложенных идей нарисовалось вот такое решение: использование объекта обертки <br/> <pre><code class="php">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();</code></pre> <br/> <br/> Тут конечно есть некоторая проблема с подсказками в IDE, но можно сгенерированные классы обертки сохранять в директорию в виде класса и, как минимум VSCode, подхватит эти классы для IDE
Красиво сделать не получится. Но можно сделать так, чтобы хотя бы на тестах оно упало и показало, что нельзя так делать. <br/> <br/> <pre><code class="php"><?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</code></pre> <br/> <br/> Но у меня для вас совет: старайтесь как можно меньше использовать наследование. Из моего опыта могу сказать, что всякий раз, когда я с помощью наследования пытаюсь решить хоть сколько-нибудь сложную задачу, постоянно вылазят такие подводные камни, что жить не хочется. Используйте композицию и интерфейсы, и вам не придётся заниматься подобной ерундой. <br/> <br/> Вот минимально-инвазивное решение, которое позволит и наследование сохранить (если оно прям ну вот сильно надо), и решить проблему при помощи композиции, основанной на трейте. <br/> <br/> <pre><code class="php"><?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</code></pre>
Как вариант можно сбрасывать скоп внутри метода <code>$api->dependency</code> <br/> <br/> <pre><code class="php">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());
}
}
}</code></pre> <br/> <br/> Либо можно использовать белый список и проверить стек вызовов, если вызывающего класса нет в списке, то кидать исключение. <br/> <br/> <pre><code class="php">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());
}
}</code></pre>
Можно в качестве зависимости указывать не сам клас NodeA, а интерфейс NodeAInterface в котором будут описаны только нужные public методы.