Php вызвать конструктор родителя

Обновлено: 01.05.2024

Зачем это все писать — об этом и так куча разных книг/статей/еще чего-то
Считаю, что большинство из них переполнено термонологией и порой не совсем легки для восприятия(имхо).
Для кого это написано?
Очевидно, для людей, которые хотят поскорее вникнуть в основы ооп, не перерывая при этом кучу литературы/манов (хотя, надо отдать должное — это порой довольно полезное занятие).

Вступление

Итак, ты уже прочитал пару-тройку статей/книг по пхп и теперь считаешь себя мега-кодером? Однако читая хабр/форумы/т.п. ты встречаешь многа букв по поводу ООП в пхп? — Тогда надеюсь, эти статьи хоть немного помогут тебе понять/запутаться (нужное подчеркнуть) в сабже =)


Поехали

Как ты уже знаешь(или сейчас узнаешь), ключевыми понятиями ооп есть: наследования, абстракция, полиморфизм и инкапсуляция.
И начнем мы с наследования.

Пункт 1 — Предистория

Жила-была, значит, одна маленькая черная… кошка =). И была у нашей кошки одна интересная особенность — она умела гавкать =). И как-то так получилось, что в один прекрасный день, родился у нее котенок.

Так что же получилось в итоге? — наш котенок унаследовал основые функции котов: бегать, спать, жрать /и т.п.

И ты, наверное, подумал, что стал наш котенок копией мамаши? — Ан нет, оказалось, что гавкать он так и не научился, но вместо этого стал говорить по-человечески =)

Пункт 2 — Кодим предисторию

public function __construct() //добавляем основной функционал котов :)
>

public function talk($text) //главная фича кошки
//гавкаем
>

* This source code was highlighted with Source Code Highlighter .

Думаю, здесь все ясно. Метод talk(), как ты уже понял, отвечают за странную особенность нашей кошки.

И родила наша кошка котенка:
Приступим-с к наследнику =)

public function __construct() //вызываем конструктор кошки
parent::__construct();
>

* This source code was highlighted with Source Code Highlighter .

Теперь наш котенок умеет все то же, что и его мама…
$kitten = new Kitten();
$kitten->talk('гав гав');
?>

Хм… ах да, забыл — котенок должен ни гавкоть, а разговаривать =)
Так что же теперь делать? — Просто переопределим метод разговора (с гавканья на разговор).
class Kitten extends Cat <
public function __construct() //вызываем конструктор кошки
parent::__construct();
>

public function talk($text) //просто создаем новый метод с таким же названием
//разговариваем
>

* This source code was highlighted with Source Code Highlighter .

* This source code was highlighted with Source Code Highlighter .

Пункт 3 — Более реальные примеры или когда можно/нужно использовать наследование.

— Ты используешь чужую библиотеку/класс и решил его расширить (для своих нужд), тобишь дописать/переписать несколько необходимых _тебе методов.
— Ты решил использовать абстрактные классы в какой-то части своего приложения. Тут, естетсвенно, тебе поможет механизм наследования. (Об абстрактных классах, если ты захочешь, сможем поговорить в другой раз).
— Ты используешь механизм полиморфизма. (И об нем, мы, может, тоже поговорим ;) ).
— Ты пишешь свой фреймворк(хм, хотя таки странно, учитывая, что ты это читаешь :))? — Тогда для контроллера/модели можно/нужно использовать наследования.

Пункт 4 — Злоключение

— Что делать, если ты по каким-либо причинам не хочешь при написании класса оставить возможность его переопределять? — Добавить ключевое слово final перед объявлением метода
final public function do_smth()<>

* This source code was highlighted with Source Code Highlighter .
з.ы. Надеюсь, что в песочнице хоть кому-то это понравицо (очень надеюсь:)), а главное — для кого-то будет полезно. Спасибо =)

Basic class definitions begin with the keyword class , followed by a class name, followed by a pair of curly braces which enclose the definitions of the properties and methods belonging to the class.

The class name can be any valid label, provided it is not a PHP reserved word. A valid class name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. As a regular expression, it would be expressed thus: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$ .

A class may contain its own constants, variables (called "properties"), and functions (called "methods").

class SimpleClass
// property declaration
public $var = 'a default value' ;

// method declaration
public function displayVar () echo $this -> var ;
>
>
?>

The pseudo-variable $this is available when a method is called from within an object context. $this is the value of the calling object.

