Стоит ли открыть исходный код ORM для PHP?

Хотелось бы узнать будет ли полезна кому-то система по работе с persistent объектами на PHP. На протяжении нескольких лет она уже работает на большом количестве коммерческих проектов с закрытым исходным кодом. И т.к. за эти годы, ничего похожего не появилось — хотелось бы знать — заинтересует ли кого-то.

Система разрабатывалась по принципу — как было бы удобно, чтобы именно ООП, без миксования работы с одной сущностью и коллекциями сущностей, ну и без дополнительных структур для хранения данных. И… без оглядки на какие-либо паттерны.

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

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

class User extends DataObject{
}


Т.е. заэкстендиться от класса DataObject

Чтобы получить пользователя:

class User extends DataObject{
    /**
     * Will return user by id
     * @param int $userId
     * @return User
     */
	public static function getById($userId){
 	return parent::_getByCondition(array('user_id'=>$userId),__CLASS__,'users');	
	}
}


Вот так просто. Можно аналогичным образом сделать ещё getByName и что угодно ещё. У объекта узер появятся get, set, save методы. Кроме парентового статического метода _getByCondition есть _getByQuery, _create.

Сам класс юзера может выглядеть так:

class User extends DataObject{
	const TABLE='users';
    /**
     * Will return country of the user
     *
     * @return Country
     */
	public function getCountry(){
 	return Country::getById($this->get('country_id'));
	}

   /**
     * Will return user by id
     *
     * @param int $userId
     * @return User
     */
	public static function getById($userId){
 	return parent::_getByCondition(array('user_id'=>$userId),__CLASS__,self::TABLE);	
	}

   /**
     * Will return user by email
     *
     * @param string $email
     * @return User
     */
	public static function getByEmail($email){
 	return parent::_getByCondition(array('email'=>$email),__CLASS__,self::TABLE);	
	}
    /**
     * Will create user
     *
     * @param string $email
     * @param string $password
     * @param string $firstName
     * @param string $lastName
     * @return User
     */
	public static function create($email,$password,$firstName,$lastName,Country $country){
		return parent::_create(array('email'=>$email,
					'password'=>$password,
					'first_name'=>$firstName,
					'last_name'=>$lastName, 
					'country_id'=>$country->getId()),__CLASS__,self::TABLE,'id');
	}
}


Далее можно, например, создать пользователя и немного поманипулировать данными.

echo User::create('vasja@pupkin.com','123','Vasja','Pupkin')->getCountry()->getCurrency()->getCode();


Пользователь полученный разными методами — getById и getByEmail — будут ссылками на один и тот же объект.

По сути — один рядок в таблице соответствует одному объекту.

Для работы с несколькими объектами есть DataObjectGroup и аналогичным образом для того, чтобы этот объект был коллекцией — нужно заэкстендить специальный класс DataObjectGroup.

class UsersGroup extends DataObjectGroup{
	/**
	 * Will return premium users (UsersGroup of Users)
	 *
	 * @return UsersGroup
	 */
	public static function getPremiumMembers(){
		return parent::_getByCondition(array('is_permium'=>1),__CLASS__,'User',User::TABLE,'id');
 	}
}


Далее можно сделать, например, такое:

foreach(UsersGroup::getPremiumMembers()->orderBy('registration_date')->limit(10) as $user){
	/**<hh user=var> User $user*/
	echo $user->getCountry()->getCurrency()->getCode()."<br />";
}


Это лишь основные возможности и принципы.

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

Почему не сразу в статью — нужна ещё привязка к какой-либо популярной прослойке работы с БД (например, pdo), немного заполировать, выбрать название и чтобы это сделать — прежде всего нужно знать — найдутся ли те, кому это нужно, либо всем достаточно существующих решений, например — Doctrine 2.

Всё работает с PHP5.2+
  • Вопрос задан
  • 2710 просмотров
Пригласить эксперта
Ответы на вопрос 5
@egorinsk
Сам по себе ORM — банальная ничем не примечательная хрень. Это уже много раз делали в других фреймворках (например, RoR, Java) и описано в книгах про паттерны. Берешь, делаешь как в Руби и пользуешься хоть до посинения.

Пример с User::create() неудачный: у реальных объектов бывает по 20 свойств и фукнция с 20 аргументами будет выглядеть дико. Функции с подчеркиванием в начале — уродливые. Передавать __CLASS__ и подобные магические методы тоже не очень как-то.

Один из сложных моментов в проектировании ORM — оптимальная организация взаимодействия с хранилищем. Например, этот ваш пример:

> foreach(UsersGroup::getPremiumMembers()->orderBy('registration_date')->limit(10) as $user){
> echo $user->getCountry()->getCurrency()->getCode()."
";

Сколько запросов сгенерирует при использовании SQL-хранилища? По идее, должно быть в районе 3-4, причем данные справочников еще бы и стоило кешировать (ибо валюты у стран меняются очень редко) и обойтись 1-2 запросами. Если у вас в цикле для каждого юзера делается запрос — хлам это, а не ORM.

Второй момент — оверхед. Вы когда-нибудь считали, какая разница по времени выполнения запроса через ваш ORM и через mysqli_query() (включая время на загрузку и инициализацию классов ORM)? Посчитайте, наверняка у вас после этого вообще пропадет желание использовать ORMы.

Третий момент — масштабирование. Можно ли, к примеру, сделав огромный сайт на вашем ORM, не переписывая кода, реализовать расшардивание базы на 100 серверов (чтобы справиться с нагрузкой). Можно ли на нем делать проекты уровня хотя бы игр для соцсетей или вконтакта?

Если у вас есть решение хотя бы некоторых из описанных 3 проблем проектирования ORM, ваша статья на тему архитектурных решений и программистских хитростей была бы крайне интересна. Если нет решения — то такой орм любой школьник может сделать, как я уже сказал, прочтя мануал к рубионрейлс.
Ответ написан
silentnuke
@silentnuke
Пишите, лишним точно не будет.
Ответ написан
Комментировать
EugeneOZ
@EugeneOZ
Я буду критиковать за говнокод :) Статические методы — это не ООП, это просто процедуры, не более. С глобальной видимостью, зависимостями.
Ну и очень, очень хреново, что у вас сущности должны от чего-то экстендится. Между наследованием и аггрегацией выбирайте аггрегацию.
Я такую хрень пару лет назад писал, сейчас вспоминать стыдно :)
Ответ написан
Вы, конечно, можете написать. Но, насколько я понял, Ваша основная мысль (потому что она выделена жирным):
> По сути — один рядок в таблице соответствует одному объекту.

А я для себя выделил вот эту мысль:
> И… без оглядки на какие-либо паттерны.

Если бы всё же оглянулись, то узнали бы что именно Вы изобрели.
Ответ написан
taliban
@taliban
php программист
У вас activerecord это простейшее взаимодействие с бд, причем это самое слабо функциональное взаимодействие, это есть в любом фреймверке, в любом фреймверке есть доп. функционал который учитывает связи итд. Как по мне оно того не стоит. Причем фцнкционал, скажу честно, удручает =)
Просто для примера другой кусок кода:
class User extends ActiveRecord{
    protected $_table = 'users';
}

$user = User::find( array('id'=>3) );
$user->name = 'new name';
$user->set('last_name', 'new last name')->set('email', 'new@email')->save();

Банально мне ничего не приходится писать в коде класса, за меня все делает родитель, сам находит поля, сам находит ключи, сам сохраняет, сам знает по какому ключу сохранить, сам знает какие поля сохранить.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы