Pull to refresh

Comments 50

Достаточно много видел разных вариантов реализации возможностей которые есть в обычных ОО языках (Delphi, Java, ActionScript) — и которых частенько не хватает php. А именно get & set методы.

ru.php.net/manual/ru/language.oop5.overloading.php
или это не то? %)
К сожалению, нет) это далеко не тоже самое =)

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

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

Возможно найду время чуть позже. и постараюсь расписать разницу — чем такой ооп(с массивами) отличается от привычного)
Определять геттеры/сеттеры вручную — сомнительный подход.
Что значит вручную?
Как то не понятно.

Когда пишешь геттер/сеттер — ты в любом случае пишешь его вручную. где бы ты этого не писал.

А вот, если Вы имели ввиду — полностью автоматизация без прописывания методов setProp|getProp — вот это действительно сомнительный подход. При таком подходе — вобще теряется смысл в геттерах/сеттерах ))
>полностью автоматизация без прописывания методов setProp|getProp

Именно это и имел в виду. Прописывать сеттеры/геттеры есть смысл при каком-нибудь нестандартном поведении объекта, когда, например, перед setEmail надо проверить валидность емаила. А так должно хватить магических __get, __call
или Source Code Highlighter не умеет пхп, или вы его не включили, но выглядит не очень красиво
скорее хабр не отображает, ввиду моей малой кармы или хабрасилы — потому что подстветка в коде была)
отображает теги наполовину? сомнительно.
да и не вижу я тут упоминания пхп:
именно там и подсвечивал. при редактировании — все фонты с цветами видны — но при просмотре топика — они по какой то причине вырезаются.
Должно хватать кармы, попробуйте снова :)
попробуйте убрать нумерацию строк — должна появиться подсветка.
ну вот, чтото более менее похожее на подстветку =)
Используя такой способ, мы лишаемся возможности автоподстановки, да и лишнее наследование ни к чему. Если самому влом писать геттеры/сеттеры, можно пользоваться Zend Studio for Eclipse, там можно автоматически генерировать их.
мне кажется вы не совсем поняли.
автоподстановка никуда не пропадает, точно также можно сгененировать их в ZDE
НО данный класс — добавляет функциональности.

Если просто сгенерировать/прописать сеттеры/геттеры — вы не сможете воспользоваться элементарным присваиванием без функции.
Вы должны будете писать $obj->setName('Michael') вместо того, чтобы просто написать $obj->name='Michael';

Иногда второй вариант более чем предпочтителен.
Решение интересное, но (как всегда) подходить к этому стоит с головой =) Нужно держать в голове, что при использовании подобного подхода при интенсивных вычислениях вы на ровном месте получаете пенальти — это медленнее обычного использования на порядок.

Код test.php

// определения классов Object и Rectangle
<...>

// Стандартный аналог
class Rectangle2
{
    public $width = 400;
    public $height = 1;
    public function getArea(){
        return $this->width*$this->height;
    }
}

$iterations = 10000;

echo "Let's perform $iterations simple iterations!\n";

$r = new Rectangle();
$t = microtime(true);
for ($i = 0; $i < $iterations; ++$i) {
    $r->height++;
    $r->height - $r->width - $r->area;
}
echo "Advanced class took ".round($res = microtime(true) - $t, 3)." sec\n";

$r = new Rectangle2();
$t = microtime(true);
for ($i = 0; $i < $iterations; ++$i) {
    $r->height++;
    $r->height - $r->width - $r->getArea();
}
echo "Standard class took ".round($res2 = microtime(true) - $t, 3)." sec\n";
echo "Difference: ", round($res/$res2, 2), " times!\n";


Результаты:

$ php test.php 
Let's perform 10000 simple iterations!
Advanced class took 1.323 sec
Standard class took 0.102 sec
Difference: 13.03 times!


Так что вот так.

PS: Ну и, разумеется, преждевременная оптимизация — корень всех зол (с) противоположность пряника
«...-преждевременная оптимизация — корень всех зол-...»

именно =)
разница достаточно копеечная (учитывая что 10к присваиваний врятли будет в рамках одного вызова)
а быстродействие — как бы ни старался, на серьезных проектах без кеширования не обойтись.
а там где есть мудрое кеширование — вопрос с подобным быстродействием впринципе гораздо менее актуален, чем предельная прозрачность, удобство архитектуры и скорость правки, расширения =))
А что нить типа этого?

class Container{
private $_islockedNames = true;
protected $_parameters;

public function __constructor($islockedNames = true){
$this->_islockedNames = (bool)$islockedNames;
}

public function __get($name){
if (isset($this->_parameters[$name])) {
return $this->_parameters[$name];
}
throw new EConteiner('__get()->'.$name);
}
public function __set($name, $value){
if (!$this->_islockedNames || isset($this->_parameters[$name])) {
$this->_parameters[$name] = $value;
}
throw new EConteiner('__set()->'.$name);
}
В принцепе да, только защелка на сет для неизвестных переменных.
Хорошо, еще вариант

class Container{
public function __get($name){
if (isset ($this->$name)) {
return $this->$name;
}
}
public function __set($name, $value){
if (isset ($this->$name)) {
$this->$name = $value;
}
throw new Exception();
}
}
1. isset в данном случае использовать ни в коем случае нельзя, посколько если значения свойства будет NULL — isset = вернет false, в то время как само свойство всё таки существует =)

2. если не пользоваться сеттерами/геттерами и присваивать вот так вот — в атоматическом режиме. т.е. если есть свойство (protected|private) то его присваивать — теряется смысл самих методов __get & __set и сокрытия свойства в protected
1. насчет isset, да не проверил каюсь
2. не согласен, никто не мешает поставить дополнительную логику с одной стороны, а с другой ваш вариант делает тоже самое хотя на нем больше навернуто:-)

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

просто функции __get & __set различают в данном случае свойства типа
1.protected
2.protected у которых есть getter|setter
3.свойства которых у объекта вовсе нет

плюс к тому, debug_backtrace — позволяет показать — где в действительности произошла ошибка(т.е. в каком скрипте на какой строке произошла попытка вызова несуществующей переменной)
>>1.protected
>>2.protected у которых есть getter|setter

что можно отдовать, что нет можно определять префиксом '_' (или каким нибудь еще) просто договоренность в именовании

debug_backtrace тоже легко цепляется, хотя мне больше нравится ошибки ловить через систему ексепшенов

ченить типа этого:
class Container{
public function __get($name){
if (property_exists($this,$name) ) {
if ( $name[0] !=='_' ){
return $this->$name;
}
/*
$backtrace=debug_backtrace();
trigger_error(
sprintf('Cannot access protected property %s::$%s in %s on line %s',$this->getClass(),$name,$backtrace[0]['file'],$backtrace[0]['line']), E_USER_ERROR);
*/
throw new EContainer('Cannot access protected property'. $name);
}
/*
$backtrace=debug_backtrace();

trigger_error(
sprintf('Notice: Undefined property: %s::$%s in %s on line %s',$this->getClass(),$name,$backtrace[0]['file'],$backtrace[0]['line'])
, E_USER_NOTICE);
*/
throw new EContainer('Notice: Undefined property'. $name);
}
public function __set($name, $value){
if (property_exists($this,$name) ) {
if ( $name[0] !=='_' ){
$this->$name = $value;
}
/*
$backtrace=debug_backtrace();
trigger_error(
sprintf('Cannot access protected property %s::$%s in %s on line %s',$this->getClass(),$name,$backtrace[0]['file'],$backtrace[0]['line']), E_USER_ERROR);
*/
throw new EContainer('Cannot access protected property'. $name);
}
/*
$backtrace=debug_backtrace();

trigger_error(
sprintf('Notice: Undefined property: %s::$%s in %s on line %s',$this->getClass(),$name,$backtrace[0]['file'],$backtrace[0]['line'])
, E_USER_NOTICE);
*/
throw new EContainer('Notice: Undefined property'. $name);
}
}

class ExContainer extends Container {
private $_a = 'aaaa';
protected $c = 'cccc';
}
По поводу отлавливания ошибок через Exception — вопрос спорный довольно.
По скольку исключения и ошибки все таки — разные по своей сути.
Деление на 0 в большинстве случае исключительная ситуация, которая произошла скорее по непонятным причинам — чем осознанно или в следствии опечатки — была вызвана конструкция $x/0;
А вот ошибки нам указывают на явные огрехи, которые были допущены при проектировании, разработки и т.д.

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

опять же банально вводим уровень ошибки как часть ексепшена
0 Fatal
1 Exception
2 Warning
3 Notice

ну и кому сколько надо

— «юзер запросил, что не положено»

согласен — это как раз таки случай Исключительной ситуации, поскольку юзер может запросить что угодно, и ошибка («что не положено») произойдёт не по нашей вине, но мы как порядочные кодеры — ее не пропустим — отловим и обработаем.

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

другой пример:
модуль запросил, что то из контейнера (думал, что там это уже есть)
функция которая забирает(может __get, а может еще кто то) сгенерила ексепшен
модуль словил сказал вах и сделал запрос в БД(или еще куда)

ЗЫ: дело вкуса
— «если что то идет не так, то не важно кто виноват надо это пресекать, а единая система „