Calling a non-static method statically throws an Error . Prior to PHP 8.0.0, this would generate a deprecation notice, and $this would be undefined.

class A
function foo ()
if (isset( $this )) echo '$this is defined (' ;
echo get_class ( $this );
echo ")\n" ;
> else echo "\$this is not defined.\n" ;
>
>
>

$a = new A ();
$a -> foo ();

$b = new B ();
$b -> bar ();

Output of the above example in PHP 7:

Output of the above example in PHP 8:

To create an instance of a class, the new keyword must be used. An object will always be created unless the object has a constructor defined that throws an exception on error. Classes should be defined before instantiation (and in some cases this is a requirement).

If a string containing the name of a class is used with new , a new instance of that class will be created. If the class is in a namespace, its fully qualified name must be used when doing this.

Note:

If there are no arguments to be passed to the class's constructor, parentheses after the class name may be omitted.

// This can also be done with a variable:
$className = 'SimpleClass' ;
$instance = new $className (); // new SimpleClass()
?>

As of PHP 8.0.0, using new with arbitrary expressions is supported. This allows more complex instantiation if the expression produces a string . The expressions must be wrapped in parentheses.

In the given example we show multiple examples of valid arbitrary expressions that produce a class name. This shows a call to a function, string concatenation, and the ::class constant.

class ClassA extends \ stdClass <>
class ClassB extends \ stdClass <>
class ClassC extends ClassB <>
class ClassD extends ClassA <>

function getSomeClass (): string
return 'ClassA' ;
>

var_dump (new ( getSomeClass ()));
var_dump (new ( 'Class' . 'B' ));
var_dump (new ( 'Class' . 'C' ));
var_dump (new ( ClassD ::class));
?>

Output of the above example in PHP 8:

In the class context, it is possible to create a new object by new self and new parent .

When assigning an already created instance of a class to a new variable, the new variable will access the same instance as the object that was assigned. This behaviour is the same when passing instances to a function. A copy of an already created object can be made by cloning it.

$instance = new SimpleClass ();

$assigned = $instance ;
$reference =& $instance ;

$instance -> var = '$assigned will have this value' ;

$instance = null ; // $instance and $reference become null

var_dump ( $instance );
var_dump ( $reference );
var_dump ( $assigned );
?>

The above example will output:

It's possible to create instances of an object in a couple of ways:

class Test
static public function getNew ()
return new static;
>
>

class Child extends Test
<>

$obj1 = new Test ();
$obj2 = new $obj1 ;
var_dump ( $obj1 !== $obj2 );

$obj3 = Test :: getNew ();
var_dump ( $obj3 instanceof Test );

$obj4 = Child :: getNew ();
var_dump ( $obj4 instanceof Child );
?>

The above example will output:

It is possible to access a member of a newly created object in a single expression:

The above example will output something similar to:

Note: Prior to PHP 7.1, the arguments are not evaluated if there is no constructor function defined.

Properties and methods

Class properties and methods live in separate "namespaces", so it is possible to have a property and a method with the same name. Referring to both a property and a method has the same notation, and whether a property will be accessed or a method will be called, solely depends on the context, i.e. whether the usage is a variable access or a function call.

public function bar () return 'method' ;
>
>

$obj = new Foo ();
echo $obj -> bar , PHP_EOL , $obj -> bar (), PHP_EOL ;

The above example will output:

That means that calling an anonymous function which has been assigned to a property is not directly possible. Instead the property has to be assigned to a variable first, for instance. It is possible to call such a property directly by enclosing it in parentheses.

public function __construct () $this -> bar = function() return 42 ;
>;
>
>

echo ( $obj -> bar )(), PHP_EOL ;

The above example will output:

extends

A class can inherit the constants, methods, and properties of another class by using the keyword extends in the class declaration. It is not possible to extend multiple classes; a class can only inherit from one base class.

The inherited constants, methods, and properties can be overridden by redeclaring them with the same name defined in the parent class. However, if the parent class has defined a method or constant as final, they may not be overridden. It is possible to access the overridden methods or static properties by referencing them with parent::.

Note: As of PHP 8.1.0, constants may be declared as final.

class ExtendClass extends SimpleClass
// Redefine the parent method
function displayVar ()
echo "Extending class\n" ;
parent :: displayVar ();
>
>

$extended = new ExtendClass ();
$extended -> displayVar ();
?>

The above example will output:

Signature compatibility rules

When overriding a method, its signature must be compatible with the parent method. Otherwise, a fatal error is emitted, or, prior to PHP 8.0.0, an E_WARNING level error is generated. A signature is compatible if it respects the variance rules, makes a mandatory parameter optional, and if any new parameters are optional. This is known as the Liskov Substitution Principle, or LSP for short. The constructor, and private methods are exempt from these signature compatibility rules, and thus won't emit a fatal error in case of a signature mismatch.

class Base
public function foo ( int $a ) echo "Valid\n" ;
>
>

class Extend1 extends Base
function foo ( int $a = 5 )
parent :: foo ( $a );
>
>

class Extend2 extends Base
function foo ( int $a , $b = 5 )
parent :: foo ( $a );
>
>

$extended1 = new Extend1 ();
$extended1 -> foo ();
$extended2 = new Extend2 ();
$extended2 -> foo ( 1 );

The above example will output:

The following examples demonstrate that a child method which removes a parameter, or makes an optional parameter mandatory, is not compatible with the parent method.

class Base
public function foo ( int $a = 5 ) echo "Valid\n" ;
>
>

class Extend extends Base
function foo ()
parent :: foo ( 1 );
>
>

Output of the above example in PHP 8 is similar to:

class Base
public function foo ( int $a = 5 ) echo "Valid\n" ;
>
>

class Extend extends Base
function foo ( int $a )
parent :: foo ( $a );
>
>

Output of the above example in PHP 8 is similar to:

Renaming a method's parameter in a child class is not a signature incompatibility. However, this is discouraged as it will result in a runtime Error if named arguments are used.

class A public function test ( $foo , $bar ) <>
>

class B extends A public function test ( $a , $b ) <>
>

// Pass parameters according to A::test() contract
$obj -> test ( foo : "foo" , bar : "bar" ); // ERROR!

The above example will output something similar to:

::class

The class keyword is also used for class name resolution. To obtain the fully qualified name of a class ClassName use ClassName::class . This is particularly useful with namespaced classes.

The above example will output:

Note:

The class name resolution using ::class is a compile time transformation. That means at the time the class name string is created no autoloading has happened yet. As a consequence, class names are expanded even if the class does not exist. No error is issued in that case.

The above example will output:

As of PHP 8.0.0, the ::class constant may also be used on objects. This resolution happens at runtime, not compile time. Its effect is the same as calling get_class() on the object.

The above example will output:

Nullsafe methods and properties

As of PHP 8.0.0, properties and methods may also be accessed with the "nullsafe" operator instead: ?-> . The nullsafe operator works the same as property or method access as above, except that if the object being dereferenced is null then null will be returned rather than an exception thrown. If the dereference is part of a chain, the rest of the chain is skipped.

The effect is similar to wrapping each access in an is_null() check first, but more compact.

// As of PHP 8.0.0, this line:
$result = $repository ?-> getUser ( 5 )?-> name ;

// Is equivalent to the following code block:
if ( is_null ( $repository )) $result = null ;
> else $user = $repository -> getUser ( 5 );
if ( is_null ( $user )) $result = null ;
> else $result = $user -> name ;
>
>
?>

Note:

The nullsafe operator is best used when null is considered a valid and expected possible value for a property or method return. For indicating an error, a thrown exception is preferable.

User Contributed Notes 13 notes

I was confused at first about object assignment, because it's not quite the same as normal assignment or assignment by reference. But I think I've figured out what's going on.

First, think of variables in PHP as data slots. Each one is a name that points to a data slot that can hold a value that is one of the basic data types: a number, a string, a boolean, etc. When you create a reference, you are making a second name that points at the same data slot. When you assign one variable to another, you are copying the contents of one data slot to another data slot.

Now, the trick is that object instances are not like the basic data types. They cannot be held in the data slots directly. Instead, an object's "handle" goes in the data slot. This is an identifier that points at one particular instance of an obect. So, the object handle, although not directly visible to the programmer, is one of the basic datatypes.

What makes this tricky is that when you take a variable which holds an object handle, and you assign it to another variable, that other variable gets a copy of the same object handle. This means that both variables can change the state of the same object instance. But they are not references, so if one of the variables is assigned a new value, it does not affect the other variable.

// Assignment of an object
Class Object public $foo = "bar" ;
>;

$objectVar = new Object ();
$reference =& $objectVar ;
$assignment = $objectVar

