Pull to refresh

Comments 37

UFO just landed and posted this here
UFO just landed and posted this here
Магические… В этом весь PHP, а не правильнее называть «Поля» (то что в первом случае) и «свойства» (то что в случае __get __set)?
Да, очень обидно на самом деле.
использование магии увеличивает время работы и уменьшает читаемость кода.
Можете показать пример ухудшения читаемости?
class Human
{
    private $passport;
    private $age;

    public function __get($name)
    {
        if ($this->isPassportPropertyName($name)) {
            $name = $this->convertToPassportPropertyName($name);
            $value = $this->passport->$name;
        } elseif (property_exists($this, $name)) {
            $value = $this->$name;
        } else {
            throw new \Exception('Oops, there is no such property here!');
        }
    }
}

vs
class Human
{
    private $passport;
    private $age;

    public function getPassportIssueDate()
    {
        return $this->passport->issueDate;
    }

    ...

    public function getAge()
    {
        return $this->age;
    }
}
Да, согласен. Здесь действительно «чёрт ногу сломит».

С другой стороны так «изощрённо» можно использовать любую языковую фичу. Пожалуй, нужно было задать вот такой вопрос: действительно не существует таких случаев, когда применение магических методов __set и __get не ухудшает (улучшает) читаемость кода?
Ну, в общем-то, я писал в контексте статьи — сравнение public, get() и __get().

Конечно, как большинство языковых конструкций, __get/__set имеют свои области применения. Но имхо, зачастую они используются не по назначению. Вот пример: gist.github.com/elfet/7081560

Кстати, интересно было бы послушать поставивших минусы (на сами минусы пофиг, конечно).

P.S. В том комментарии не стал писать (т.к. разговор шёл именно про геттеры в пределах одного класса), но вообще, конечно, идеальная читаемость достигается в этом случае:
$vasya->getPassport()->getIssueDate()
Однозначного выбора все равно нет.
Где-то нужно использовать именно сеттеры, где то достаточно (и очень удобно) — использовать магику. А в части случаев вообще достаточно публичного атрибута.

Тут главное без фанатизма. PHP-кодеры, повсеместно использующие гетеры и сеттеры меня, честно говоря, тоже убивают. А те, кто отказываются от них полностью только из принципа, мол, «геттеры/сеттеры — это моветон» ничем не отличаются от первых.
Имхо, стоит придерживаться одного стиля.

Меня, например, убивает ситуация, когда в каких-то классах публичные свойства, а в каких-то геттеры. Иногда ещё умудряются сочетать и то, и другое в одном классе.

Кстати, изначальное использование геттеров всегда сильно упрощает рефакторинг. Скажем, вы решили запретить повторное переназначение имени для пользователя. Если имя у вас было публичным свойством, то вам придётся добавлять геттер и менять логику во всех местах, где это свойство использовалось, в случае геттера — достаточно изменить логику геттера.
А реализуйте ка геттерами это:

public function __get($name)
{
if(isset($this->_attributes[$name]))
return $this->_attributes[$name];
elseif(isset($this->getMetaData()->columns[$name]))
return null;
elseif(isset($this->_related[$name]))
return $this->_related[$name];
elseif(isset($this->getMetaData()->relations[$name]))
return $this->getRelated($name);
else
return parent::__get($name);
}

:)
Я вообще не буду это реализовывать так.

Через некоторое время вы поймёте, почему. И время это приблизится, если вы начнёте использовать тег source, расставлять фигурные скобки там, где надо, стараться не использовать множественных возвратов внутри одного метода и использовать array_key_exists вместо isset.

Ещё ближе к этому моменту вы подойдёте тогда, когда вместо
isset(($this->getMetaData()->...)

будете писать:
$this->getMetadata()->has...();


И совсем близко вы к этому подойдёте тогда, когда поймёте, что в этом магическом __get вы пытаетесь получить некую сущность, которая является конкатенацией множеств атрибутов объекта, различных свойств метаданных и неких свойств родительского класса. Скажем, вы назовёте её «properties». И тогда вы подумаете, что и для метаданных колонки+связи — это некоторые «properties». И т.д.

И получится что-то вроде этого:
public function getProperties()
{
    return array_merge(parent::getProperties(), $this->_attributes, $this->getMetadata()->getProperties());
}
public function getProperty($name)
{
    $properties = $this->getProperties();
    if (!array_key_exists($name, $properties)) {
        throw new \InvalidArgumentException('There is no [' . $name . '] property here.');
    }

    return $properties[$name];
}
Вот только всё равно это не избавит от конфликтов именований. Поэтому я бы вообще не стал вводить такую «property»-сущность, а продолжал бы работать со всеми составляющими отдельно.
На самом деле я не буду так думать, это забота ребят из Yii Framework.
Однако им удалось создать весьма популярный и один из самых быстрых PHP-фреймворков современности.

Не думаю, что у вас есть что-то, что может сравнится с ним по популярности. Да, пускай ваш код более удобно читать новичкам. Но кто его будет читать, если он окажется никому не нужным? Кичиться надо результатом, а не теорией и размышлениями «о смысле жизни».

Ваши примеры явно страдают в производительности перед примером из Yii. C таким же успехом можно писать на Java и не париться. Путь PHP же — это «быстрее, выше, сильнее», а понятный для новичков код — это уже вторичное и зависит от конкретного проекта.
Каким образом всё написанное вами связано с программированием и кодом? Yii — хорошая штука. Но это не делает её идеальной. Тем более при разговоре о коде, который присутствует в первом коммите в гитхаб 5 с лишним лет назад (после этого был лёгкий рефакторинг к упомянутому виду 4 с лишним года назад). В коде класса 2550 строк и 17 контрибьюторов.

Производительность? Зависит от того, как использовать. В сферическом вакууме оптимизировать нечего, в другом вакууме можно лениво подсчитывать свойства один раз. В случае с ActiveRecord (как в приведённом коде), проще было бы один раз подсчитать (схема не так часто меняется). Но это всё экономия на спичках с учётом, что всё это происходит в ORM.

В современном мире нужно не только «быстрее, выше, сильнее». Предположим, у вас итеративный процесс. Наговнокодили «быстро, круто, сильно» первую итерацию, а дальше пошли новую работу искать.

Java-way? Если у вас есть желание и дальше считаться тупоголовыми php-шниками — не вопрос. Я предпочитаю использовать нестрогость PHP для получения некоторых преимуществ перед остальными платформами, вы, видимо, предпочитаете эту нестрогость использовать для говнокода.

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

Кичиться? Лет 8 назад во времена засилия чёрного SEO мой php-софт, написанный за 2 месяца, делал на автомате по 100к уников в день и приносил неслабые деньги, т.е. со своей задачей справлялся. Вот только трогать его я старался как можно меньше — всё висело на соплях. А трогать хотелось, т.к. развивать было что. Даже через год после разработки хотел запустить это на новом сервере — не удалось. Мораль сей басни такова — говнокод тоже бывает крутым и полезным. Чаще всего один раз и в неизменном виде.

P.S. Класс Record в Doctrine 1 состоит примерно из 2700 строк. Doctrine 1 — хороший для своих задач и своего времени ORM. В настоящее время есть Doctrine 2.
В таких случаях я стараюсь использовать публичные свойства, а геттеры и сеттеры я использую для критических свойств, которые необходимо более строго контролировать

Считаю, что удобнее для восприятия использовать что-то одно в отдельно взятом классе. Программист, работающий с классом, может не знать, какое свойство критическое, а какое — нет, и придется ему отвлекаться на выяснение «а как же правильно» для каждого свойства.
Всегда надо использовать геттеры, т.к. это позволяет сохранять интерфейс доступа к объекту при изменении логики работы объекта без применения «магии».
Никакого фанатизма. Единообразие и гибкость при отсутствии каких-либо значимых дополнительных временных затрат.

При наличии современных IDE вообще удивляюсь, что кому-то это может быть неудобно. Создал приватные свойства, на нужные повесил геттеры (public или protected, private для lazy-load'а и т.п.) В итоге пользователь интерфейса набирает в IDE «get» и видит все доступные геттеры.

Это реально ускоряет разработку:
1. Не надо раздумывать, какие свойства делать приватными, а какие — нет.
2. Становится доступен рефакторинг доступа к свойствам.
3. Список доступных для изменения/получения свойств доступен через набор префиксов get/set (при выборе стандартного паттерна именования геттеров/сеттеров).
Не нужно забывать о возможных изменениях кода. Допустим $a->bar у нас был когда-то int и мы делали так
$a->bar = 1;
switch($a->bar) { case 1: ...

Со временем мы пришли к выводу, что нам нужно изменить int на array $a->bar = array(); и теперь по ВСЕМУ коду, который в своей работе использует класс $a, необходимо вносить изменения.

Если бы мы использовали get/set то изменения коснулись бы только этих методов класса
// было
public function setBar($value) {
   $this->bar = (int) $value;
}

// стало
public function setBar($value) {
   $this->bar[] = $value;
}
Не нужно забывать и о том, что изменений вида $a->id = (int) в $a->id = (array) в принципе не должно быть. Переменные следует именовать, чтобы не было неоднозначности. Программист, который единичный целочисленный id преобразовал в массив и оставил его с тем же именем — очень плохой программист.

А если мы уверены в том, что конкретные (а зачастую все) атрибуты классы в дальнейшем не поменяют свой тип — зачем «перестраховываться» от того, чего не будет?
Мне тоже странно видеть размышления на тему, а если вдруг пользователь станет емейл сообщением, то что делать с его возрастом?
Не хочу переходить, на личности, но зачем спорить? За плечами 8 лет ежедневного кодинга на PHP, сейчас мне этот язык нравится все меньше, в больших проектах «магия» и отсутствие типов иногда влечет за собой печальные последствия (в одном месте возвращает метод boolean в другом void и прочее)… «Мы уверены» — да ты можешь быть уверен, что ТВОЙ код не поменяется. Но если ты работаешь не один над проектом, который постоянно развивается, нельзя быть в чем-то уверенным. Это с родни преждевременной оптимизации, когда ты пишешь код основываясь на фичах текущей версии языка забывая о том, что язык изменяется — меняя свое поведение. Тем самым у тебя получается нечитаемый говнокод, который оптимально работает только с одной версией PHP.

Пример из жизни «напиши опрос — ну там ставишь один вариант ответа и голосуешь» -> «ну нам бы хотелось что бы было несколько опросов сразу» -> «ну да, но кроме radio должен быть еще выбор нескольких вариантов ответов» -> «а добавьте так что если выбрали один вариант, появляются другие зависимые от него»… Между -> может пройти и месяц и год и неделя… Код изначально нужно проектировать так что бы его можно было легко модифицировать.

Вариант с магией и огромным switch — плохая практика, во первых он разрастется со временем до такой степени, что понять его через 2 месяца не представится возможным, а заказчику нужно добавить фичу еще вчера…
1. Вот здесь habrahabr.ru/post/197332/#comment_6883056 вы писали от восторга, какой прекрасный код написали ребята из Yii. Почему же у них нет постфиксов «Array» в названиях переменных?

2. Кроме смены типа может смениться логика:
$query->limit = 100000;

И вдруг (по объективным причинам, которые изначально не учли) мы захотели ограничить максимальное возможное количество возвращаемых записей.
public function setLimit($limit)
{
    if ($limit > self::MAX_LIMIT) {
        $limit = self::MAX_LIMIT;
    }
    $this->limit = $limit;
}


3. Кто вам сказал, что в ваш прекрасный int не насуют массивов? Вы это как-то отслеживаете? Почему вы вообще говорите о типе свойства, если разговор идёт о public в языке без строгой типизации?

4. Сменить типа свойства просто — скажем, сменили строковую дату в ISO 8601 на DateTime.
Ребята из Yii говнокодеры, я понял вашу мысль. А вы гораздо умнее их, даже при том что выносите логику поведения на уровень моделей, да-да.
Вы не поняли мою мысль. Моя мысль в том, что у вас у самого нет сформировавшегося подхода.

И поподробнее про логику и уровень моделей. Мне кажется, вы кое-что путаете в терминологии.
Руководствуюсь принципом «поменьше магии».
Эх, опять.

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

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

А выбора всё равно лишаем…
Я считаю что вместо того чтобы вводить магические свойства необходимо рефакторить код. Насчет геттеров и прямого доступа, я уверен, что необходимо чтобы все кодеры писали в одном стиле. Или не используем геттеры совсем или не используем публичный доступ. Конечно же, мы выбираем геттеры. Любая современная IDE должна умет их автоматически генерировать.
За достаточно продолжительный срок разработки на php я использовал все три приведённых способа. И пришёл к такому выводу: когда с кодом работают от 2 человек, то количество магии должно быть сведено к минимуму. Поэтому лучше использовать геттеры и сеттеры. Да, кода становится чуть больше. Да, мы теряем какие-то наносекунды на вызов метода, но плюсы перевешивают минусы. Помимо указанных в публикации, я добавлю такие:
1. Проще рефакторинг
2. Программист всегда знает что происходит: вызов метода или обращение к свойству, а это о многом может рассказать.
Sign up to leave a comment.

Articles