скажу отличие Error от Exception по другому ))

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

это можно организовать разными уровнями ексепшена, ексепшен с англицкого исключение.
да и просто когда у тебя проскакивает ошибка аж до боего сервера. пользователю намного приятней смотреть на надпись «сообщите пользователю», чем на пустой лист или на текст с логином и паролем от БД.
Пользователю должно быть глобоко до фени, запланированный это ексепшен или косяк в коде
Соглашения об именования — можно было бы и придерживаться. но опять таки
посмотрите на пример в посте с прямоугольником.

У него есть виртуальное свойство «area» которое я могу прочитать, но по понятным причинам — я его не могу установить — было несколько бредово))
И в данном случае соглашение об именовании никак бы не помогло разрешить ситуацию =)
если цепляться за соглашения об именах, то сделать с морды две черточки для закрытах совсем, одну для закрытых га запись
а можно сделать список с правами аля $rights = [$paramName]=>rigt, где райт 0,1,2
заполнять этот список будет проще, чем прописывать гетеры
а это разве не мудрёно?
это как раз таки избыточность, которая ни к чему =)

зачем придумывать, ограничивать себя по именованию — когда можно сделать как в посте и писать дальше, не заботясь об именовании =)
ну про именование отметился, а почему про права не покритиковал?
это было про всё)) и про права в том числе)
Если у вас методы называются «по-верблюжьи», и к свойствам вы хотите обращаться со строчной, то в классе Object хорошо бы чуть изменить строчки, где формируется имя метода по переданной переменной. Например:
$getterMethod = 'get' . ucfirst($name);

А вообще, мне нравится подход. :)
изменить можно, только нужно ли? — так если только для красоты ))
php ведь язык регистронезависимый ;)
Я неправ.
Но тут вы тоже как-то погорячились: имена переменных и свойств от регистра все же зависят, а вот про имена методов я как-то не знал О:)
согласен, нужно было уточнить))
К недостаткам данного варианта, можно добавить то, что в классах унаследованных от Rectangle работать getter/setter уже не будет

class MyRectangle extends Rectangle{
function dummy(){
//getHeight() не вызывается
var_dump($this->height);
}
}

$mr = new MyRectangle();
$mr->dummy();

в Вашей интерпретации метод getHeight и не должен вызываться, поскольку к свойству height Вы обращаетесь внутри класса.
В данном случае height находится в области видимости класса MyRectangle — и следовательно magic метод __get не вызывается.
если вы попробуете вызвать снаружи — class MyRectangle extends Rectangle{ } $mr = new MyRectangle(); echo $mr->height;

метод getHeight — сработает.
Так что — работает так как и должно))
Ну если так и задумано…
что нельзя производить внуков от Object

Тогда действительно все правильно :)
— «что нельзя производить внуков от Object» —

Можно и нужно производить =)
UFO just landed and posted this here
UFO just landed and posted this here
Не вижу противоречий в Ваших комментах и моём топе =)

Логику никто не отменял, и я вроде не предлагал использовать повсеместно ;)
Собственно статья вовсе не рассказывает где и как именно применяются Getter/Setter в программировании,
она лишь показывает достаточно красивое и эффективное решение данного вопроса не более, и ни в коей мере не является панацей от всего =)

Например, мне в силу особенностей некоторых моих реализаций — более чем необходим был инструментарий с которым я мог бы совершать прямые присваивания $this->myProp=$value;
и при этом пользоваться функциональностью Getter/Setter и более того, мне крайне необходимо было избежать случайных присваиваний =)

А про быстродействия — то, что кешируется — выводится «моментально» — а! микросекундные! задержки при совершении административных операций — мне кажется обсуждать нет смысла ;)
не понимаю как топ мог залесть в минус… ведь клевое решение поставленной задачи, если вам это не нужно это не повод ставить «-»
Вообще, конечно, есть смысл автоматизировать написание свойств когда при присвоении есть еще какая-то логика, например проверка полученного значения.
Можно через __call().
Есть ещё одна тонкость.

// выше используем код из примера к публикации

$rect->height=1000;
var_export(isset($rect->height));

выведет false.

Надо в класс Object добавить метод __isset, к примеру так:
public function __isset($name)
{
        $getter='get'.$name;

        if(method_exists($this,$getter)){
            return $this->$getter()!==null;
        }elseif(property_exists($this,$name)){
            return true;
        }

        return false;
 }


Теперь
$rect->height=1000;
var_export(isset($rect->height));
var_export(isset($rect->weight));

выведет true false

Как это реализованно в YII.
Sign up to leave a comment.

Articles