//
// $objectVar --->+---------+
// |(handle1)----+
// $reference --->+---------+ |
// |
// +---------+ |
// $assignment -->|(handle1)----+
// +---------+ |
// |
// v
// Object(1):foo="bar"
//
?>

$assignment has a different data slot from $objectVar, but its data slot holds a handle to the same object. This makes it behave in some ways like a reference. If you use the variable $objectVar to change the state of the Object instance, those changes also show up under $assignment, because it is pointing at that same Object instance.

$objectVar -> foo = "qux" ;
print_r ( $objectVar );
print_r ( $reference );
print_r ( $assignment );

//
// $objectVar --->+---------+
// |(handle1)----+
// $reference --->+---------+ |
// |
// +---------+ |
// $assignment -->|(handle1)----+
// +---------+ |
// |
// v
// Object(1):foo="qux"
//
?>

But it is not exactly the same as a reference. If you null out $objectVar, you replace the handle in its data slot with NULL. This means that $reference, which points at the same data slot, will also be NULL. But $assignment, which is a different data slot, will still hold its copy of the handle to the Object instance, so it will not be NULL.

$objectVar = null ;
print_r ( $objectVar );
print_r ( $reference );
print_r ( $assignment );

I need to have a class constructor in PHP call its parent's parent's (grandparent?) constructor without calling the parent constructor.

I know this is a bizarre thing to do and I'm attempting to find a means that doesn't smell bad but nonetheless, I'm curious if it's possible.

The Grampa constructor sets properties for itself that are inherited by its children. Papa does some stuff in it's constructor that will mess up Kiddo. So I need the call to Grandpa constructor to set properties for Kiddo during construction.

@MitMaro. I agree and I actually solved my actual problem by creating an intermediate class that extended Grandpa. Then both Papa and Kiddo extended that class. Kiddo required some intermediate functionality of Papa but didn't like it's constructor so the class has that additional functionality and both extend it.

15 Answers 15

The ugly workaround would be to pass a boolean param to Papa indicating that you do not wish to parse the code contained in it's constructor. i.e:

good workaround, but it is not acceptable if the parent class comes from some external library wish you with to extend. I like the too much php answer below.

That's actually quite clever. In my implementation I do if($bypass) return; & can position it so some important stuff gets done before the bypass.

You must use Grandpa::__construct() , there's no other shortcut for it. Also, this ruins the encapsulation of the Papa class - when reading or working on Papa , it should be safe to assume that the __construct() method will be called during construction, but the Kiddo class does not do this.

Can't understand how. Declaring __construct as static results in the following error for me "Fatal error: Constructor Grandpa::__construct() cannot be static" under PHP5.3

When I tried it, I didn't declare it as static. I created the class structure normally but instead of calling parent::__construct(), I called Grandpa::__construct() and it worked. I doesn't seem right to me either but this whole question got kinda weird.

Agreed. I use this all the time - you can call a class by it's name, not just via the magic names of parent or self . I have been known to go three or four classes deep. In fact, I've started referring to my base class by it's name rather than using parent, that way I'm sure I'm getting the right object, always.

@SparK If you are using PHP 5.3.0 or later, Late Static Bindings would most likely solve your use case.

Personally I wouldn't choose to do this as it means Papa's contractor won't get called at all. I'd go with something like cballou's approach (i.e. of passing an argument to Papa's constructor and having it invoke it's parents constructor or not based on that).

The situation we are in here is in such a way that we need to skip the parent's logic and in most of the cases we can't change the grandparent or the parent classes. I believe this is the best way to do it as there are changes made only in the child. The only issue is that it might throw an E_STRICT notice link, it didn't for me though when I tested it.

This is an interesting solution, however, if you really don't need the logic of the parent's constructor, are you sure that you are really making a subsclass of a parent?

This is the correct answer. While it may seem silly to inherit Papa but you want to call the GrandPa constructor without Papa, I've found it useful to be do. I want to keep the hierarchy, but I need to do a clean Kiddo constructor that doesn't have anything to do with Papa, but still want the benefits of using what's going on in GrandPa's constructor. Maybe Papa is doing a bunch of junk in the constructor that isn't needed or wanted by Kiddo, but it still has useful components.

Beautiful solution using Reflection .

I ended up coming up with an alternative solution that solved the problem.

  • I created an intermediate class that extended Grandpa.
  • Then both Papa and Kiddo extended that class.
  • Kiddo required some intermediate functionality of Papa but didn't like it's constructor so the class has that additional functionality and both extend it.

