геттеры, сеттеры и магические методы. Когда они оправданы?

Silentland

Новичок
У нас есть класс user, работающий с пользователями. Предположим, нам нужно работать со статусом пользователя для чего в лоб можно написать пяток методов:
PHP:
public function setCurrentStatus($value) {
    $_SESSION['status'] = $value;
}
    
public function getCurrentStatus($id) {
    return isset($_SESSION['status']) ? $_SESSION['status'] : "guest";
}

public function updCurrentStatus($id) {
   setCurrentStatus(readStatus($id));
}

public function readStatus($id) {
        $res = mysql_query("select `status` from `users` where `id` = '" . (int)$id . "'" );
        $row = mysql_fetch_assoc($res);
        return $row['status'];       
}

public function changeStatus($id, $value) {
    mysql_query("update `users` set `status` = '" . $value . "' where `id` = '" . (int)$id . "'");
}
Можно парные методы заменить одним и вызывать его с параметром или без
PHP:
public function currentStatus($value) {
    if (isset($value)) {
         $_SESSION['status'] = $value;
    } else {
        return isset($_SESSION['status']) ? $_SESSION['status'] : "guest";
    }
}
Можно пойти дальше и переопределить методы __get и __set, тогда обращаться будет еще проще.

А если выпить Фанты, то можно ответвить, к примеру, два класса userSession и userDB и сделать там методы __get и __set универсальными. Пример __get для работы с базой
PHP:
public function __get($param) {
    if (array_key_exists($params, $this->params)) {
        $res = mysql_query("select `" . $param . "` from `users` where `id` = '" . $this->getCurrentId() . "'" );
        ...
        return $param_from_db;
    }
}
Вообще, удобно!

Но т.к. у меня это все в теории, а форумчане явно с пользователями давно работают, скажите, что предпочтительнее в большинстве случаев?
 

Silentland

Новичок
Ага, но статус-то храниться не в $this->status, а в базе или сессии и чтобы его оттуда взять/положить нужен посреднический метод
 

fixxxer

К.О.
Партнер клуба
public function readStatus($id) {
$res = mysql_query("select `status` from `users` where `id` = '" . (int)$id . "'" );
$row = mysql_fetch_assoc($res);
return $row['status'];
}
А с остальными полями пользователя как? Тоже по одному полю select-update и на каждое по методу?
 

Silentland

Новичок
Насколько знаю, остальные поля в профиле обычно обновляют скопом. Т.е. просто отправляется форма с заменой значений, но у меня все не так... Написал класс, который обрабатывает различные списки, меню и проч. Там есть методы add, delete, update и get, на вход принимается массив данных (обычно из $_REQUEST), имена допустимых полей и имя таблицы к которой применяются запросы. Соответственно 80% однообразных полей в таблицах заполняется через него.
 

fixxxer

К.О.
Партнер клуба
Ну так может представить все поля пользователя в виде ArrayObject, а сохранять все кучкой вызовом метода save()? ;)

И что вообще там в методах класса делает $id? Должен же уже быть new User($id). Если же методы обновления статуса по id нужны, то они должны быть статическими.
 

Silentland

Новичок
Ну да, пользовательская анкета, заполняемая при регистрации, единственное, что отправляется на сервер массивом) В остальных случаях одно поле — одна запись. Вся логика как в этом примере. Пользователь создает поле (перетаскивает картинку), сразу же создается пустая запись в БД и возвращается ее id, по которому уже идут запросы на удаление/обновление (начинается непосредственная загрузка картинки) и поля все работают так как в примере подписи к картинкам: изменил, убрал фокус, сохранилось. Думаю и регистрационную анкету так сделать, чтобы пользователь создавался сразу же при заполнении любого поля. Не знаю, насколько будет хорошо с точки зрения безопасности, зато можно вернуться через день и продолжить заполнение анкеты. Куки-то сохранились)

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

Zvook

Новичок
Думаю и регистрационную анкету так сделать, чтобы пользователь создавался сразу же при заполнении любого поля.
Ну не уверен что это хорошая затея. Если я правильно понял о чем речь, то в модели можно сделать какойнибудь статический метод, типа ::findBy(array('id' => $id)), который вернет объект модели с выборкой из базы данных. А new User(); не принимал бы никаких аргументов и означал бы просто заведение экземпляра модели, не обязующего к сохранению чего-то в базу. А по поводу того, что id не будет доступен пока не произойдет запись в базу, ну так это вроде как нормально. Просто при model->save() она автоматически обновится подсосав id записи, которую только что записала в базу.
Или я не о том вообще? :)
 

Silentland

Новичок
На счет анкеты, заполняемой частями. Сейчас интернет упорно движется в этом направлении. Т.е. вы ходите по сайту, набираете товары, что-то делаете и на вас сразу же создается запись, привязанная к куки. Соберетесь купить — попросят заполнить пару недостающих полей. А анкеты, передаваемые целиком останутся, наверное, только в банках )

Про model->save() (или $user->save() в моем случае) не очень понятно, что обновится? Внутренних параметров в классе User нет, да и смысла в них, наверное, нет, потому что единственный параметр, который где-то сохраняется, это id и хранится он в сессии. Остальные функции работают либо с ним, либо с вручную заданным id.

А работают примерно так:
PHP:
function abc ($id, $value = null) {
    if (is_null($value)) {
            $value = $id;
            $id = $this->getCurrentId();
    }        
   ...
}
И тут у меня вопрос, как лучше обозначать параметры, которые могут отсутствовать?
Если делать
PHP:
function abc ($id, $value) {
    if (isset($value)) ...
}
abc($val)
то интерпретатор ругается, мол не указаны все параметры, поэтому сравниваю с null. Как обычно поступают в таких случаях?
 

Silentland

Новичок
Отличная статья! Взял на заметку. Жаль, что там не рассматриваются методы __get() и __set(). Единственное, что останавливает от использования: «Невозможно использовать перегруженные свойства в других языковых конструкциях, кроме isset()»

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

Василий М.

Новичок
зачем хранить данные пользователя в свойствах класса. Получается, что при создании экземпляра придется делать запрос к БД и перезаписывать все свойства актуальными значениями
а как ты хотел иначе?

Жаль, что там не рассматриваются методы __get() и __set()
там нечего рассматривать в общем-то. Их задача тягать туда-сюда данные из $this или из $this->data. Больше ничего.
 

Silentland

Новичок
а как ты хотел иначе? + lazy initialization?
В большинстве случаев от класса требуется всего один параметр. status, id или name, к примеру. А по-вашему придется каждый раз тягать все поля из базы...

там нечего рассматривать в общем-то
Из этого нечего как раз интересуют особенности типа «Невозможно использовать перегруженные свойства в других языковых конструкциях, кроме isset()». К сожалению, у меня недостаточно опыта, чтобы сказать насколько часто придется использовать полученные параметры в других конструкциях. Если часто, то грош цена этим методам.
 

craz

Нестандартное звание
В большинстве случаев от класса требуется всего один параметр. status, id или name, к примеру. А по-вашему придется каждый раз тягать все поля из базы...
Что в полях тебе в этих? Это спички забудь, плюс ты себя ограничиваешь в возможности выбирать по условиям, к примеру тебе надо будет выбрать всех юзеров которые забанены, как ты будешь выходить из ситуации? Или выбрать по id только того юзера у которого ban = 'y'. А если с таким id пользователь не забанен вывалить ошибку?

Это с потолка примеры, но придумать можно очень много примеров из жизни
 

Василий М.

Новичок
А по-вашему придется каждый раз тягать все поля из базы...
почему?
Ты что-нибудь слышал про ActiveRecord? Про DataMaper?
Ты сейчас пишешь ерунду:
PHP:
public function readStatus($id) {
        $res = mysql_query("select `status` from `users` where `id` = '" . (int)$id . "'" );
        $row = mysql_fetch_assoc($res);
        return $row['status'];       
}
ты ОДНИМ запросом можешь получить все данные из базы:
PHP:
$user->findById(123); // в user теперь все данные одной строки таблицы
echo $user->getName(); // петя 
$user->setName('вася'); // изменили свойство объекта 
$user->save(); // сохранили
 

radioheaded

PHP нуб
Я бы советовал вам пересмотреть вообще ваш подход к работе с сущностями в БД. Есть несколько простых правил, которые значительно облегчают жизнь. Я попробую сформулировать основные.

- Выбирать сущность из БД целиком. Вообще обычно это даже не обсуждается, но вы, зачем-то, хотите дергать каждое поле в отдельности. Поверьте, для современной БД разницы никакой.
- Сосредоточить работу с сущностью в одном месте. То есть, когда вы обновляете запись, не важно, какие именно поля, это должно происходить ровно в одном месте. Например, в определенном методе определенного класса. Так проще контролировать происходящее и это гарантирует вам, что при любых изменениях в структуре сущности (и в БД) вам придется проделать минимум действий (а в идеале — вообще ничего не придется делать).
- Аккумулировать изменения в объекте сущности. Не нужно при изменении какого-то свойства сущности каждый раз реально изменять его в БД. Объект должен собирать все изменения, о которых вы ему сообщаете, и затем уже одним запросом выполнить обновление записи в БД. Немедленное изменение может потребоваться в некоторых случаях, но вы можете применять изменения в любой момент.

Вообще, вам не мешало бы почитать теории. Начните с гугления ActiveRecord. Или, если чувствуете в себе силы, лучше DomainObject и DataMapper. Просто в двух словах довольно сложно объяснить все преимущества этих подходов и все недостатки вашего кода.
 

Silentland

Новичок
ты ОДНИМ запросом можешь получить все данные из базы: + Никто не запрещает вытягивать по одному полю, только будет это экономией на спичках. + - Выбирать сущность из БД целиком. Вообще обычно это даже не обсуждается, но вы, зачем-то, хотите дергать каждое поле в отдельности. Поверьте, для современной БД разницы никакой.
Вот так номер, а мне на sql.ru по этому поводу прямо противоположное сказали: «Ага, а еще можно памяти докупить. Ну, надо же где-то хранить туеву хучу ненужных данных, что вернулись как результат запроса.» Хорошо, что здесь переспросил!

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

Про ActiveRecord, DomainObject и DataMapper почитаю, спасибо за наводку!
 
Сверху