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

radioheaded

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

Silentland

Новичок
Прочитал обо всем... на уровне Википедии, правда. Паттерн ActiveRecord понравился, у самого даже подобные мысли возникали, но откинул, посчитав реализацию чересчур накрученной. Думаю, как применить к моему случаю. У меня куча мелких таблиц вроде типов товаров, видов организаций, списка цветов (оттенков), списка валют, списка размеров (одежды), списка языков, списка городов и т.п. Думаю, подобные второстепенные таблицы в любой базе в большинстве. Все операции с ними однотипные, поэтому класс для, реализующий общие функции для них нужно оставить или сделать им Active Records.

Попробовал на своем примере:
PHP:
//Низкоуровневая работа с БД
static class Sql {
    public function add($tableName) { ... }
    public function del($tableName, $id) { ... }
    public function get($tableName, $id) { ... }
    public function set($tableName, $fieldsArr) { ... }
    public function saveSort($tableName, $posArr, $pidArr) { ... }
    ...
}

//ActiveRecord
class ActiveRecord {
    private function __constructor($tableName) {
        setQueue('add', $tableName)
    }       
    public function del($tableName, $id) {
        setQueue('del', $tableName, $id)
    }
    public function get($tableName, $id) {
         setQueue('get', $tableName, $id) 
    }
    public function set($tableName, $fieldsArr) {
         setQueue('set', $tableName, $fieldsArr) 
    }
    public function save() {
         switch ($queue) {
             /* ... */ Sql::add($tableName)
              /* ... */ Sql::del($tableName, $id)
              /* ... */ Sql::get($tableName, $id) 
              /* ... */ Sql::set($tableName, $fieldsArr)
        }
    }
}
//Распределение поступающих от клиента запросов
switch ($_REQUEST['to']) {
    case 'productsType':
        switch ($_REQUEST['act']) {
            case 'add':
                $result = new ActiveRecord($_REQUEST['tableName']);
                break;
            case 'del':
                $result = ActiveRecord::del($_REQUEST['tableName'], $_REQUEST['id']);
                break;
            case 'get':
                $result = ActiveRecord::set($_REQUEST['tableName'], $_REQUEST['id']);
                break;
            case 'set':
                $result = ActiveRecord::set($_REQUEST['tableName'], $_REQUEST);
                break;
            case 'sort':
                $result = Sql::saveSort($_REQUEST['tableName'], $_REQUEST['posArr'], $_REQUEST['pidArr'])
        };
        break;
    case 'colorList': ...
    case 'sizesList': ...
    case 'currencyList': ...
}
echo json_encode($result);
Какая-то чушь. Бесполезная куча кода. Непонятно, как использовать функцию save() из ActiveRecord. Ведь, чтобы сохранить значения из экземпляра, его надо создать. А при создании добавляется запись в БД. А если мы просто обновляем ничего добавлять не нужно.
Про Data Mapper почитал, что нашел на русском... Вроде про меня, но с какой стороны подойти не ясно...

Вероятнее всего, имелось в виду другое. Вам хотели сказать, что нужно доставать только те записи, которые вам нужны в данный момент.
Неа. Мой изначальный вопрос звучал как: «Как в запросе select написать обращение к столбцу `x` и если он не существует все его значения выдать как null. В крайнем случае, вообще, ничего не выдать, главное, чтобы запрос не завершался ошибкой и выдавал бы остальные значения» Речи о количестве записей не велось. Только о наборах полей
 

craz

Нестандартное звание
Неа. Мой изначальный вопрос звучал как: «Как в запросе select написать обращение к столбцу `x` и если он не существует все его значения выдать как null. В крайнем случае, вообще, ничего не выдать, главное, чтобы запрос не завершался ошибкой и выдавал бы остальные значения» Речи о количестве записей не велось. Только о наборах полей
Правильно поставленный вопрос = 70% ответа. В вашем случае, вам этого хотеться не должно. В такой постановке задача решается на пхп, выборкой всех значений, и потом обработкой данных. ИМХО опять же.
 

Василий М.

Новичок
- это твоя "низкоуровневая" приблуда. Низкоуровневая работа с БД должна осуществляться минимум - через родные функции/классы, максимум - через обертки (раз, два), не замещающие никоим образом возможности работы с SQL.
Никаких методов
PHP:
get($tableName, $id)
ActiveRecord должен выглядеть примерно так:
PHP:
class User
{
	protected $table = 'user';

	protected $db; // Класс для работы с СУБД, mysqli или mysql 

	public function __construc(Database $db)
	{
		$this->db = $db;
	}

	// Свойства - такие жэе, как поля в таблице user
	protected $id, $first_name, $last_name;
	
