Какие запросы мешают вам использовать ORM?

whirlwind

TDD infected, paranoid
если заранее знаешь всю либу вдоль и поперек - наверно да, правок меньше
Нет, ты объектами и декларируешь схему. Все должно делаться на одном языке - человек работает только с native PHP кодом. По причине не соблюдения этого требования, в частности, меня Propel не устроил.

-~{}~ 04.10.06 17:16:

2StUV вот конкретно тебе пример элемента модели. Что здесь может быть не понятного? Мне, что бы сменить логику, не нужно лазить по куче файлов и искать - а не влияет ли вот этот скрипт на конкретно таблицу CategoryType?

PHP:
wcmfInclude("orm/Persistent.php");

class CategoryType extends Persistent {
	
	protected function _registerAtoms(){
		parent::_registerAtoms();
		$this->_setAtom("cid",		new Atom_String(4,2),true);
		$this->_setAtom("name",		new Atom_String(32,3));
		$this->_setAtom("fulldesc",	new Atom_String(128));
	}

	function asString(){
		return $this->isSelected() ? $this->get("name") : parent::asString();
	}

}
 

StUV

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

к тому же то что было известно в этой области в конце 90-х совсем не то, что сейчас и лет через 5 тот кто будет работать с вашим кодом будет так же материться как я когда-то =)

---------------

а вообще - чистый тупой флейм начался - уже из религиозной сферы...

для меня главное было высказать свое "твердое нет 2 ORM"
что я уже неоднократно сделал - поэтому из топика удаляюсь

удачи в стандартизации orm! ;)
 

whirlwind

TDD infected, paranoid
лет через 5 тот кто будет работать с вашим кодом будет так же материться как я когда-то =)
Я всячески стараюсь этого избежать :)

Кста, ты мну кажись не понял. Какой дебаг? Если об
кидать перед компиляцией Event
то я имел в виду сборку SQL-запроса
 

bkonst

.. хочется странного?...
а что теперь отсутствие документации - признак кривости кода ?
Да. И раньше так было.

если заранее знаешь всю либу вдоль и поперек - наверно да, правок меньше

а если - вот тебе доки, вот старые схема/код, вот новые требования..... - сразу все меняется местами
+ 10 минут на прочтение раздела "Описание модели -> Связи между сущностями"
- X минут на поиск ошибки, вызванной случайной опечаткой в одном из N исправленных запросов
 

StUV

Rotaredom
ты объектами и декларируешь схему.
да, интуитивно этот последний код понятен
но для реализации всей схемы потребуется изучить либу полностью... но при этом сейчас мало прогеров которые не знают sql на уровне "создать схему/написать запрос/сделать оптимизацию"
 

Raziel[SD]

untitled00
Автор оригинала: bkonst
Да откуда ж я знаю? Я, что ли это должен доказывать? Покажите, что в любых условиях TCO будет больше. Или используйте "некоторых" вместо "всех".

...Чуть ли не в каждом втором посте заявляю, что ORM имеет собственную ограниченную нишу. И наоборот, чуть ли не в каждом втором посте вижу что кто-то "имел опыт работы с криво написанным/использованным/недокументированным ORM, поэтому сама идея ORM - дерьмо".
1. Занимался разработкой типовых и не типовых проектов - времени уходило больше, чем plain-sql. Аналогичная картина наблюдалась и у коллег. С поддержкой тоже самое.
2. Что интересно, у всех криво написаная ORM, а вы к сожалению, не предоставили нам свою идеальную ORM.
3. Разве кто-то сказал, что сама идея дерьмо ? идея как раз хорошая.

З.Ы. Если тебе интересно расчитать ТСО - посчитай, а мне это не интересно.
 

whirlwind

TDD infected, paranoid
но для реализации всей схемы потребуется изучить либу полностью... но при этом сейчас мало прогеров которые не знают sql на уровне "создать схему/написать запрос/сделать оптимизацию"
Я тебя могу обрадовать. Не нужно изучать для этого либу. Вот смотри
PHP:
class Product extends Persistent {

	protected function _registerAtoms(){
		parent::_registerAtoms();
		$this->_setAtom("name",			new Atom_String(128,3));
		$this->_setAtom("shortname",	new Atom_String(32,3));	// for menues
		$this->_setAtom("type",			new Atom_Reference("Reference.ProductType"));
		$this->_setAtom("shortdesc",	new Atom_Text(5120));	// 5kb
		$this->_setAtom("fulldesc",		new Atom_Text(61440));	// 60kb
		$this->_setAtom("fbundle",		new Atom_Boolean());
		$this->_setAtom("fhidden",		new Atom_Boolean());
		$this->_setAtom("factive",		new Atom_Boolean());
		$this->_setAtom("price",		new Atom_Numeric());
		$this->_setAtom("img_height",   new Atom_Integer());
		$this->_setAtom("img_width",    new Atom_Integer());
		$this->_setAtom("img_file",     new Atom_String(64,3));
		$this->_setAtom("tumb_height",  new Atom_Integer());
		$this->_setAtom("tumb_width",   new Atom_Integer());
        $this->_setAtom("tumb_file",    new Atom_String(64,3));
	}

    function getNewIdentifier(){
		return Image::getNewIdentifier();
    }

    function getImagesDir(){
		return Image::getImagesDir();
    }

	function asString(){
		return $this->isSelected() ? $this->get("name") : parent::asString();
	}

	protected function _onMake(){
		$this->set("factive",true);
		$this->set("type",Glob::getDefaultProductType());
		return parent::_onMake();
	}
    
    protected function _onDelete(){
        if ( !parent::_onDelete() ) return false;
        $this->dropImages();
        return true;
    }

	function dropImage(){
        if ( ($imagesDir = $this->getImagesDir()) === null ) return false;
        $imagePath = $imagesDir."/".$this->get("img_file");
        if ( $this->get("img_file") && file_exists($imagePath) )
            unlink($imagePath);
        $this->set("img_file","");
		$this->set("img_width",0);
		$this->set("img_height",0);
		return true;
	}

    function dropTumb(){
		if ( ($imagesDir = $this->getImagesDir()) === null ) return false;
        $tumbPath = $imagesDir."/".$this->get("tumb_file");
        if ( $this->get("tumb_file") && file_exists($tumbPath) )
            unlink($tumbPath);
        $this->set("tumb_file","");
		$this->set("tumb_width",0);
		$this->set("tumb_height",0);
        return true;
    }
	
	function dropImages(){
		return $this->dropImage() && $this->dropTumb();
	}
    
    function setImage(Image $image){
		if ( ($imagesDir = $this->getImagesDir()) === null ) return false;
        if ( !$this->get("img_file") ){
            $fileId = $this->getNewIdentifier();
            $this->set("img_file",$fileId.".".$image->getFileExtension());
        }
		$this->set("img_width",$image->getWidth());
		$this->set("img_height",$image->getHeight());
        return $image->save($imagesDir."/".$this->get("img_file"));
    }
	
	function setTumb(Image $image){
        if ( ($imagesDir = $this->getImagesDir()) === null ) return false;
		if ( !$this->get("tumb_file") ){
			$fileId = $this->getNewIdentifier();
			$this->set("tumb_file",$fileId.".".$image->getFileExtension());
		}
		$this->set("tumb_width",$image->getWidth());
		$this->set("tumb_height",$image->getHeight());
		return $image->save($imagesDir."/".$this->get("tumb_file"));
	}

}
Разве неясно из декларации модели что здесь есть связь с классом, приведенном в предыдущем посте? Для этого тебе не нужно залазить в консоль или еще куда то что бы посмотреть на структуру таблицы. Ты видешь это там, же где пишешь код.

А N:N объявляется вообще до смешного просто
PHP:
class ProductCategory extends Persistent_Mapping {

	public function ProductCategory(){
		$this->Persistent_Mapping("Product","Category");
	}

	protected function _registerAtoms(){
		parent::_registerAtoms();
		$this->_setAtom("sortorder",new Atom_Integer());
	}
	
	protected function _onInsert(){
		$sql = new ORM2SQL_Query();
		$rs = $this->getConnection()->executeQuery(
			sprintf("SELECT MAX(%s) as num FROM %s WHERE %s=%d",
			$sql->shf($this,"sortorder"),
			$sql->from($this),
			$sql->shf($this,"slave"),$this->getSafe("slave"))
		);
		
		if ( $rs->getRecordCount() ){
			$rs->next();
			$this->set("sortorder",$rs->get("num")+1);
		}else
			$this->set("sortorder",0);
		return parent::_onInsert();
	}

}
Вот кстати, здесь пример того, что от Plain-SQLя не застрахован никакой ORM. Однако ж наздоровье - пользуйтесь, там где ORM не в силах. За то весь код там, где конкретный элемент схемы, для которого он разрабатывался.

-~{}~ 04.10.06 17:29:

Raziel[SD] Товарисчь, разуйте глаза и посмотрите в топике, она далеко не идеальная, но я работаю над этим.
 

bkonst

.. хочется странного?...
но для реализации всей схемы потребуется изучить либу полностью...
Для реализации схемы потребуется изучить, как определить связи и как определить поля. Учитывая, что есть 2 типа связей и штук 7 часто используемых типов полей - объем описания составит странички две.
 

bkonst

.. хочется странного?...
Raziel[SD]
1. Занимался разработкой типовых и не типовых проектов - времени уходило больше, чем plain-sql. Аналогичная картина наблюдалась и у коллег. С поддержкой тоже самое.
"У меня" == "существуют". Не "все"

2. Что интересно, у всех криво написаная ORM, а вы к сожалению, не предоставили нам свою идеальную ORM.
Как уже сказали выше - разуй глаза. Ищем, работаем, думаем, поднимаем топики, опять же.

Разве кто-то сказал, что он не осилил ? Сказано было что поддержка таких проектов сложнее т.е. дороже.
[...]
3. Разве кто-то сказал, что сама идея дерьмо ? идея как раз хорошая.
Т.е. идея хорошая но обходится дороже?

З.Ы. Если тебе интересно расчитать ТСО - посчитай, а мне это не интересно.
Тю, ё-мое. Как громкие заявления делать - так интересно, а как TCO считать - так неинтересно.
 

Raziel[SD]

untitled00
bkonst я думаю этот разговор имеет смысл продолжить года через два, когда поддержка старых проектов станет актуальной :)
дабы не разжигать дальше холи вар:
Разумеется, проекты и спользованием ORM разрабатывать и поддерживать дешевле. Я был не прав :) ой, забыл добавить, и ORM у меня была косячная.

Да, идея хорошая, красивая, сам потратил на нее около двух лет.
 

whirlwind

TDD infected, paranoid
Автор оригинала: fisher

есть туристические маршруты(entity#1), по разным странам(entity#2). маршруты принадлежат разным компаниям(entity#3). каждый маршрут состоит из "точек" ((entity#4, m:m), проходит через разные города(entity#5), и где-то просто посещение достопримечательности(entity#6), где-то есть обед, а где-то ужин + ночевка. мне нужно

1) найти все компании, которые возят народ с ночевкой в барселоне (у кого-то там отель и он хочет продавать места в отеле оптом именно этим компаниям)

2) найти в каждой стране по 5 наиболее популярных достопримечательностей, через которые проходит больше всего маршрутов.
Выпало свободных пару часов, вспомнил о старом споре

Модель + фикстура + тесты на задания здесь http://prolib.ru/.closed/tourism.zip
Фреймворк здесь svn://svn.prolib.ru/wcmf/trunk
Еще понадобится Creole здесь http://creole.phpdb.org/trac/

Кому лень лазить запускать, приведу код тестов

Это первая задача
PHP:
require(dirname(__FILE__)."/../init.php");
require_once("orm/BulkLoader.php");

$wantedCompanies = Array("Terminator","Love in a cottage");

$map = MetaData::createObject("Map.RouteLocation");
$location = MetaData::createObject("Reference.Location");
$route = MetaData::createObject("Reference.Route");
$company = MetaData::createObject("Reference.Company");

// setup filters

$map->filterBy("f_night_quart",true);
$location->filterBy("name","Barselona");

$loader = new BulkLoader($map);
$loader->attachEssence($location);
$loader->attachEssence($route);
$loader->attachEssence($company);
$loader->fetch();

// check results

if ( $loader->fetchCount() != sizeof($wantedCompanies) )
	throw new Exception("Fail");
while ( $loader->next() ){
	$company = $loader->get("Company");
	if ( !in_array($company->get("name"),$wantedCompanies) )
		throw new Exception("Fail");
}
echo "Test passed\n";
===========================================================
Это вторая задача

PHP:
require(dirname(__FILE__)."/../init.php");
require_once("orm/BulkLoader.php");

$limitPerCountry = 2;
$wantedResults = Array(
	Array("2","Dream-land","New-York"),
	Array("1","Dream-land","Chicago"),
	Array("3","Land of darkness","Barselona"),
	Array("2","Land of darkness","Rome"),
	Array("1","Land of nod","Spi-Esh-Pey"),
);

$map = MetaData::createObject("Map.RouteLocation");
$location = MetaData::createObject("Reference.Location");
$country = MetaData::createObject("Reference.Country");

$loader = new BulkLoader();
$loader->setAutoGroup();
$loader->attachEssence($map);
$loader->attachEssence($location);
$loader->attachEssence($country);
$loader->getIncludeList()->set("RouteLocation","slave");
$loader->sort("name","Country");
$loader->sortD("cnt");
$loader->sort("name","Location");

$query = $loader->getQuery();
$query->expr()->addChunk(new SqlExpr("COUNT(*)","cnt"));
$loader->fetch();

$result = Array();
$counter = 0;
while ( $loader->next() ){
	$result[] = Array(
		$loader->get("cnt"),
		$loader->get("Country")->get("name"),
		$loader->get("Location")->get("name"),
	);
	$counter ++;
	if ( $counter >= $limitPerCountry ){
		$countryId = $loader->get("Country")->getId();
		while ( $loader->next() ){
			if ( $loader->get("Country")->getId() != $countryId ){
				$loader->getResultSet()->previous();
				break;
			}
		}
		$counter = 0;
	}
}

// check results
if ( $result !== $wantedResults )
	throw new Exception("Fail");

echo "Test passed\n";
С лимитированием заморачиваться не стал. Хотелось бы посмотреть варианты запроса native SQL.


PS. со звездочкой делать было лень
 

zerkms

TDD infected
Команда форума
whirlwind
честно пытался пробовать понять твой код что здесь, что на агиле. не получается ;)
 

whirlwind

TDD infected, paranoid
zerkms что конкретно не понятно? Вот это?


PHP:
$map->filterBy("f_night_quart",true); 
$location->filterBy("name","Barselona"); 

$loader = new BulkLoader($map); 
$loader->attachEssence($location); 
$loader->attachEssence($route); 
$loader->attachEssence($company); 
$loader->fetch();
Что тут может быть непонятного? Непонятно почему так? смотрим декларацию модели - там всего 5 классов смысл и связи между которыми более чем прозрачны.

ЗЫ. Попытаюсь объяснить на пальцах:

$map - это объект, связывающий 1 Route -> N Locations
$location - нам нужно для того, что бы отфильтровать Барселону по имени. Если бы нам был известен PK Барселоны, мы могли бы отфильтровать непосредственно по $map вот так

PHP:
$map->filterBy("slave",$barselonaPk);
т.к. название Барселоны нам не нужно, можно было бы не включать сущность location в выборку.
Вот так бы выглядел запрос в этом случае
PHP:
$map->filterBy("f_night_quart",true); 
$map->addFilterBy("slave",$barselonaPk);

$loader = new BulkLoader($map); 
$loader->attachEssence($route); 
$loader->attachEssence($company); 
$loader->fetch();
$route - это декларация маршрута. каждый маршрут согласно условиям задачи относится к какой либо компании. Нам нужна компания, а $route в данном случае служит клеем между location, через которые этот route пароходит и компанией. Что бы развернуть эти связи мы добавляем route, а затем company. В итоге получили цепочку связей (фактически лефтджойнов)

Location <- RouteLocation -> Route -> Company

В результате получаем на каждой итерации цикла $loader->next()

объект Location, объект маршрута Route с которым связана это Location и компанию к котрой принадлежит маршрут.
 

Wicked

Новичок
whirlwind
А как насчет модификации данных, а-ля "увеличить всем сотрудникам зарплату в 1.1 раза?" :)
 

whirlwind

TDD infected, paranoid
Началось в колхозе утро :)

Пишите ТЗ давайте обсуждать цену, а то я запарился есличестно халявными примерами сыпать.

Если вкратце, что все зависит от схемы. В самом простом варианте - Mass(Update|Delete) выглядит как перехват запроса и удаления WHERE id=?. Т.к. запрос структурированный и для простых случаев его структура очень даже прозрачна, все сводится к сбросу условий

PHP:
$query->cond(); // [b]всем сотрудникам[/b]
 
Сверху