I've upvoted the other two answers that provided valid yet ugly solutions for an uglier question:)

I think the better idea here is to break the functionality you are trying to use out of the constructed and into a protected method. Then you can call that method from a constructor selectively

This does not answer the exact question you've phrased. This happens if the real world muddies up something which should be clear and confined. It's a pity for this question.

Another option that doesn't use a flag and might work in your situation:

I agree with "too much php", try this:

I got the result as expected:

This is a feature not a bug, check this for your reference:

It is just the way it works. If it sees it is coming from the right context this call version does not enforce a static call.

Instead it will simply keep $this and be happy with it.

parent::method() works in the same way, you don't have to define the method as static but it can be called in the same context. Try this out for more interesting:

It also works as expected:

But if you try to initialize a new Papa, you will get an E_STRICT error:

Strict standards: Non-static method Kiddo::hello() should not be called statically, assuming $this from incompatible context

You can use instanceof to determine if you can call a Children::method() in a parent method:

Just remember that parent is only a shortcut to whatever first parent that implemented the method… Thus from a descendant calling AscendantName::method works and from an ascendant calling static::method will always call the latest generation that implemented the method. And you may check not to call yourself using (get_class($this)==__CLASS__?'Healthy people dont\'t call themself…':'Calling latest') if you'd like parents to call kid methods (seems strange but might be usefull combined with private function and __call magic function )…

There's an easier solution for this, but it requires that you know exactly how much inheritance your current class has gone through. Fortunately, get_parent_class()'s arguments allow your class array member to be the class name as a string as well as an instance itself.

Bear in mind that this also inherently relies on calling a class' __construct() method statically, though within the instanced scope of an inheriting object the difference in this particular case is negligible (ah, PHP).

Consider the following:

Again, this isn't a viable solution for a situation where you have no idea how much inheritance has taken place, due to the limitations of debug_backtrace(), but in controlled circumstances, it works as intended.

I was wondering if its possible to call the parents __construct(), before the child's __construct() with inheritance in PHP.

Ideally, I would be able to do something in between them. If this is not possible, is there an alternative, which would allow me to do this?

The reason I want to do this is to be able to load a bunch of default settings specific to the Tag that Form can use when __construct() is called.

EDIT: Sorry forgot to add this.. I'd rather not call the parent class from the child class. It's simply because it exposes some private data (for the parent) to the child, when you pass it as an argument

This is what I want to do:

Tag.php

Form.php

Thanks! Matt Mueller

Can you elaborate on what you mean by this 'exposes some private data (for the parent) to the child'?

Private data of a parent class won't be exposed to any subclasses. Public or protected data will, private won't.

Yah, but if the (private) parameters goes through the child constructor first, the child has access to them. I editted it to clarify.

Why is this a problem? Simply have your child constructor pass through the arguments it doesn't care about and ignore them.

Because other people will be extending the Tag and I don't want all this garbage coming through the child class.

4 Answers 4

Just call parent::__construct in the child.

Ahh sorry. I forgot to add that I would rather not have that in the child's constructor. Please read edit.

Decided that this was the best of the alternatives. Unfortunately, its not exactly what I wanted - oh well.. Thanks!

yeah just call parent::__construct() in your construct

This is the way to do, simply read OOP manual if you don't agree. Don't try to reinvent the wheel. If you have concern regarding your model, try to post that too, to see how you did. Probably you are doing a bad design technique.

I reread your edited question, but still don't understand what problems you have. What data is visible to the child class from parent? Maybe you should post a bad example, with some echo to see what do you see wrong.

Yes, but only internally (i.e., by writing a PHP extension), so if I were you I'd settle with calling parent::__construct() . See this section on the PHP wiki.

Sorry, PHP is not Java. I think not requiring (implicitly or explictly) the super constructor to be called was a very poor design decision.

From the sounds of it you may want to rethink your design so that you don't need to pass the parameters in the constructor. If you don't think it can be done, ask it as a question, you might be surprised by some of the suggestions.

The child class has the ability to override the parent constructor without calling it at all. I would recommend having a final method in the parent class. That way everyone knows you don't want this being overriden, and any inherited class (rightly) has access to do whatever it wants in the constructor.

