Вызвать родительский конструктор php

Обновлено: 17.05.2024

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


Правильным ответом будет «в зависимости от ситуации». Оба способа могут являются корректным с точки зрения полученного результата. Реализуем поддержку обоих способов:

Выглядит отвратительно. Кроме того поддержка класса будет затруднена. Что произойдет, если нам понадобится добавить еще несколько способов создания экземпляров класса Time?


Также, вероятно, стоит добавить поддержку числовых строк (защита от дурака не помешает):

Реорганизация кода с использованием именованных конструкторов

Добавим несколько статичных методов для инициализации Time. Это позволит нам избавиться от условий в коде (что зачастую является хорошей идеей).


Теперь каждый метод удовлетворяет принцип Единой ответственности. Публичный интерфейс прост и понятен. Вроде бы закончили? Меня по прежнему беспокоит конструктор, он использует внутреннее представление объекта, что затрудняет изменение интерфейса. Положим, по какой-то причине нам необходимо хранить объединенное значение времени в строковом формате, а не по отдельности, как раньше:


Это некрасиво: нам приходится разбивать строку, чтобы потом заново соединить её в конструкторе. А нужен ли нам конструктор для конструктора?

Мы встроили тебе конструктор в конструктор.

Нет, обойдемся без него. Реорганизуем работу методов, для работы с внутренним представлением напрямую, а конструктор сделаем приватным:

Единообразие языковых конструкций

Наш код стал чище, мы обзавелись несколькими полезными методами инициализации нового объекта. Но как часто случается с хорошими конструктивными решениями — ранее скрытые изъяны выбираются на поверхность. Взгляните на пример использования наших методов:

  • fromString — использует в названии детали реализации PHP;
  • fromValues ​​- использует своего рода общий термин программирования;
  • fromMinutesSinceMidnight - использует обозначения из предметной области.
  • fromString => fromTime
  • fromValues => fromHoursAndMinutes


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

Часть 2: Когда использовать статические методы

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

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

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.

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

15. Объектно-ориентированное программирование

15.1. Классы и объекты. Поля и методы

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

Объект - это экземпляр класса.

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

Именем класса может быть любое слово, при условии, что оно не входит в список зарезервированных слов PHP, начинается с буквы или символа подчёркивания и за которым следует любое количество букв, цифр или символов подчёркивания.

15.2. Области видимости

Для полей и методов можно задавать области видимости

  • public — свойства или методы, объявленные как public, могут быть доступны в любом месте.
  • protected — protected свойства и методы доступны внутри класса, а также в дочерних классах.
  • private — доступ к private свойствам и методам имеет только класс, в котором эти свойства или методы объявлены

15.3. Переменная $this

Для того, чтобы обратиться к свойству или методу класса внутри другого метода этого класса, вместо имени объекта следует писать специальную переменную $this.

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

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

Конструкторы при наследовании

Конструкторы, определённые в классах-родителях не вызываются автоматически, если дочерний класс определяет собственный конструктор. Чтобы вызвать конструктор, объявленный в родительском классе, требуется вызвать parent::__construct() внутри конструктора дочернего класса. Если в дочернем классе не определён конструктор, то он может быть унаследован от родительского класса как обычный метод (если он не был определён как приватный).

Приватный конструктор. Паттерн Singleton

Singleton – один из самых простых шаблонов для понимания. Основное назначение – гарантировать существование только одно экземпляра класса. Причиной обычно является следующее: требуется только один объект исходного класса и Вам необходимо, что бы объект был доступен в любом месте приложения, т.е. глобальный доступ.

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

15.5. Динамический вызов свойств и методов

15.6. Статические свойства и методы. Константы

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

Чтобы объявить метод статическим, нужно после модификатора доступа (то есть после public, private или protected) написать ключевое слово static.

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

15.7. Магические методы

Метод __set() будет выполнен при записи данных в недоступные (защищённые или приватные) или несуществующие свойства.

Метод __get() будет выполнен при чтении данных из недоступных (защищённых или приватных) или несуществующих свойств.

Метод __call() запускается при вызове недоступных методов в контексте объект.

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

Полный список магических методов: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone() и __debugInfo()

