<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>webbricks &#187; mvc</title>
	<atom:link href="http://blog.grzegorzpawlik.com/tag/mvc/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.grzegorzpawlik.com</link>
	<description>Doświadczenie, to coś, co zdobywamy tuż po chwili w której było nam potrzebne ...</description>
	<lastBuildDate>Tue, 07 Feb 2012 10:09:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Lazy Loading in Model layer (cakePHP style)</title>
		<link>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-in-model-layer-cakephp-style/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-in-model-layer-cakephp-style/#comments</comments>
		<pubDate>Mon, 25 Oct 2010 13:54:24 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[Inne]]></category>
		<category><![CDATA[design patterns]]></category>
		<category><![CDATA[LazyLoading]]></category>
		<category><![CDATA[model]]></category>
		<category><![CDATA[mvc]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1402</guid>
		<description><![CDATA[CakePHP is not supporting Lazy Loading yet. Before if starts, I asked myself &#8220;how would I do that?&#8221;. My solution is based on PHP5, so I imagine that supporting PHP4 in 1.x branch is the reason of no-Lazy-Loading. Anyway &#8211; &#8230; <a href="http://blog.grzegorzpawlik.com/2010/10/lazy-loading-in-model-layer-cakephp-style/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>CakePHP is not supporting Lazy Loading yet. Before if starts, I asked myself &#8220;how would I do that?&#8221;. My solution is based on PHP5, so I imagine that supporting PHP4 in 1.x branch is the reason of no-Lazy-Loading. Anyway &#8211; I wanted to share my ideas.</p>
<p>Let&#8217;s start with basics. The opposite of lazy loading is eager loading, which means &#8220;Load anything that can be needed. Ever.&#8221; In that approach model layer in Cake could look like that (it&#8217;s very simplified, the only relationship is belongsTo):</p>
<h4>Eager Loading</h4>
<pre name="code" class="php">

class Model {
	function find(){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."`";

		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."<br/>");
		if(isset($this->belongsTo) &#038;&#038; is_array($this->belongsTo)){
			foreach($this->belongsTo as $related){
					$this->{$related} = new $related();
			}
		}
	}
}

class User extends Model{
	protected $belongsTo = array("Group");
}

class Group extends Model{
}

$user =  new User();

$user->find();
echo "<br/>";
$user->Group->find();
</pre>
<p>One could see that Group object was initialized long before it is used:</p>
<pre>
creating User
creating Group
calling User::find()

string 'Select * from `users` as `User`' (length=31)

calling Group::find()

string 'Select * from `groups` as `Group`' (length=33)
</pre>
<p>Even more &#8211; it&#8217;s being initialized even if we don&#8217;t call any method in this object (comment $user->Group->find(); to see that).</p>
<h4>Lazy Loading</h4>
<pre name="code" class="php">
class Model {
	function find(){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."`";

		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."<br/>");
	}

	function __get($property){
		if(!isset($this->{$property})) {
			$this->{$property} = new $property();
		}
		if(in_array($property, $this->belongsTo)){
			return $this->{$property};
		}else{
			$r = new ReflectionClass($this);
			throw new Exception(
				"$property not related with {$r->getName()}"
			);
		}
	}
}

class User extends Model{
	protected $belongsTo = array("Group");
}

class Group extends Model{
}

$user =  new User();

$user->find();
echo "<br/>";
$user->Group->find();
</pre>
<p>When using Lazy Loading pattern, Group object isn&#8217;t created untill we need it. Magic method __get() is used to make that possible. __get() is called whenever we try to access an attribute in object that doesn&#8217;t exists.</p>
<p>But there&#8217;s question of searching in model, so it returns appropriate data, ie. var_dump($user->find());</p>
<pre>
array
  0 =>
    array
      'User' =>
        array
          'id' => int 1
          'name' => string 'Frank' (length=6)
      'Group' =>
        array
          'id' => int 1
          'name' => string 'Admin' (length=5)
  1 =>
    array
      'User' =>
        array
          'id' => int 2
          'name' => string 'John' (length=5)
      'Group' =>
        array
          'id' => int 10
          'name' => string 'User' (length=4)
</pre>
<p>We know that belongsTo means that User should have field qroup_id, so the query could look like this:</p>
<pre name="code" class="sql">
Select User.id, User.name, Group.id, Group.name from users as User
Inner Join groups as Group on (User.group_id = Group.id)
</pre>
<p>(Now CakePHP, since 1.2 AFAIR, has Containable behavior, but it depends on loaded models).</p>
<p>Lets keep cake&#8217;s convention and assume, that we&#8217;ll pass informations about related elements through $contain variable.</p>
<p>function find($contain=null)</p>
<p>Now I want to say: object $user, please return me all users from database and groups they&#8217;re in:</p>
<pre name="code" class="php">
$user =  new User();

$user->find(
	array(
		"belongsTo" => array($user->Group)
	)
);
</pre>
<p>Lets update find() method:</p>
<pre name="code" class="php">
	function find($contain=null){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."` \n";

		if(!empty($contain) &#038;&#038; is_array($contain["belongsTo"])){
			foreach($contain["belongsTo"] as $belongsToModel){
				$query .= $belongsToModel->getBelongsToJoin($this);
			}
		}

		var_dump($query);

		//return query result
	}
</pre>
<p>which now assumes that model has method, which can create the join part of query:</p>
<pre name="code" class="php">
	function getBelongsToJoin($obj){
		$owner_reflection = new ReflectionObject($this);
		$obj_reflection = new ReflectionObject($obj);
		return "INNER JOIN `".strtolower($owner_reflection->getName())."s`
					as `".$owner_reflection->getName()."`
						ON (`".$owner_reflection->getName()."`.`id` = `".$obj_reflection->getName()."`.`".strtolower($owner_reflection->getName())."_id`)
					";
	}
</pre>
<p>it is ugly because we keep using reflections on and on. To make that prettier we could implements getName() method in Model class.</p>
<p>Anyway. Even in case of find which considers relations, we can use Lazy Loading pattern.</p>
<p>As a bonus, code sample with more related models and example of use (and small refactorin &#8211; when I write about Lazy Loading it looked silly when I was loading ReflectionClasss fifteen times ;)):</p>
<pre name="code" class="php">

class ModelException extends Exception{}

class Model {

	protected $reflection = null;

	function getName(){
		if(is_null($this->reflection)){
			$this->reflection= new ReflectionClass($this);
		}
		return $this->reflection->getName();
	}

	function getTableName(){
		return strtolower( $this->getName() . "s" );
	}

	function getFKName(){
		return strtolower( $this->getName() . "_id");
	}

	function getBelongsToJoin($obj){
		$owner_reflection = new ReflectionObject($this);
		$obj_reflection = new ReflectionObject($obj);
		return "INNER JOIN `".$this->getTableName()."`
					as `".$this->getName()."`
						ON (`".$this->getName()."`.`id` = `".$obj->getName()."`.`".$this->getFKName()."`)
					";
	}

	function find($contain=null){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "\nSelect * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."` \n";

		if(!empty($contain) &#038;&#038; is_array($contain["belongsTo"])){
			foreach($contain["belongsTo"] as $belongsToModel){
				$query .= $belongsToModel->getBelongsToJoin($this);
			}
		}

		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."<br/>");
	}

	function __get($property){
		if(!isset($this->{$property})) {
			$this->{$property} = new $property();
		}
		if(in_array($property, $this->belongsTo)){
			return $this->{$property};
		}else{
			$r = new ReflectionClass($this);
			throw new ModelException(
				"$property not related with {$r->getName()}"
			);
		}
	}
}

class User extends Model{
	protected $belongsTo = array("Group", "Unit", "Company");
}

class Group extends Model{
}

class Unit extends Model{}
class Company extends Model{}

$user =  new User();

$user->find(
	array(
		"belongsTo" => array($user->Unit, $user->Company)
	)
);
</pre>
<p>What You think about that?</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-in-model-layer-cakephp-style/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Dobre praktyki programowania w CakePHP #4</title>
		<link>http://blog.grzegorzpawlik.com/2009/10/dobre-praktyki-programowania-w-cakephp-4/</link>
		<comments>http://blog.grzegorzpawlik.com/2009/10/dobre-praktyki-programowania-w-cakephp-4/#comments</comments>
		<pubDate>Mon, 12 Oct 2009 20:12:34 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[beforeFilter]]></category>
		<category><![CDATA[DRY]]></category>
		<category><![CDATA[mvc]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=365</guid>
		<description><![CDATA[O podobnych kwestiach pisałem już przy okazji wpisu #2 z tej serii. Ale warto jeszcze raz przypomnieć o tym, że tak jak zasady projektowania obiektowego tak należy dokładnie zrozumieć co oznacza podział aplikacji na warstwy MVC. W tym wpisie skupię &#8230; <a href="http://blog.grzegorzpawlik.com/2009/10/dobre-praktyki-programowania-w-cakephp-4/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>O podobnych kwestiach pisałem już przy okazji wpisu <a href="/?p=53">#2 z tej serii</a>. Ale warto jeszcze raz przypomnieć o tym, że tak jak <a href="/?p=279">zasady projektowania obiektowego</a> tak należy dokładnie zrozumieć co oznacza podział aplikacji na warstwy MVC.</p>
<p>W tym wpisie skupię się na <strong>warstwie modelu</strong>.<br />
Jest ona czasem nazywana warstwą biznesową aplikacji- i nie jest to przypadek. Dzieje się tak dlatego, że w niej zawarte są (albo powinny być) reguły biznesowe, które dany system realizuje. Przejdę jak najszybciej do przykładu, który to obrazuje.</p>
<p>Wyobraź sobie system zarządzania firmą wypożyczającą samochody. Cake wypiekł Ci CRUD dla modelu Cars i Costs (koszty generowane przez te samochody). Teraz klient informuje Cię o pewnej zasadzie, która brzmi:<br />
&#8220;Każdy koszt można powiązać z dowolną ilością samochodów. Koszt może nie być powiązany z żadnym samochodem (koszt ogólny). Dane powiązanie koszt-samochód jest opatrzone konkretną kwotą (kwota powiązania). Jeśli koszt jest powiązany z jednym lub więcej samochodami, to suma kwoty ich powiązań musi się równać wartości kosztu. Czyli koszt musi być całkowicie rozdzielony na samochody, lub wcale.<br />
Na przykładzie: mogę dodać koszt &#8216;naprawa po wypadku&#8217;, który powiążę z jednym samochodem na pełną kwotę. Mogę dodać koszt &#8216;ubezpieczenie za rok 2008&#8242;, które rozdzielę po równo między wszystkimi samochodami i mogę dodać &#8216;spinacze do biura&#8217;, którego nie powiąże z żadnym samochodem&#8221;.<br />
To co właśnie usłyszałeś to reguła biznesowa, którą Twoja aplikacja musi obsłużyć.</p>
<p>Od razu oczywiście zabierasz się ochoczo za zdefiniowanie relacji wiele do wielu (HABTM) w modelach Cost i Car. Jeśli uważasz, że to wszystko i modele już będą jak zwykle służyć do wykonywania selectów na bazie- to sygnał, że możesz jeszcze nie rozumieć idei MVC, więc czytaj dalej.</p>
<p>Zabierasz się za implementowanie funkcjonalności. Bez większego zagłębiania się w szczegóły zbudujesz widok, w którym wybierzesz dla danego kosztu ileś samochodów z listy, wypełnisz ich kwoty i po kliknięciu zapisz poleci wszystko do kontrolera, a w nim będziesz miał 4 (o cztery za dużo) foreachów, którymi będziesz sprawdzał, czy suma się zgadza, a jeśli tak &#8211; pozwolisz na zapisz, bla, bla, bla. Jesteś zadowolony.</p>
<p>Jednak za miesiąc przychodzi klient i mówi: &#8220;fajne jest to przypisywanie kosztów do samochodów, ale ja chciałbym, żeby móc do kosztu przypisać samochody tylko na część kwoty&#8221;.<br />
Jako porządny podwykonawca zgadzasz się, dajesz nura w kontroler i rozdmuchujesz go o pięć nowych ifów i trzy foreache, żeby obsłużyć wszystkie niuanse, które właśnie się pojawiły.</p>
<p>Ale licho&#8230; ekhm, klient nie śpi: &#8220;Skoro można przypisywać samochody do kosztów nie w całości &#8211; to chciałbym móc w widoku samochodu możliwość wybrania kosztów i je do niego przypisać&#8221;.<br />
I co teraz robisz?</p>
<p>Jeśli zabierasz się za przeniesienie mechanizmów z kontrolera Costs do modelu Cost &#8211; może nie pójdziesz jeszcze do piekła. Jeśli zapaliła Ci się czerwona lampka, ale nie wiesz o co chodzi &#8211; czytaj dalej. Jeśli zaczynasz kopiować wspomniane mechanizmy do kontrolera Cars &#8211; jesteś w ciemnej du&#8230; hmm piwnicy. Dlaczego?</p>
<p>To, że łamiesz zasadę MVC to już powinieneś czuć przez skórę. Możesz jeszcze nie wiedzieć czym to grozi. To, że łamiesz zasady projektowania obiektowego &#8211; mogłeś nie zauważyć. Ale, że masz w dwóch różnych miejscach (CostsController i CarsController) mechanizm robiący to samo i Ci to nie przeszkadza &#8211; za to właśnie trafisz do piekła.</p>
<p>Powtórzenie, które przed chwilą opisałem ma dwie poważne konsekwencje:<br />
1. Mechanizmy dbające o spójność bazy danych masz rozproszone w wielu klasach. Dlatego zmiana reguły biznesowej pociąga za sobą konieczność zmiany wielu dublujących się wierszy kodu. Łatwo wtedy o pomyłkę.</p>
<p>2. Model nie dba samodzielnie o spójność danych. Pisząc w ten sposób aplikację zakładasz, że każdy programista w każdym momencie rozwoju aplikacji (czyli nawet za dwa lata) będzie dokładnie pamiętał tą (i wszystkie inne) reguły biznesowe. Dodając kolejny element, znów będzie musiał samodzielnie zadbać o każdy aspekt spójności danych. Łatwo wtedy o pomyłkę.</p>
<p>Dlatego o spójność danych <strong>musi dbać</strong> warstwa modelu. Bo ona dba o sama siebie. Jeśli odpowiednio zaprojektujesz model Cost, przy pomocy callback&#8217;ów beforeValidate, before i afterSave zawrzesz w modelu zapewnienie spójności, wszystko co pozostanie w przyszłości do zrobienia to przygotowanie odpowiedniej tablicy w kontrolerze i wywołanie $this-&gt;Cost-&gt;save(&#8230;).</p>
<p>Dlatego czwartą zasadę formułuję następująco:<br />
<strong><span style="font-size: large;">mechanizmy dbające o spójność bazy danych umieszczaj w warstwie modelu</span></strong></p>
<p>ps. Witam wszystkich po przydługiej przerwie wakacyjno-inietylko ;)</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2009/10/dobre-praktyki-programowania-w-cakephp-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