Another, not recommended, more "reinventing the wheel", solution would be to define a function in the parent class, say _construct(), that's called in its own construct. Its not really clear, doesn't use language features/constructs, and is very specific to a single application.

One last thing to keep in mind: you can't really hide information from the child class. With Reflection, serialize, var_dump, var_export and all these other convenient APIs in the php language, if there is code that shouldn't do anything with the data, then there's not really much you can do asides from not store it. There are libraries and such that help create sandboxes, but its hard to sandbox an object from itself.

Edit: Somehow I missed Artefacto's answer, and I suppose he is right (I've never tried writing an extension to do that). Still, implementing it breaks developer expectations while making it harder to actually see code to explain what's going in.

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

Предполагаю, что вы уже сталкивались с некоторыми из них, ведь существуют довольно распространенные методы, и тем не менее, я считаю, что компетентному программисту PHP необходимо уверенное владение всеми возможностями языка.
Я думаю, это можно считать, своего рода, отправной точкой в мир Магических методов.

Приступая к изучению

Когда я сам изучал этот материал, я использовал всевозможные учебники и статьи, в которых излагались довольно глупые или вообще бесполезные примеры. Я считаю, что для того чтобы понять что-то нужно попробовать это в контексте реальной задачи. Именно с этого мы и начнем.

Представим себе, что мы хотим получать все твиты, при помощи Tweeter Api. Мы получаем JSON всех твитов текущего пользователя и хотим превратить каждый твит в объект с методами, которые позволят проводить определенные операции.

Ниже, я представил базовый класс Tweet:


Теперь, когда мы создали объект, мы можем приступать к изучению самих методов. (Прим. переводчика — некоторые конструкции будут иногда опускаться, чтобы акцентировать на роли и возможностях каждого метода)

Конструкторы и Деструкторы

Пожалуй, одним из самых наиболее распространенных магических методов является конструктор ( __construct() ). Если вы достаточно внимательно следили за созданием приложения Cribbb в моем блоге, вы достаточно осведомлены об этом методе.

Метод __construct() автоматически вызывается, когда был создан экземпляр объекта. В нем вы можете задать начальные свойства объекта или установить зависимости.

Когда мы создаем экземпляр класса Tweet, мы можем передать параметры, которые поступят в метод __construct(). Из примера выше, вы можете видеть, что мы не вызываем этот метод и не должны вызывать — он вызывается автоматически.

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

При попытке удалить объект будет вызван метод __destruct(). Опять же, по аналогии с конструктором — это не то что нужно вызывать, ведь PHP все сделает за вас. Этот метод позволит вам очистить все, что вы использовали в объекте, например соединение с базой данных.

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

Геттеры и сеттеры

Когда вы работаете с обьектами в PHP, вам бы очень хотелось обращаться к свойствам объекта как-то так:

Однако, если у свойства text установлен модификатор доступа protected, то такое обращение вызовет ошибку.
Магический метод __get() будет отлавливать обращения к любым не публичным свойствам.

Метод __get() приминает имя свойства, к которому вы обращаетесь, в качестве аргумента. В приведенном выше примере сначала проверяется существование свойства в объекте и если оно существует, то возвращается его значение.

Как и в примерах выше — вы не должны вызывать этот метод напрямую, PHP будет вызывать его каждый раз, при попытке получения доступа к не публичным свойствам класса.

В обратной ситуации — если вы попытаетесь установить значение свойства, которое не является публичным — вы получите ошибку. И опять же, в PHP есть свой метод, который будет вызван при попытке установить в не публичное поле какое-либо значение. Данный метод принимает 2 параметра в качестве аргументов — свойство, в которое хотели записать значение, и само значение.

Если вы хотите использовать данный метод, ваш класс получит свойство, на подобии этого:

В приведенных выше примерах я показал как можно получить или установить значения свойств, не имеющих модификатор доступа public. Однако работа с данными магическими методами не всегда будет лучшей идеей. Гораздо лучше иметь множество методов для получения и записи свойств, так как в этом случае они формируют определенный API и это означает, что при изменении способа хранения или обработки ваш код не будет сломан.

Впрочем, вы все равно иногда будете встречать методы __get() и __set(), которые принято называть геттерами и сеттерами соотвественно. Это довольно хорошее решение, если вы решили изменить какое-либо значение или добавить немножко бизнес-логики.

Проверка свойства на существование

Если вы знакомы с PHP, вы скорее всего знаете о существовании функции isset(), которую обычно применяют при работе с массивами. Вы так же можете использовать эту функцию, для того чтобы понять — задано свойство в обьекте или нет. Вы сможете определить магический метод __isset(), для того чтобы можно проверять не только общедоступные свойства, но и другие.

Как вы видите выше, __isset() метод отслеживает вызов функции на проверку существования и получает в качестве аргумента — название свойства. В свою очередь, в методе вы можете использовать функцию isset(), для проверки существования.

Очистка переменной
По аналогии с функцией isset(), функция unset() обычно используется при работе с массивами. Опять же, вы можете использовать функцию unset() для того чтобы очистить значение не публичного свойства. Чтобы применить данный метод на не публичные свойства, вам понадобиться метод __unset(), который будет отслеживать попытки очистить не публичный свойства класса.

Приведение к строке

Метод __toString() позволит вам определить логику работы вашего приложения, при попытке привести обьект к типу строке.
Например:

Можно сказать, что когда вы пытаетесь обратиться к обьекту, как к строке, например при использовании echo, обьект будет возвращен так, как вы определите в __toString() методе.

Хорошей иллюстрацией в данном случае может случить Eloquent Models из фреймворка Laravel. При попытке приведения обьекта к строке вы получите json. Если вы хотите увидеть как Laravel это делает, рекомендую обратиться к исходному коду.

Сон и пробуждение

Функция сериализации ( serialize() ), является довольно распространенным способом хранения обьекта. Например, если бы вы хотели сохранить обьект в базе данных, для начала вы должны были бы его сериализовать, затем сохранить, а когда бы он вам потребовался снова, вы должны были бы его получить и десериализовать ( unserialise() ).

Метод __sleep(), позволяет определить какие свойства должны быть сохранены. Если бы мы к примеру, не хотели сохранять какие-либо связи или внешние ресурсы.

Представим себе, что когда мы создаем обьект, мы хотим определить механизм его сохранения.

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

А после того как настанет время для пробуждения обьекта, нам могут понадобиться все то, что мы не сохранили при сериализации. В конкретном примере нам нужно установить соединение с базой данных. Это можно сделать, при помощи магического метода __wakeup().

Вызов методов

Магический метод __call(), будет перехватывать все попытки вызовов методов, не являющихся публичными. Например, у вас может быть массив данных, которые вы хотите изменить:

Еще один типичный пример это использование другого публичного API в своем обьекте.

В приведенном выше примере, мы можем вызвать метод getLocation на обьекте класса Tweet, но на самом деле мы его делегируем классу Location.
Если вы пытаетесь вызвать статический метод, вы можете так же воспользоваться __callStatic() магическим методом. Главное помните, что работает он лишь при вызове статичных методов.

Клонирование

Когда вы создаете копию объекта в PHP, по факту в переменную записывается не новый объект, а идентификатор, ссылающийся на оригинальный обьект. То есть любое изменение в ссылающимся объекте влечет за собой изменение первоначального объекта, однако удаление любого из объектов не повлияет на существование других:

Для того чтобы создать копию обьекта вам следует использовать ключевое слово clone.

Однако, если у нас есть несколько связанных обьектов, зависимости, которые находятся в них, так же будут скопированы.

Для того чтобы решить данную проблему мы можем определить метод __clone() для того чтобы определить правильное поведение:

Вызов обьекта как функции

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

В данном примере я применяю обьект $tweet, как callback-функцию, ко всем значениям массива $users. В данном примере, мы добавим твит каждому пользователю. Согласен, данный пример является немного искусственным, однако я уверен, что вы действительно найдете применение этому методу.

Заключение

Как вы могли уже убедиться, PHP использует магические методы для реагирования на те или иные действия с методами или свойствами обьекта. Каждый из данных методов срабатывает автоматически, вы просто определяете что должно произойти, а об остальном позаботиться PHP.

В течении довольно длительного времени я не понимал истинного значения магических методов. Я думал, что они нужны, лишь для того чтобы делать какие-либо интересные вещи с обьектами. И когда я, наконец, понял их истинное предназначение, я смог писать более мощные обьекты в рамках более серьезных приложений.

Надеюсь, что в каждом из примеров, представленных в этом руководстве, я смог показать как магические методы могут помочь в повседневных задачах. И я буду не первым кто согласиться, что когда вам обьясняют какой-либо материал, но не обьясняют его практическое применение это реально раздражает.

Читайте также: