Pull to refresh

Публичные свойства, геттеры и сеттеры или магические методы?

Reading time 5 min
Views 60K
Original author: Russell Walker
Как правило, мнения расходятся касательно того, хорошей ли практикой является использование публичных свойств в PHP классах или всё же стоит использовать геттеры и сеттеры (и хранить свойства приватными или защищёнными). Ещё одно, компромиссное мнение, состоит в том, чтобы использовать магические методы __get() и__set().
У каждого из подходов существуют свои достоинства и недостатки, давайте взглянем на них…


Пример использования публичных свойств:
class Foo 
{
    public $bar;
}
 
$foo = new Foo();
$foo->bar = 1;
$foo->bar++;

В данном примере bar является публичным свойством класса Foo. При таком подходе мы можем манипулировать данным свойством как угодно и хранить в нём любые данные.

Достоинства публичных свойств
  • По сравнению с геттерами и сеттерами, нужно печатать на много меньше текста!
  • Код выглядит более читабельным и работать с ним проще, чем с вызовами геттеров и сеттеров.
  • Вызов публичного свойства (вместо set или get) работает быстрее и использует меньше памяти чем вызов метода, но выгода от этого будет почти незаметной до тех пор, пока вы не будете вызывать метод множество раз в длинном цикле.
  • Объекты с публичными свойствами могут использоваться в качестве параметров для некоторых PHP функций (таких как http_build_query).

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


Пример использования геттеров и сеттеров:
class Foo
{
    private $bar;
     
    public function getBar()
    {
        return $this->bar;
    }
     
    public function setBar($bar)
    {
        $this->bar = $bar;
    }
}
 
$foo = new Foo();
$foo->setBar(1);
$foo->setBar($foo->getBar() + 1);

Свойство bar тут является приватным, в связи с этим, доступ к нему не может быть получен напрямую. Для того, чтобы получить значение свойства, вам придётся использовать метод getBar, или setBar для присвоения свойству значения. Чтобы вы могли быть уверенны в том, что входные данные полностью корректны, данные методы могут включать в себя соответствующий функционал для их валидации.

Достоинства геттеров и сеттеров
  • Используя геттеры и сеттеры вы можете осуществлять контроль за тем, какие именно данные содержатся в свойствах объекта, и отклонять любые некорректные значения.
  • Так же вы можете осуществлять дополнительные операции перед тем, как установить или получить значение свойства (например, если обновление данного свойства должно вызывать некоторое действие, такое как оповещение пользователя).
  • При установке значения, которое является объектом или массивом, вы можете явно указать тип переменной в сигнатуре функции(прим. public function setBar(Bar $bar)). К большому сожалению, PHP не позволяет проделывать тоже самое с типами int и string!
  • Если значение свойства должно получаться из внешнего источника или среды исполнения, вы можете использовать ленивую загрузку данных — таким образом ресурсы, требуемые для загрузки данных, будут задействованы непосредственно во время получения значения свойства. Разумеется, в данном случае нужно соблюдать осторожность, и не следует получать данные из внешнего источника при каждом обращении к свойству. Будет лучше сделать одно обращение к базе данных и заполнить значения всех свойств сразу, чем делать это для каждого в отдельности.
  • Вы можете сделать свойство доступным только на чтение или только на запись, путём создания только геттера или только сеттера.
  • Вы можете добавить геттеры и сеттеры в интерфейс для того, чтобы отобразить их в API.

Недостатки геттеров и сеттеров
  • Для разработчиков, которые используют прямой доступ к свойствам, геттеры и сеттеры кажутся настоящей головной болью! Для каждого свойства нужно определить само свойство, геттер и сеттер; и для того чтобы использовать данное свойство в коде, нужно осуществлять дополнительные вызовы метода — намного легче написать $foo->bar++; вместо $foo->setBar($foo->getBar() + 1); (хотя, конечно, можно добавить ещё один метод $foo->incrementBar();)
  • Как уже отмечалось выше, существуют небольшие дополнительные расходы, затрачиваемые на вызов метода.
  • Имена геттеров и сеттеров принято начинать с глаголов get и set, но данные глаголы так же могут использоваться и в других методах, которые ни коим образом не относятся к свойствам класса.


Пример использования магических геттеров и сеттеров:

class Foo
{
    protected $bar;
 
    public function __get($property)
    {
        switch ($property)
        {
            case 'bar':
                return $this->bar;
            //etc.
        }
    }
 
    public function __set($property, $value)
    {
        switch ($property)
        {
            case 'bar':
                $this->bar = $value;
                break;
            //etc.
        }
    }
}
 
$foo = new Foo();
$foo->bar = 1;
$foo->bar++;

В данном случае свойство bar не является публичным, однако в коде оно используется так, как если бы было публичным. Когда PHP не может найти соответствующего публичного свойства он вызывает соответствующий магический метод (__get() для получения значения, __set() для установки значения). Данный подход может показаться золотой серединой, но у него есть существенный недостаток (см. недостатки ниже!). Следует также отметить, что __get() и __set() методы НЕ вызываются для публичных свойств, и вызываются в случае, если свойство помечено как protected или private и находится за пределами области видимости, или если свойство не определено.

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

Недостатки магических геттеров и сеттеров
  • Недостатком магических методов является то, что они не делают свойство доступным(«видимым»). Для того чтобы использовать или расширить класс, вам необходимо «просто знать» какие свойства он содержит. В большинстве случаев это невозможно (если только вы не один из хардкорных программистов, которые думают что notepad является IDE!), хотя бывают случаи, когда упомянутые выше преимущества, перевешивают это ограничение. Как заметил один из комментаторов, этот недостаток может быть устранён использованием phpDoc тегов @property, @property-read, и @property-write. Круто.


Какой подход использовать
Очевидно, что у геттеров и сеттеров есть ряд существенных преимуществ, и некоторые люди считают, что их стоит использовать всё время (особенно те, у кого прошлое связано с Java!). Но на мой взгляд, этим они нарушают естественное развитие языка, и их излишняя сложность и детализация принуждают меня работать с этим, даже если в этом нет надобности (меня раздражает, когда обычные геттеры и сеттеры НЕ делают ничего, кроме получения и установки свойства). В таких случаях я стараюсь использовать публичные свойства, а геттеры и сеттеры я использую для критических свойств, которые необходимо более строго контролировать, или если в них необходимо использовать отложенную загрузку данных.

Другие альтернативы?
До изучения PHP я использовал C#. В C# все свойства имеют методы доступа, но вам не нужно вызывать их как методы, вы можете манипулировать свойсвами напрямую и соответствующие методы будут вызываться магически. Это в некотором роде это похоже на магические методы __get() и __set() в PHP, однако свойства остаются определены и доступны. Это золотая середина и было бы очень хорошо увидеть аналогичную возможность в PHP.

Печально, но, RFC необходимый для реализации C# подобного синтаксиса определения методов доступа к свойствам не набрал необходимых две трети голосов: wiki.php.net/rfc/propertygetsetsyntax-v1.2 Вот так!
Tags:
Hubs:
+8
Comments 37
Comments Comments 37

Articles