Обращение к родительскому классу из наследника

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

Перезапись конструктора родительского класса

15.9. Наследование vs композиция и аггрегация

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

Это один из принципов ООП, который подразумевает, что класс наследник, будет иметь все те же свойства и методы, что и в базовом классе. В коде это выглядит так:

Один класс использует внутри своей реализации свойства или методы объекта другого класса. При этом используемый объект создается внутри класса. Пример:

Композиция - это по сути включение класса, внутрь другого класса с помощью создания объекта внутри этого класса.

У такого подхода есть один недостаток - сильная связанность, это значит, что, для того чтобы поменять класс A на A1, вам придется переписывать конструктор (new A1 вместо A).

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

Один класс использует внутри своей реализации свойства или методы объекта другого класса. При этом используемый объект создается вне класса. Пример:

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

Композиция лучше наследования

Как говорится в известной книге "Шаблоны проектирования" Банды четырёх, по мере возможности нужно выбирать композицию, а не наследование. Есть много хороших причин использовать как наследование, так и композицию. Главная цель этой максимы заключается в том, если вы инстинктивно склоняетесь к наследованию, то постарайтесь представить, может ли композиция лучше решить вашу задачу. В каких-то случаях это действительно более подходящий вариант.

Вы спросите: "А когда лучше выбирать наследование?" Всё зависит от конкретной задачи, но можно ориентироваться на этот список ситуаций, когда наследование предпочтительнее композиции:

  • Ваше наследование — это взаимосвязь is-a, а не has-a. Пример: Человек → Животное vs. Пользователь → Детали пользователя (UserDetails).
  • Вы можете повторно использовать код из базовых классов. (Люди могут двигаться, как животные.)
  • Вы хотите внести глобальные изменения в производные классы, изменив базовый класс. (Изменение расхода калорий у животных во время движения.)

15.11. Трейты вместо множественного наследования

В PHP нельзя наследовать от нескольких классов сразу, только от одного.

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

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

Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait.

Экземпляр трейта нельзя создать - трейты предназначены только для подключения к другим классам.

15.12. Абстрактные классы и интерфейсы. Полиморфизм

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

Пусть у вас есть класс User, а от него наследуют классы Employee и Student.

При этом предполагается, что вы будете создавать объекты классов Employee и Student, но объекты класса User - не будете, так как сам класс User используется только для группировки общих свойств и методов своих наследников.

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

Для того, чтобы объявить класс абстрактным, нужно при его объявлении написать ключевое слово abstract

Абстрактные классы также могут содержать абстрактные методы.

Такие методы не должны иметь реализации, а нужны для того, чтобы указать, что такие методы должны быть у потомков.

А собственно реализация таких методов - уже задача потомков.

Для того, чтобы объявить метод абстрактным, при его объявлении следует написать ключевое слово abstract.

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

При этом область видимости этих методов должна совпадать или быть менее строгой. Что значит менее строгой: например, если абстрактный метод объявлен как protected, то реализация этого метода должна быть protected или public, но не private.

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

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

Фактически ваш родительский класс описывает интерфейс потомков, то есть набор их публичных методов, обязательных для реализации.

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

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

В PHP пространства имён используются для решения двух проблем, с которыми сталкиваются авторы библиотек и приложений при создании повторно используемых элементов кода, таких как классы и функции:

  • Конфликт имён между вашим кодом и внутренними классами/функциями/константами PHP или сторонними.
  • Возможность создавать псевдонимы (или сокращения) для Ну_Очень_Длинных_Имён, чтобы облегчить первую проблему и улучшить читаемость исходного кода.

Пространства имён PHP предоставляют возможность группировать логически связанные классы, интерфейсы, функции и константы.

Для класса Page из файла /admin/page.php укажем пространство имен Admin:

А для класса Page из файла файл /users/page.php укажем пространство имен Users:

Давайте теперь в файле /index.php создадим объект одного и второго класса Page:

Конструкция use позволяет подключить класс по его полному имени, и после этого можно будет обращаться к этому классу просто по имени класса. Можно использовать псевдонимы для классов с помощью конструкции as . Это полезно, если вы хотите использовать классы с одинаковыми именами из разных неймспейсов

15.14. Автозагрузка через spl_autoload_register

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

Composer - это менеджер зависимостей для PHP. Вы можете описать от каких библиотек зависит ваш проект и Composer установит нужные библиотеки за вас.

При установке php пакетов Composer не просто устанавливает их, он также устанавливает все зависимости, от которых эти пакеты зависят. Т.е., например, если загружаемая библиотека будет зависеть от 3 других пакетов, а каждая из них, ещё в свою очередь от нескольких, то Composer всё это установит автоматически.

Загрузку сторонних библиотек Composer выполняет в папку vendor , которую данный php скрипт создаёт в корневой директории проекта. Также composer создаёт специальный файл autoload.php . Если вы подключите этот файл в проекте, вы сразу сможете использовать все загруженные библиотеки.

Пример: вызываем в консоли команду composer require monolog/monolog

Появятся файлы composer.json , composer.lock и папка vendor .

Файл composer.json - это главный файл Composer. В нем содержится описание основных пакетов, включая требования к их версиям.

Файл composer.lock - это файл, содержащий уже не требования, а реальные версии пакетов, которые были установлены на компьютер пользователя. Основное назначение файла composer.lock заключается в полном сохранении среды, в которой осуществлялась разработка и тестирование проекта. Например, если вы захотите скопировать проект в какое-то другое место без переноса файла composer.lock , то выполнив в нём команду composer install , вы можете получить другие версии пакетов. Это может случиться из-за выхода новых версий основных пакетов, описанных в файле composer.json , их зависимостей, зависимостей их зависимостей и т.д.

Если же вы разворачиваете проект, включающий в себя composer.lock , то вы получите те же версии зависимостей, которые были при разработке и тестировании. Поэтому composer.lock необходимо добавлять в git.

В папке vendor располагаются исходники библиотек и файл autoload.php . Вы можете подключить autoload.php и начать использовать классы, которые эти библиотеки предоставляют:

15.16. Автозагрузка собственных классов с помощью composer. Стандарт PSR-4

Вы даже можете добавить свой код в автозагрузчик, добавив поле autoload в composer.json

После добавления поля autoload в composer.json необходимо повторно выполнить команду dump-autoload для повторной генерации файла vendor/autoload.php

Composer зарегистрирует автозагрузчик PSR-4 для пространства имен App .

В таблице ниже представлены примеры соответствий полностью определённого имени класса, префикса пространства имён, базового каталога и итогового пути к файлу.

Fully Qualified Class Name Namespace Prefix Base Directory Resulting File Path
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

15.17 Reflection API

Рефлексия (отражение) - процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения. PHP включает в себя полноценный Reflection API, который предоставляет возможность проводить интроспекцию классов, интерфейсов, функций, методов и модулей. Кроме того, Reflection API позволяет получать doc-блоки комментариев функций, классов и методов.

Класс Profile является черным ящиком. И если у нас нет доступа к коду, то посмотреть как он устроен можно используя ReflectionClass:

ReflectionClass похож на аналитика для класса Profile, и это основная идея Reflection API.

PHP дает ключи к закрытым параметрам, поэтому мы можем получить доступ к ним с помощью:

ReflectionClass: сообщает информацию о классе. ReflectionFunction: сообщает информацию о функции. ReflectionParameter: извлекает информацию о параметрах функции или метода. ReflectionClassConstant: сообщает информацию о константе класса.

Reflection API входит в состав языка, поэтому нет необходимости в дополнительной установке или конфигурации.

Механизм рефлексии широко используется генераторами документаций, а так же в фреймворках для конфигурирования роутингов, параметров сериализации, настройки прав доступа и т.д.

15.18 SPL - Standart PHP Library

Стандартная библиотека PHP (SPL) - это набор интерфейсов и классов, предназначенных для решения стандартных задач.

SPL предоставляет ряд стандартных структур данных, итераторов для итерирования объектов, интерфейсов, стандартных исключений, некоторое количество классов для работы с файлами и предоставляет ряд функций, например spl_autoload_register().

Данный набор входит в состав языка, поэтому нет необходимости в дополнительной установке или конфигурации.

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

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

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.

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

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

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