Привязка области действия метода PHP - PullRequest
0 голосов
/ 02 ноября 2018

Меня смущает то, как PHP вызывает метод в иерархии parent-children. Вот код

class A {
        private function foo() {
            echo "A!";
        }
        public function test() {
            $this->foo();
        }
 }

class C extends A {
       public function foo() {
            echo 'C!';
       }
}

$c = new C();
$c->test();  

Выход составляет A!

рассмотрим другой пример, но измените только видимость метода foo () в классе A на public .

class A {
        public function foo() {
            echo "A!";
        }
        public function test() {
            $this->foo();
        }
 }

 class C extends A {
        public function foo() {
            echo 'C!';
        }
 }

 $c = new C();
 $c->test();  

Этот вывод C!

Любые объяснения приветствуются.

Ответы [ 5 ]

0 голосов
/ 02 ноября 2018

Правило: private и final методы объекта всегда будут вызываться напрямую , без обращения к таблице переопределения.

Это правило запекается в двигателе :

/* Check if this calls a known method on $this */
if (opline->op1_type == IS_UNUSED && opline->op2_type == IS_CONST &&
        CG(active_class_entry) && zend_is_scope_known()) {
    zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op2) + 1);
    fbc = zend_hash_find_ptr(&CG(active_class_entry)->function_table, lcname);

    /* We only know the exact method that is being called if it is either private or final.
     * Otherwise an overriding method in a child class may be called. */
    if (fbc && !(fbc->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_FINAL))) {
        fbc = NULL;
    }
}

«Почему», спросите вы? Ответ: потому что это так. В языковом дизайне это называется «скрытием имени», и от языка зависит, как работает скрытие имени . Взять, к примеру, C ++. Он имеет четко определенные, и сложные правила сокрытия имен. У PHP есть свои правила. Они отличаются от C ++. Но они уникальны для PHP. Это просто то, что вы должны запомнить о языке.

Я признаю, что документы могли бы лучше объяснить это, однако.

0 голосов
/ 02 ноября 2018

Я думаю, что ответ лежит на официальной документации. Документ здесь

В нестатических контекстах вызываемый класс будет классом экземпляра объекта. Так как $ this-> будет пытаться вызывать закрытые методы из той же области

Как примечание, хотя это могло бы устранить мою путаницу, просто сказать, что "так оно и есть", кажется недостаточно. Потому что это странное поведение. Это не так ясно, как поздняя статическая привязка или отношение к , называемому scope class C (что можно проверить с помощью метода get_called_class () в test ()).

0 голосов
/ 02 ноября 2018

Это поведение в большинстве языков (по замыслу).

Если метод private, $this->method() будет вызывать его напрямую (игнорировать метод производного класса)

Если метод virtual (эта концепция взята из C ++, в PHP и Java все открытые / защищенные методы virtual), $this->method() вызовет метод производного класса.

Правило - это правило, это проблема ООП. Например, в ООП мы можем захотеть убедиться, что метод никогда не был переопределен, тогда мы можем сделать его private. Если разработчик отменяет это случайно, и, следовательно, вводит странное поведение, если родитель внезапно вызвал реализацию ребенка. Благодаря @ deceze

0 голосов
/ 02 ноября 2018

Private foo () является частью класса A, и A не знает о существовании класса C. Это может работать без него. Расширяя класс A, C наследует все открытые и защищенные методы A, поэтому доступен test (). Вы бы вызвали закрытый метод foo () класса A из метода test (), а не из метода C. Расширение работает только в одном направлении, вы не сможете вызывать методы из C изнутри A. A может быть создается без C, и ничто не может гарантировать, что другой класс расширит его.

0 голосов
/ 02 ноября 2018

Хорошо, позвольте мне попробовать [Опять]

В основном оба метода существуют бок о бок.

Под частной структурой подразумевается, что эта вещь (метод или свойство) не видна, недоступна или доступна для записи ничем, кроме класса, в котором она была определена. Поэтому дети не могут ни использовать ее, ни модифицировать ее. Это поведение частного.

Причина (причина), лежащая в основе концепции, заключается в том, чтобы гарантировать определенную функциональность (или значение, свойства) независимо от того, как расширяется класс. Это хорошо, потому что при разработке родительского класса мы можем не знать, как он будет использоваться какими-либо расширяющими классами, и могут существовать функциональные возможности, которые являются критическими и не должны изменяться. Это дает нам возможность гарантировать это в нашем коде, не зная реализации потомков.

Одна из причин, по которой они могут существовать бок о бок, заключается в том, что внутри дочернего класса при вызове $this->foo() нет никакой двусмысленности относительно того, что foo вызывается только как общедоступная версия C::foo() видимая для class C (дочерний элемент ) учебный класс.

class A {
    private function foo() {
        echo "A!";
    }
    public function parentTest() {  //formerly A::test() 
        //if we used C::foo() from here Overriding A::foo().
        //having a concept of private would lose all meaning
        //because we would be allowing the child to modify 
        // the functionality of private A:foo()
        $this->foo();
    }
 }

class C extends A {
   public function foo() {
        echo 'C!';
   }
   public function childTest() {
       //A::foo() cannot be used from here
        //so PHP knows to use C::foo()
        $this->foo(); 
    }
}

Короче говоря, для дочернего класса C::foo() доступен только один Foo, поэтому его нельзя спутать с частной версией A::foo() PHP. Таким образом, оба метода могут существовать без переопределения дочерним методом метода родителей.

Не позвольте мне запутать вас больше здесь, потому что в некотором смысле метод childs перекрывает родителей. Мы можем увидеть это, если позвоним $obj->foo() из глобального пространства. Если мы не определяем foo в дочернем элементе C, мы получаем ошибку. Но если мы определяем публичный в C, он используется. Еще одна вещь, которая показывает, что она перезаписывает (в некоторых отношениях), если мы устанавливаем копию родительского элемента A::foo() как окончательную. В этом случае, если мы попытаемся определить C::foo(), мы получим ошибку, как и следовало ожидать.

Но (вот важная часть), ни один из них не влияет на функциональность личных вещей в том, что касается родителей, и поэтому они не нарушают частный принцип.


Теперь для родителей вызовите свой собственный метод.

Здесь большинство людей смущаются, и вы спрашиваете:

При вызове того, что для всех намерений и целей, C :: test, почему он предпочитает закрытый A :: foo публичному C :: foo

Это не так, потому что вы вызываете приватный метод A::foo() из другого метода A::test() в том же классе class A, где был определен приватный метод.

Если бы он предпочел метод потомка своему собственному, тогда не было бы смысла иметь частную видимость, потому что мы бы просто нарушили его, позволив ребенку изменить метод. Другими словами, мы будем использовать функциональность ребенка вместо функции, помеченной как private.

Действительно, это единственный разумный способ, которым это могло бы работать. В том же ключе, если вы перезаписываете A::test() с тем же точным кодом, но в class C или C::test() тот же самый вызов теперь будет обращаться к общедоступному методу, потому что это единственный видимый внутри класса C, где новый вызов пришел от.

Summery

Это единственный логический способ работы private, если классу, определяющему приватный метод, было разрешено использовать дочернее определение метода, это полностью устранило бы причину наличия приватности в первую очередь, и я бы не стал пытаться объяснить это. Потому что тогда это будет просто то, что мы называем protected, которое у нас уже есть.

UPDATE

Так, иногда он вызывает метод из своего собственного, иногда он вызывает метод из родительского.

Не совсем верно. Вызовы приватного метода (foo), поступающие от метода, определенного только в parent (A), всегда будут обращаться к приватному методу parent (A) (foo). Вызовы, поступающие от метода, определенного в child (C), получат доступ либо к методу Child (C) (foo), либо к ошибке при попытке доступа к закрытому методу Parent (A) (foo).

Case1: , если A:foo() вызывается из A (независимо от экземпляра), он будет вызывать свой закрытый метод независимо от того, существует ли метод из дочернего класса, который переопределяет foo. В этом контексте is called from within A означает вызов из любого метода, определенного в A и не перезаписанного в B. Вызов foo происходит из класса A.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function parentTest() {
        //parentTest Is not overwritten in C
        //so when C::parentTest() is called that call goes here
        //inside the A class where foo is defined.
        $this->foo();
    }
}

class C extends A {
   private function foo() {
        echo "C!\n";
   }
}

//this is called from A
//because there is no method named parentTest outside of A
(new C)->parentTest(); //A!

См. Ошибку, которую вы совершаете: (new C)->parentTest(); физически не существует в C. Он включен в C по наследству, и его область действия все еще находится в родительском классе, когда речь идет о частном доступе, потому что foo и parentTest существуют только в классе A.

Поэтому он имеет доступ к закрытым методам класса A, потому что это класс A, даже если объект является экземпляром C.

Вы можете полностью удалить класс C. Поведение будет таким же.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function parentTest() {
        $this->foo();
    }
}
//the path the code follows to execute is identical to the previous example.

(new A)->parentTest(); //A!

Case2: , если A:foo() вызывается напрямую от дочернего элемента A и не перезаписан , вы получаете доступ к закрытому методу ошибки. Звонок на foo поступает из класса C.

class A {
    private function foo() {
        echo "A!\n";
    }
}

class C extends A {
   public function childTest() {
        //A::foo() is not visable, and no C::foo() exists
        $this->foo();
   }
}

//this comes direct from Class C, 
//which cannot directly access Class A's private scope
(new C)->childTest(); //Error: Call to private method A::foo()

Case3: , если A:foo() вызывается из дочернего элемента A, а переопределяется , он вызывает метод Child. is called from a child of A означает любой метод, определенный в C, независимо от того, переопределяет ли он (открытый / защищенный) метод A. Звонок foo поступает из класса C.

class A {
    private function foo() {
        echo "A!\n";
    }
}

class C extends A {
   public function foo() {
        echo "C!\n";
   }

   public function test() {
        //C::test() calls it's foo function
        //because the call comes from here not inside the A class
        $this->foo();
   }
}

//this is called from C, the method childTest is defined in C

(new C)->childTest(); //C!

Это верно независимо от того, существует ли публичный метод с тем же именем (test) в родительском элементе.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function test() {
        //this is a public method that is overwritten as normal by C
        //when this is called the, C::test() is called as normal 
        //because of inheritance
        $this->foo();
    }
}

class C extends A {
   public function foo() {
        echo "C!\n";
   }

   public function test() {
        //C::test() calls it's foo function
        //again the call comes from here not inside the A class
        $this->foo();
   }
}

//This is called from C, because test is overwritten in C and is visible (public)

(new C)->test(); //C!

Как я показал в первом примере в этом Обновлении (за исключением его обратной версии), вы можете полностью удалить класс A, и способ выполнения кода не изменится:

class C{
   private function foo() {
        echo "C!\n";
   }

   public function test() {
        $this->foo();
   }
}
 //the code executes along the same path even without A
 (new C)->test(); //C!

Вкратце: наследование - это не простая операция копирования и вставки. Код из A не копируется и не вставляется в C. Каждый класс все еще существует, просто A разделяет все, что доступно с C (защищено, общедоступно)

Надеюсь, это поможет, это лучшее, что я могу сделать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...