	public function findById($id)
	{
		$data = $this->db->query('select id, first_name, last_name from ' . $this->table . 'where id = ' . $id);
		// нашли данные?
		if ($data) {
			// Транслируем их в свойства класса 
			foreach ($data as $key => $value) {
				if (isset($this->$key)) {    // теперь в объекте есть свойства 
					$this->$key = $value;    // $this->id, $this->first_name, $this->last_name
				}
			}

		}
	}
	
	public function setFirstName($name)
	{
		$this->first_name = $name; 
	}

	public function getFullName()
	{
		return $this->first_name . ' ' . $this->last_name; 
	}
	
	public function save()
	{
		if ($this->id) {
			$this->db->query('UPDATE ' . $this->table . ' SET first_name="' . $this->first_name . '", last_name="' . $this->last_name . '" where id = ' . $this->id);
		} else {
			$this->db->query('INSERT INTO ' . $this->table . ' VALUES(null, "' . $this->first_name . '", "' . $this->last_name . '")');
		}
	}
}
и и клиентский код
PHP:
$db = new Database('host', 'user', 'password');

$user = new User($db);
$user->findById(123); // нашли пользователя
echo $user->getFullName(); // Петя Иванов
$user->setFirstName('Федя');
echo $user->getFullName(); // Федя Иванов
$user->save();
 

Silentland

Новичок
Правильно поставленный вопрос = 70% ответа. В вашем случае, вам этого хотеться не должно. В такой постановке задача решается на пхп, выборкой всех значений, и потом обработкой данных. ИМХО опять же.
Поэтому я и задавал вопрос на форуме sql, т.к. нужно было сделать это одним запросом без ПХП :)

ActiveRecord должен выглядеть примерно так:
Ага, теперь становится понятно, что создание экземпляра класса не несет в себе автоматическое создание поля в БД. Так же понятно, что любая манипуляция данными, не создающая новой записи всегда использует лишний запрос findById($id). Т.е. этот шаблон удваивает количество запросов к базе (косяк). Судя по ссылке из следующего поста, не возбраняется увеличивать его универсальность, определяя переменные $class_name, $table_name и т.п.

Автору топика, если он хочет AR, стоит взять что-то вроде https://github.com/j4mie/paris.
Сам нормально не напишет, опыта не хватит
Точно не напишу! Спасибо за ссылку, очень не хватает примеров хорошего кода для ПХП!
 

Василий М.

Новичок
Ага, теперь становится понятно, что создание экземпляра класса не несет в себе автоматическое создание поля в БД. Так же понятно, что любая манипуляция данными, не создающая новой записи всегда использует лишний запрос findById($id). Т.е. этот шаблон удваивает количество запросов к базе (косяк). Судя по ссылке из следующего поста, не возбраняется увеличивать его универсальность, определяя переменные $class_name, $table_name и т.п
поясни, пожалуйста, выделенные вещи. я ничего не понял. где ты тут косяки увидел? где "лишний запрос"?
 

Silentland

Новичок
Очень просто. Нам нужно установить значение в какое-либо поле
PHP:
$user->setFirstName('Федя');
Но для этого нужен экземпляр $user
Чтобы его получить нужно проделать следующее
PHP:
$user = new User($db);
$user->findById(123); // нашли пользователя
Что несет в себе либо создание записи, либо запрос на выборку данных по айдишнику.
Просто установить айдишник, без запроса, мы не можем т.к. он protected
 

craz

Нестандартное звание
каша в голове... по-моему...

$user = new User($db); - это по твоему как то влияет на данные в базе? (Кстати $db как аргумент при создании объекта, тоже имхо плохое архитектурное решение...)
или это
$user->findById(123);
?
 

fixxxer

К.О.
Партнер клуба
(Кстати $db как аргумент при создании объекта, тоже имхо плохое архитектурное решение...
А какое хорошее? global $db?

Другое дело, что инстанциировать лучше не напрямую, и $db может передавать ModelFactory или IoC.

Еще лучше, если не $db, а менеджер соединений, но это ТСу точно ни к чему
 

Silentland

Новичок
Да $db, тут вообще, не при чем
Пусть будет $user = new User(); Ок.
У этого $user все хорошо, но $this->id равен null, а если это так, то устанавливая параметры мы создаем новую запись в БД. Нам это надо? Нет. Что делать? Остается одно: $user->findById(123);
А что делает $user->findById(123)? Она первой же строчкой запрашивает у базы все параметры о пользователе с id=123. А нам они и не нужны совсем для обновления информации. Айдишник мы и так знаем. На лицо лишний запрос... Или, правда, чего-то не понимаю...
 

craz

Нестандартное звание
А какое хорошее? global $db?

Другое дело, что инстанциировать лучше не напрямую, и $db может передавать ModelFactory или IoC.

Еще лучше, если не $db, а менеджер соединений, но это ТСу точно ни к чему
ну не так согласись, чтобы во все модели передавать хендлер соединения... давайте еще параметры бд туда пихать и в каждой модели подключаться...

Silentland
К сожалению не понимаешь...
смотри $user->update($arrayFields); || $user->delete($id);
зачем сначала получать данные то?

Понимаешь или нет?

$this->id не равен нулю если у тебя есть __get($name) и в нем ты разобрав return fields; из function fetchAll($id){} отдашь $this->id.

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

fixxxer

К.О.
Партнер клуба
Хэндлер соединения, конечно, не нужно :)

Я про то, что популярный подход с App::db() - это вовсе не образец для подражания.

protected getDb(), который берет "откуда-то", хоть бы и из App::db() в текущей реализации - уже лучше: это всегда можно изменить.
 

craz

Нестандартное звание
Хэндлер соединения, конечно, не нужно :)

Я про то, что популярный подход с App::db() - это вовсе не образец для подражания.

protected getDb(), который берет "откуда-то", хоть бы и из App::db() в текущей реализации - уже лучше: это всегда можно изменить.
я это и имел ввиду. вообще по сути надо иметь class Db и уже от него наследовать все модели. Такой подход мне кажется оптимальным.
 

radioheaded

PHP нуб
На лицо лишний запрос... Или, правда, чего-то не понимаю...
И не надо ничего понимать прямо сейчас. Если тебе что-то не подходит — не используй это. Когда будет не хватать — сам дойдешь до верного решения.

В крупных проектах есть десятки причин всегда выбирать сущность перед обновлением. Самое простое — проверить на существование. Если вы будете делать просто update, то как вы узнаете, обновилась запись или нет? Допустим, что новое значение поля, которое вы хотите обновить, такое же, как и старое. Вы не сможете отличить ситуацию «записи с таким идентификатором не было» от «ничего не изменилось». Запрос просто выполнится и вернет 0 affected rows.

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

craz

Нестандартное звание
ага ему все стало ясно как белый день...
 

Silentland

Новичок
К сожалению не понимаешь...
смотри $user->update($arrayFields); ...
А что мы обновляем-то, какую строчку? в запросе у нас where id = ' . $this->id .'
А если __get($name) нет? Экземпляр только что создан, откуда там ему взяться?

По-простому, чтобы обновить нам нужно, чтобы в $this->id был нужный нам айдишник. Как его туда поместить, не используя findById($id)? Использовать __set($id)?


И не надо ничего понимать прямо сейчас. Если тебе что-то не подходит — не используй это. Когда будет не хватать — сам дойдешь до верного решения.
Скорее всего так и будет)

Кстати, ковыряю сейчас движок, написанный крутым программером (если зп 120 т.р., которую он получал тогда показатель крутости), там класс для работы с БД выглядит так:
PHP:
	namespace Lib\Sql;
	
	class MySql {
		
		private $host;
		private $user;
		private $pass;
		private $db;
		private $connect;
		
		public function __construct() {
		}
		
		public function init($params) {
			
			$this->host = $params[0];
			$this->user = $params[1];
			$this->pass = $params[2];
			$this->db = $params[3];
			
			$this->connect();
			
		}
		
		public function connect() {
			
			$this->connect = mysql_connect($this->host, $this->user, $this->pass) or $this->error(mysql_error());
			
			mysql_select_db($this->db);
			$this->query("SET NAMES 'UTF8'");
			
		}
		
		public function getOne($entry, $query) {
			
			$res = $this->query("select `" . $entry . "` from " . $query . "");
			$row = $this->fetch($res);
			
			return $row[$entry];
			
		}
		
		public function count($entry, $query) {
			
			$res = $this->query("select count(`" . $entry . "`) as `" . $entry . "` from " . $query . "");
			$row = $this->fetch($res);
			
			return $row[$entry];
			
		}
		
		public function getLastId() {
			
			return mysql_insert_id();
			
		}
		
		public function query($text) {
			
			return mysql_query($text);
			
		}
		
		public function fetch($result) {
			
			return mysql_fetch_assoc($result);
			
		}
		
		public function error($errorText) {
			
			print $errorText;
			
		}
		
	}
В коде вызывается так:
PHP:
public function getIdFromUrl($url) {
			
			$sql = \Lib\sql();
			
			return $sql->getOne("id", "`categories` where `url` = '" . $url . "';");
			
		}
Собственно, это вся надстройка над БД, которая есть в движке. Без всяких active records и т.п.
 

fixxxer

К.О.
Партнер клуба
ковыряю сейчас движок, написанный крутым программером
обычный бессмысленный говнокод. Mysql-функции просто обернуты в класс безо всякого смысла. Тот же PDO дает куда больше возможностей из коробки.

Посмотри http://phpfaq.ru/safemysql.

если зп 120 т.р., которую он получал тогда показатель крутости
Ну ты понимаешь.
 
Сверху