<?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; CakePHP</title>
	<atom:link href="http://blog.grzegorzpawlik.com/category/cakephp/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>Co nowego w cake 2.0?</title>
		<link>http://blog.grzegorzpawlik.com/2011/09/co-nowego-w-cake-2-0/</link>
		<comments>http://blog.grzegorzpawlik.com/2011/09/co-nowego-w-cake-2-0/#comments</comments>
		<pubDate>Tue, 06 Sep 2011 18:28:17 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1633</guid>
		<description><![CDATA[Nowy, lepszy, szybszy model ju w drodze&#8230; znalazłem ciekawy artykuł z krótkim podsumowaniem ,który zdecydowanie polecam http://nuts-and-bolts-of-cakephp.com/2011/08/14/under-the-hood-of-cakephp-2-0/]]></description>
			<content:encoded><![CDATA[<p>Nowy, lepszy, szybszy model ju w drodze&#8230; znalazłem ciekawy artykuł z krótkim podsumowaniem ,który zdecydowanie polecam<br />
<a href="http://nuts-and-bolts-of-cakephp.com/2011/08/14/under-the-hood-of-cakephp-2-0/" title="Under the hood of CakePHP 2.0">http://nuts-and-bolts-of-cakephp.com/2011/08/14/under-the-hood-of-cakephp-2-0/</a></p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2011/09/co-nowego-w-cake-2-0/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Float vs. setlocale()</title>
		<link>http://blog.grzegorzpawlik.com/2010/12/float-vs-setlocale/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/12/float-vs-setlocale/#comments</comments>
		<pubDate>Thu, 02 Dec 2010 15:30:05 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[Inne]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[setlocale]]></category>
		<category><![CDATA[type-casting]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1439</guid>
		<description><![CDATA[Mark Story wpadł na ciekawy problem, który opisał na swoim blogu. W dużym skrócie, jeśli używasz setlocale() to rzutowanie typów danych może działać nieprzewidywalnie. Polecam wpis Marka. Jednak grzebiąc nieco po php.net okazało się, że jest jeden sposób na zmianę &#8230; <a href="http://blog.grzegorzpawlik.com/2010/12/float-vs-setlocale/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Mark Story wpadł na ciekawy problem, który <a href="http://mark-story.com/posts/view/php-floats-localization-and-landmines">opisał na swoim blogu</a>.</p>
<p>W dużym skrócie, jeśli używasz setlocale() to rzutowanie typów danych może działać nieprzewidywalnie. Polecam wpis Marka. Jednak grzebiąc nieco po php.net okazało się, że jest jeden sposób na zmianę typu, która bierze pod uwagę locale:</p>
<pre name="code" class="php">
$pi = 3.141593;
echo (float)(string)$pi ."\n";
var_dump(setlocale(LC_NUMERIC, 'pl_PL.utf8'));
$polishPi = 3.141593;
// poniżej "problematyczne" rzutowanie
echo "string: " . (string)$polishPi ."\n";
echo "float: " . (float)(string)$polishPi ."\n";
echo "floatval(): " . floatval((string)$polishPi) ."\n";
echo "double: " . (double)(string)$polishPi ."\n";
echo "real: " . (real)(string)$polishPi ."\n";

//settype wydaje się działać dobrze z setlocale
var_dump(settype($polishPi, "double")); // można użyć "float"
echo "settype(): " . $polishPi ."\n";
var_dump(gettype($polishPi));
var_dump($polishPi*2);
var_dump(gettype($polishPi*2));
</pre>
<p>out:</p>
<pre>3.141593
<pre class="xdebug-var-dump" dir="ltr"><small>string</small> <font color="#cc0000">'pl_PL.utf8'</font> <i>(length=10)</i>
</pre>
<p>string:		3,141593<br />
float:		3<br />
floatval():	3<br />
double:		3<br />
real:		3</p>
<pre class="xdebug-var-dump" dir="ltr"><small>boolean</small> <font color="#75507b">true</font>
</pre>
<p>settype():	3,141593</p>
<pre class="xdebug-var-dump" dir="ltr"><small>string</small> <font color="#cc0000">'double'</font> <i>(length=6)</i>
</pre>
<pre class="xdebug-var-dump" dir="ltr"><small>float</small> <font color="#f57900">6,283186</font>
</pre>
<pre class="xdebug-var-dump" dir="ltr"><small>string</small> <font color="#cc0000">'double'</font> <i>(length=6)</i>
</pre>
</pre>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/12/float-vs-setlocale/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lazy Loading w modelach a&#8217;la CakePHP</title>
		<link>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-w-modelach-ala-cakephp/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-w-modelach-ala-cakephp/#comments</comments>
		<pubDate>Tue, 19 Oct 2010 14:29:07 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[Inne]]></category>
		<category><![CDATA[lazy loading]]></category>
		<category><![CDATA[model]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[PHP5]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1383</guid>
		<description><![CDATA[CakePHP nie wspiera aktualnie leniwego ładowania obiektów. W prawdzie są jakieś skrypty, ale jeszcze żaden z nich nie chciał mi zadziałać. Zacząłem się zastanawiać: &#8220;Gdybym miał zaimplementować Lazy Loading w warstwie modelu, to jak bym to zrobił?&#8221; To co mi &#8230; <a href="http://blog.grzegorzpawlik.com/2010/10/lazy-loading-w-modelach-ala-cakephp/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>CakePHP nie wspiera aktualnie leniwego ładowania obiektów. W prawdzie są jakieś skrypty, ale jeszcze żaden z nich nie chciał mi zadziałać. Zacząłem się zastanawiać: &#8220;Gdybym miał zaimplementować Lazy Loading w warstwie modelu, to jak bym to zrobił?&#8221; To co mi wyszło korzysta z rozwiązań PHP5, więc może wsparcie gałęzi 1.x Cake&#8217;a dla PHP4 implikuje brak tego wzorca?</p>
<p>Tak czy siak, podzielę się z Wami moimi pomysłami.</p>
<p>Zacznijmy od początków. Przeciwieństwem leniwego ładowania jest ładowanie gorliwe (lub zachłanne), to znaczy ładowanie na zapas wszystkiego co może okazać się potrzebne. Warstwa modelu w cake&#8217;u mogła by wyglądać tak (duże uproszeczenie, jedyna możliwa relacja to 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>Widzimy, że obiekt Group jest inicjowany długo przed użyciem:</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>Mało tego, ładuje się nawet jeśli nigdy nie wywołamy metody w tym obiekcie (zakomentuj $user->Group->find(); żeby się o tym przekonać).</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>W tym rozwiązaniu obiekt Group nie zostanie stworzony, dopóki nie będziemy go potrzebować. Wykorzystana jest do tego magiczna metoda __get, która jest wywoływana wtedy, gdy próbujemy się odnieść do atrybutu obiektu, który nie istnieje.</p>
<p>ale pozostaje kwestia wyszukiwania w modelu tak, żeby zwrócił powiązane dane<br />
np.: var_dump($user->find());</p>
<pre>
array
  0 =>
    array
      'User' =>
        array
          'id' => int 1
          'name' => string 'Franek' (length=6)
      'Group' =>
        array
          'id' => int 1
          'name' => string 'Admin' (length=5)
  1 =>
    array
      'User' =>
        array
          'id' => int 2
          'name' => string 'Janek' (length=5)
      'Group' =>
        array
          'id' => int 10
          'name' => string 'User' (length=4)
</pre>
<p>Wiemy, że belongsTo oznacza, że User powinien mieć pole (group_id), zatem query powinno wyglądać mniej więcej tak:</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>W tej chwili cakephp zawiera behavior containable, jednak on polega na załadowanych już modelach.</p>
<p>Zostańmy przy terminologii cake&#8217;a i przyjmijmy, że będziemy przekazywać informacje na temat powiązanych elementów w zmiennej $contain</p>
<p>function find($contain=null)</p>
<p>Teraz chciałbym powiedzieć: obiekcie $user zwróć mi wszystkich użytkowników z bazy wraz z informacją w jakich są grupach:</p>
<pre name="code" class="php">
$user =  new User();

$user->find(
	array(
		"belongsTo" => array($user->Group)
	)
);
</pre>
<p>Zatem zaktualizuję metodę find:</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>która od teraz zakłada, że model posiada metodę, która potrafi utworzyć odpowiedniego joina:</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>to jest oczywiście bardzo brzydkie ze względu na refleksje. Metodę getName() można jednak zaimplementować w klasie Model.</p>
<p>Tak czy siak, w tym wypadku nawet w find()&#8217;zie uwzględniającym relacje można stosować Lazy Loading</p>
<p>Na koniec całość kodu, z większą ilością relacji i przykładem działania (i małym refactoringiem &#8211; skoro już mowa o lazy loading to głupio było ładować piętnaście razy ReflectionClass):</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>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/10/lazy-loading-w-modelach-ala-cakephp/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Utrzymanie jakości kodu</title>
		<link>http://blog.grzegorzpawlik.com/2010/10/utrzymanie-jakosci-kodu/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/10/utrzymanie-jakosci-kodu/#comments</comments>
		<pubDate>Fri, 15 Oct 2010 13:49:02 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[Agile]]></category>
		<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[jakość oprogramowania]]></category>
		<category><![CDATA[QA]]></category>
		<category><![CDATA[software quality]]></category>
		<category><![CDATA[zarządzanie projektem]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1379</guid>
		<description><![CDATA[Właśnie uświadomiłem sobie, że utrzymanie wysokiej jakości kodu to jest ciągła praca. Wiem, że brzmi to może jak banał, ale to nie jest jakaś wytyczna ustalona raz na początku projektu. Zrozumienie tego zajęło mi 4 lata. Nie możesz się umówić, &#8230; <a href="http://blog.grzegorzpawlik.com/2010/10/utrzymanie-jakosci-kodu/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Właśnie uświadomiłem sobie, że utrzymanie wysokiej jakości kodu to jest <strong>ciągła praca</strong>.</p>
<p>Wiem, że brzmi to może jak banał, ale to nie jest jakaś wytyczna ustalona raz na początku projektu. Zrozumienie tego zajęło mi 4 lata. Nie możesz się umówić, że od teraz, od tego projektu, od tej funkcji będziesz pisał kod wysokiej jakości.</p>
<p>Możesz umówić się, że będziesz pisał kod o jakości najlepszej na jaką Cię w tym momencie stać.</p>
<p><span id="more-1379"></span></p>
<p>Jednak w międzyczasie nauczysz się czegoś nowego. Wysokiej jakości kodu to taki, którego <strong>jakość rośnie ciągle</strong>. Nie tylko wraz z dodawaniem nowych linii. Również trafiając na kod z przed roku warto pochylić się nad nim.</p>
<p>Czy masz zespół, który stosuje <a href="http://97rzeczy.devblogi.pl/artykuly/8/zasada-skautow">zasadę skautów</a>?</p>
<p>Chciałbyś być członkiem takiego zespołu?</p>
<p>Jak zawsze &#8211; staraj się myśleć, żeby nie popaść w <a href="http://www.devblogi.pl/2010/05/pozacanie.html">przesadę</a>.</p>
<hr/>
A to wszystko doszło do mnie, gdy próbowałem w projekcie rozładować trochę powiązania między kontrolerami, gdzie ajaxowa lista kierowców generowana była w kontrolerze/akcji cars/list_drivers ;)</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/10/utrzymanie-jakosci-kodu/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Long Polling – tutorial, part 2</title>
		<link>http://blog.grzegorzpawlik.com/2010/10/long-pollingtutorial-part-2/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/10/long-pollingtutorial-part-2/#comments</comments>
		<pubDate>Tue, 12 Oct 2010 11:00:10 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1363</guid>
		<description><![CDATA[jak udawać &#8220;server push&#8221; przy pomocy &#8220;client pull&#8221;? Jak już mogłeś przeczytać na wikipedii, long polling oznacza po prostu zapytania (w naszym przypadku zapytania XHR, czyli ajax), które potrafią czekać bardzo długi czas na odpowiedź serwera. Serwer w ogóle nie &#8230; <a href="http://blog.grzegorzpawlik.com/2010/10/long-pollingtutorial-part-2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h4>jak udawać &#8220;server push&#8221; przy pomocy &#8220;client pull&#8221;?</h4>
<p>
Jak już mogłeś przeczytać na <a href="http://en.wikipedia.org/wiki/Push_technology#Long_polling">wikipedii</a>, long polling oznacza po prostu zapytania (w naszym przypadku zapytania XHR, czyli ajax), które potrafią czekać bardzo długi czas na odpowiedź serwera. Serwer w ogóle nie odpowiada dopóki coś interesującego (z punktu widzenia zapytania) się nie pojawi.
</p>
<p>
Istnieją dwa możliwe przypadki:</p>
<ol>
<li>coś się pojawia (w naszym przypadku nowy post w bazie danych), zatem serwer wysyła informacje, zapytanie kończy się sukcesem i robi coś użytecznego z otrzymanymi danymi</li>
<li>nic się nie pojawia przez jakiś czas, zapytanie kończy się timeoutem.</li>
</ol>
<p>
Oba kończą się jednak tym samym &#8211; wywołaniem kolejnego zapytania, które będzie oczekiwało, aż wydarzy się coś interesującego.
</p>
<p>
Pewnie masz kilka pomysłów jak to zaimplementować. Jeśli tak &#8211; wypróbuj je teraz (oraz jeśli różnią się od mojego rozwiązania &#8211; chętnie je obejrzę, więc wrzuć je w komentarzu.)
</p>
<p>
Po drodze można się natknąć na kilka problemów. Mam nadzieję, ze uda mi się pokazać je wszystkie.=
</p>
<p><h4>założenia</h4>
<p>Warto pamiętać, że będziemy intensywnie odpytywać serwer. Zatem nie jest dobrym pomysłem wysyłanie html&#8217;a tam i z powrotem. Zapytania <a href="http://en.wikipedia.org/wiki/Push_technology#Long_polling">XHttpRequest</a> powinny oczekiwać odpowiedzi w formacie <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>.<br />
Kolejną kwestią jest to, że jeśli nie ma nic istotnego do wysłania z serwera, lepiej żeby zapytanie skończyło się timeoutem, niż otrzymało odpowiedź &#8220;nic nowego&#8221;. Nawet jeśli nie jest to super eleganckim rozwiązaniem (timeout jednak jest pewnego rodzaju awarią).
</p>
<p>
Powinniśmy też zdecydować jak wiele informacji wysyłać do serwera, aby otrzymać nowe wiadomości dla wątku. Minimum to id &#8220;korzenia&#8221; wątku oraz id ostatniej wyświetlonej wiadomości. Te informacje wystarczą, żeby zdobyć wszystkie wiadomości, które znajdują się w wątku, oraz ich id jest więcej niż najstarsza wiadomość w wątku. Jednak w tym wypadku musimy wykonać przynajmniej dwa zapytania: jedno, aby zdobyć wartości &#8220;lft&#8221; i &#8220;rght&#8221; korzenia oraz drugie, aby pobrać nowe wątki.
</p>
<p>
Drugim podejściem jest wysłanie listy identyfikatorów wszystkich postów w wątku i zwrócenie wszystkich wiadomości, których wartości parent_id zawierają się w tym zestawie.
</p>
<p><span class="font-size: .8em">Jest jeszcze trzeci pomysł, na który wpadłem już podczas redagowania tego tutoriala. Otóż można wysyłać wartości &#8220;lft&#8221; i &#8220;rght&#8221; korzenia zamiast jego id, wtedy oszczędzimy jedno z zapytań (w porównaniu do pierwszego przedstawionego podejścia). To rozwiązanie sprawia, że przy pierwotnym generowaniu find musi zwracać też te pola. Nie wiem, które z rozwiązań okazało by się szybsze &#8211; trzeba by to sprawdzić empirycznie.</span></p>
<p>
Ja wybrałem podejście pierwsze dlatego, że jest rozwiązaniem czystszym. Nie mam wystarczająco dużo informacji, które z rozwiązań będzie szybsze.
</p>
<p>
Utwórz zatem akcje, która przyjmuje dwa parametry (id korzenia i ostatniej wiadomości w wątku) i wysyła w odpowiedzi nowe wiadomości (jeśli są) lub nie robi nic (nawet nie wysyła pustej odpowiedzi).
</p>
<p><pre name="code" class="php">
//controllers/posts_controller.php
	function get_new($rootId, $lastMessageId){
		$root = $this->Post->read(null, $rootId);

		$posts = array();

		while(empty($posts)){
			$posts = $this->Post->find(
				"all",
				array(
					"conditions"=>array(
						"and"=> array(
							"Post.lft > " => $root["Post"]["lft"],
							"Post.lft < " => $root["Post"]["rght"],
							"Post.id >" => $lastMessageId
						)
					)
				)
			);
			if(empty($posts)){
				sleep(2); // sleep for two seconds
			}
		}
		$this->set("posts", $posts);
		$this->render("get_new", false);
	}
</pre>
<p>i widok:</p>
<pre name="code" class="php">
// views/posts/get_new.ctp
echo json_encode($posts);
</pre>
</p>
<p>
Możesz wypróbować teraz tą akcję w przeglądarce. Jednak jeśli wybierzesz /posts/get_new/1/8, gdzie 8 to id ostatniej odpowiedzi &#8211; Twój serwer dostanie czkawki. Dla testów wybierz takie dane, żeby serwer zwrócił jakieś &#8220;nowe&#8221; wiadomości (na przykład /posts/get_new/1/7).
</p>
<h4>Problem z serwerem</h4>
<p>
Zapchanie serwera ma jakiś związek z sesjami. Apache czeka do końca wykonywanego skryptu zanim zapisze dane sesji, na końcu którego niejawnie zapisuje sesję. Z jakiegoś powodu Apache nie może zwolnić wątku dla danego zapytania jeśli sesja nie jest zamknięta (nawet jeśli wciśniesz &#8220;anuluj&#8221; w przeglądarce, co zazwyczaj powinno skutkować zakończeniem wykonania skryptu). Co należy zrobić do wywołać funkcję session_commit(); na samym początku akcji, które są pobierane techniką long polling. (jeśli w danej akcji musisz zachować jakieś dane w sesji, zrób to przed session_commit() i przed pętlą while).</p>
<pre name="code" class="php">
//controllers/posts_controller.php
	function get_new($rootId, $lastMessageId){
		session_commit();
		$root = $this->Post->read(null, $rootId);
		//...
</pre>
</p>
<p>
Mamy teraz gotową akcję, która zwraca łańcuch w formacie JSON z nowymi wiadomościami. Załadujmy je teraz do naszego wątku.
</p>
<p>
Na początku trzeba zadbać o to, żeby id korzenia i ostatniego postu były zapamiętane w obiekcie Thread. Id korzenia jest w polu parent_id pierwszego elementu (zmienna $thread w widoku PostsController::view()), id ostatniego postu znajdziemy podczas generowania drzewa dyskusji.<br />
Dodaj te właściwości do klasy Thread:</p>
<pre name="code" class="javascript">
//webroot/js/thread.js
(function(window, document, udefined){
	window.Thread = function(_posts) {
		this.posts = _posts;
		this.rootId = false; //<==
		this.lastId = false; //<==
		this.extendJQuery();
		return this;
	};

})(window, document);
</pre>
</p>
<p>
Zachowaj id korzenia i ostatniej wiadomości w metodzie createThread():</p>
<pre name="code" class="javascript">
//webroot/js/thread.js
Thread.prototype.createThread = function(){
	if(this.posts.length <1){
		return this;
	}
	this.rootId = this.lastId = posts[0].Post.parent_id; //<==
	var _this = this; //musimy zapamiętać this, gdyż w metodzie each this oznacza aktualny element w danym kroku iteracji
	$(this.posts).each(function(k, v){
		if($("#"+v.Post.parent_id).find("ul").length == 0){
			if($("#"+v.Post.parent_id).find("div.reply").length == 0){
				_this.createReplyDiv().appendTo("#"+v.Post.parent_id);
			}
	        $("
<ul>", {class: "posts children"}).appendTo("#"+v.Post.parent_id);
		}
	     $("
<li>", {
	    	  id: v.Post.id,
	    	  innerHTML: "
<div>"+v.Post.message+"</div>

"
	    	  ,
	    	  class: "post child"
	     }).
	     append(_this.createReplyDiv()).
	     appendTo("#"+v.Post.parent_id+">ul");
	     if(v.Post.id>_this.lastId){ //<==
	    	 _this.lastId = v.Post.id;
	     }
	});
	return this;
};
</pre>
</p>
<p>
Teraz chcielibyśmy, aby były pobierane dane z akcji get_new, jednak obiekt thread nie ma pojęcia, że tam się one znajdują. Umożliwmy przekazanie docelowego adresu (endpoint) url w konstruktorze.</p>
<pre name="code" class="javascript">
//webroot/js/thread.js
(function(window, document, udefined){
	window.Thread = function(_posts, _updateEndpoint) {//<==
		this.posts = _posts;
		this.rootId = false;
		this.lastId = false;
		this.updateEndpoint = _updateEndpoint; //<==
		this.extendJQuery();
		return this;
	};

})(window, document);
</pre>
</p>
<p>
i zaktualizuj inicjowanie obiektu klasy Thread:</p>
<pre name="code" class="javascript">
//views/layouts/default.ctp
		$(document).ready(function(){
			if(!window.posts) return false;
			window.thread = new Thread(
				posts,
				"&lt;?php echo $html->url(
							array(
							'controller'=>'posts',
							'action'=>'get_new')
				);?&gt;"
			).createThread();
			console.log(thread);
		});
</pre>
</p>
<p>
teraz stwórz metodę update(). Powinna pobierać JSON z nowymi postami i w wypadku timeoutu czy sukcesu wywołać siebie samą ponownie:</p>
<pre name="code" class="javascript">
//webroot/js/thread.js
Thread.prototype.update = function(){
	if(!this.updateEndpoint || !this.rootId || !this.lastId){
		alert("Thread corrupted");
		return false;
	}
	_this = this;
	$.ajax({
		url: this.updateEndpoint +"/"+ this.rootId+ "/" + this.lastId,
		dataType: 'json',
		timeout: 5000,
		success: function(){
			_this.update();
		},
		error: function(){
			_this.update();
		}
	});
	return true;
}
</pre>
</p>
<h4>bug #1</h4>
<p>
Przy okazji natknąłem się na błąd wprowadzony przez moją nieuwagę. Możesz go zauważyć, kiedy w bazie pojawi się element o id równym 10 i większym. Okazuje się, że obiekt Thread zachowuje największe id, ale porównuje je w kolejności słownikowej (sprawdź w konsoli firebug: "10">"9" zwraca false). Proszę, popraw instrukcję "if" na samym końcu w ten sposób:</p>
<pre name="code" class="javascript">
//...
		if(parseInt(v.Post.id)>_this.lastId){
			_this.lastId = parseInt(v.Post.id);
		}
}
</pre>
<p>Wywołaj teraz nowo utworzoną metodę w Thread.createThread():</p>
<pre name="code" class="javascript">
Thread.prototype.createThread = function(){
	var _this = this;
	if(this.posts.length <1){
		return this;
	}
	this.rootId = this.lastId = posts[0].Post.parent_id;
	$(this.posts).each(function(k, v){
		if($("#"+v.Post.parent_id).find("ul").length == 0){
			if($("#"+v.Post.parent_id).find("div.reply").length == 0){
				_this.createReplyDiv().appendTo("#"+v.Post.parent_id);
			}
        	$("
<ul>", {class: "posts children"}).appendTo("#"+v.Post.parent_id);
		}

		$("
<li>", {
			id: v.Post.id,
			innerHTML: "
<div>"+v.Post.message+"</div>

",
			class: "post child"
		}).
		append(_this.createReplyDiv()).
		appendTo("#"+v.Post.parent_id+">ul");
		if(parseInt(v.Post.id)>_this.lastId){
		 _this.lastId = parseInt(v.Post.id);
		}
	});
	this.update(); //<==
	return this;
}
</pre>
</p>
<p>
Możesz teraz sprawdzić w konsoli, czy raz za razem próbuje pobrać nowe dane z serwera. Dodaj post w wątku przy pomocy innej przeglądarki i sprawdź co dzieje się z zapytaniem...<br />
Jak widzisz mamy pozytywną odpowiedź, ale od tego momentu każdy kolejny request dostaje tą samą odpowiedź a drzewo wątku nie jest aktualizowane. Poprawimy to teraz. Po pierwsze wyodrębnij jako metodę fragment kodu (z metody createThread), która tworzy kolejne elementy z wypowiedziami - możemy ją ponownie użyć...
</p>
<p>
Thread.createThread should look like that:</p>
<pre name="code" class="javascript">
Thread.prototype.createThread = function(){
	if(this.posts.length <1){
		return this;
	}
	this.rootId = this.lastId = posts[0].Post.parent_id;
	var _this = this;
	$(this.posts).each(function(k, v){
		_this.addPost(v.Post.id, v.Post.parent_id, v.Post.message); //<==
	});
	this.update();
	return this;
};
</pre>
</p>
<p>
oraz nasza nowa metoda Thread.addPost:</p>
<pre name="code" class="javascript">
Thread.prototype.addPost = function(id, parentId, message){
	if($("#"+parentId).find("ul").length == 0){
		if($("#"+parentId).find("div.reply").length == 0){
			this.createReplyDiv().appendTo("#"+parentId);
		}
        $("
<ul>", {class: "posts children"}).appendTo("#"+parentId);
	}
     $("
<li>", {
    	  id: id,
    	  innerHTML: "
<div>"+message+"</div>

"
    	  ,
    	  class: "post child"
     }).
     append(this.createReplyDiv()).
     appendTo("#"+parentId+">ul");
     if(id>this.lastId){
    	 this.lastId = id;
     }
}
</pre>
</p>
<h4>bug #2</h4>
<p>
Trafiłem na jeszcze bardziej zakamuflowany błąd - skrypt powoduje wyciek pamięci na serwerze (wystarczy poczekać odpowiednio długo, gdy "lecą" kolejne requesty) - po timoucie xhr'a skrypt po stronie serwera najwyraźniej się nie kończy.<br />
Dodałem prosty licznik do metody get_new:</p>
<pre name="code" class="php">
//controllers/posts.php
	function get_new($rootId, $lastMessageId){
		//...
		$counter = 2; //<==
		while(empty($posts)){
			$posts = $this->Post->find(
				"all",
				array(/*...*/)
			);
			if($counter>5){ //<==
				break; //<==
			}else{ //<==
				$counter += 2; //<==
			} //<==
			if(empty($posts)){
				sleep(2); // sleep for two seconds
			}
		}
		$this->set("posts", $posts);
		$this->render("get_new", '');
	}
</pre>
</p>
<p>
Dlaczego inkrementuję o 2? Dlatego, że w pętli jest 2 sekundowy sleep(), zatem sprawdzam, czy skrypt "przespał" przynajmniej 5 sekund (co ma związek z 5 sekundowym timeoutem w żądaniu ajax).
</p>
<p>
Aby zamknąć sprawę - wywołaj wyodrębnioną metodę w zdarzeniu success:</p>
<pre name="code" class="javascript">
//webroot/js/thread.js
Thread.prototype.update = function(){
	if(!this.updateEndpoint || !this.rootId || !this.lastId){
		alert("Thread corrupted");
		return false;
	}
	_this = this;
	$.ajax({
		url: this.updateEndpoint +"/"+ this.rootId+ "/" + this.lastId,
		dataType: 'json',
		timeout: 5000,
		success: function(data){ //<==
			$(data).each(function(k,v){
				_this.addPost(v.Post.id, v.Post.parent_id, v.Post.message); //<==
			});
			_this.update();
		},
		error: function(){
			_this.update();
		}
	});
	return true;
}
</pre>
</p>
<p>
To tyle. Oczywiście przy pomocy long polling (i niedługo web sockets) możesz zaimplementować więcej fajnych funkcjonalności. Możesz naprzykład wyświetlać informację, że ktoś zaczął odpowiadać na konkretną wypowiedź w wątku.
</p>
<p>
Jest jeszcze kilka usprawnień, które warto w takim systemie dodać, jak choćby podświetlenie nowo dodanych odpowiedzi, ale wykracza to już poza tematykę tego tutoriala.
</p>
<p>
Mam nadzieję, że ten tutorial się wam spodobał, nie wahajcie się mi napisać o tym co o nim myślicie.</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/10/long-pollingtutorial-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Long Polling, tutorial – cześć 1</title>
		<link>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-1/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-1/#comments</comments>
		<pubDate>Mon, 20 Sep 2010 17:30:44 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[JS and friends]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1222</guid>
		<description><![CDATA[(poprzednia część tego tutoriala) Teraz, kiedy mamy wygenerowane drzewo dyskusji, następnym krokiem będzie umożliwienie odpowiedzi w wątku. Użytkownik powinien móc kliknąć pod konkretną wypowiedzią i natychmiast zacząć wpisywać odpowiedź. Dlatego pod każdym elementem li.post utwórzmy div&#8217;a, którym będzie konterem dla &#8230; <a href="http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-1/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="/?p=1189">(poprzednia część tego tutoriala)</a><br />
<!--pagetitle:Placeholder dla formularza odpowiedzi--><br />
Teraz, kiedy mamy wygenerowane drzewo dyskusji, następnym krokiem będzie umożliwienie odpowiedzi w wątku. Użytkownik powinien móc kliknąć pod konkretną wypowiedzią i natychmiast zacząć wpisywać odpowiedź.<br />
<span id="more-1222"></span></p>
<p>Dlatego pod każdym elementem li.post utwórzmy div&#8217;a, którym będzie konterem dla formularza odpowiedzi. Znajdź w thread.js linie odpowiedzialne za tworzenie elementów li zawierających posty i dodaj html z div&#8217;em do parametru innerHTML:</p>
<pre name="code" class="javascript">
//app/webroot/js/thread.js
$("
<li>", {
	    	  id: v.Post.id,
	    	  innerHTML: "
<div>"+v.Post.message+"</div>

" +
	    	  			 "
<div class='reply' />" //<---
	    	  ,
	 class: "post child"
	 }).
	 appendto("#"+v.post.parent_id+">ul");
</pre>
<p>Przyda się teraz nieco css&#8217;ów. Kontener powinien zajmować nieco więcej miejsca (żeby łatwiej było w  niego kliknąć) oraz po najechaniu na niego myszką powinien zmienić kolor, sugerując użytkownikowi, że może tam kliknąć. Podlinkuj nowy plik css do layoutu:</p>
<pre name="code" class="php">
//app/views/layouts/default.ctp
echo $this->Html->css('cake.generic');
echo $this->Html->css('thread'); //<-add this
echo $this->Javascript->link(array("thread"));
</pre>
<p>a następnie stwórz plik thread.css z taką zawartością:</p>
<pre name="code" class="css">
/* app/webroot/css/thread.css */
div.reply {
	height: 5px;
	cursor: text;
}
div.reply:hover{
	background-color: #8EAFEB;
}
</pre>
<p>
Tak to powinno wyglądać teraz:<br />
<img src="http://blog.grzegorzpawlik.com/wp-content/uploads/2010/09/replyplaceholder.png" alt="" title="replyplaceholder" width="708" height="274" class="alignnone size-full wp-image-1211" />
</p>
<p>Bardziej spostrzegawczy zauważyli od razu, że miejsce pod pierwszą wypowiedzią w wątku nie zachowuje się tak jak powinno, poprawmy nieco thread.js:</p>
<pre name="code" class="javascript">
if($("#"+v.Post.parent_id).find("ul").length == 0){
	if($("#"+v.Post.parent_id).find("div.reply").length == 0){
		$("
<div>", {class: "reply"}).appendTo("#"+v.Post.parent_id);
	}
	$("
<ul>", {class: "posts children"}).appendTo("#"+v.Post.parent_id);
}
</pre>
<p>Teraz pojawiło nam się odrobinę redundancji w kodzie &#8211; dwa miejsca, gdzie jest tworzony element div.reply. Naprawmy to. Usuniemy dodawanie stringa</p>
<pre>
&lt;div class='reply'/&gt;
</pre>
<p>i zastąpimy go tworzeniem elementu DOM przy pomocy jQuery, a następnie wyekstrahujemy motodę, która to robi. Ostatecznie kod powinien wyglądać tak:</p>
<pre name="code" class="javascript">
(function(window, document, udefined){
	window.Thread = function(_posts) {
		this.posts = _posts;
		return this;
	};
})(window, document);

Thread.prototype.createReplyDiv = function(){
	return $("
<div>", {class: "reply"});
}

Thread.prototype.createThread = function(){
	if(this.posts.length <1){
		return this;
	}
	var _this = this; //* patrz poniżej
	$(this.posts).each(function(k, v){
		if($("#"+v.Post.parent_id).find("ul").length == 0){
			if($("#"+v.Post.parent_id).find("div.reply").length == 0){
				_this.createReplyDiv().appendTo("#"+v.Post.parent_id);
			}
	        $("
<ul>", {class: "posts children"}).appendTo("#"+v.Post.parent_id);
		}
	     $("
<li>", {
	    	  id: v.Post.id,
	    	  innerHTML: "
<div>"+v.Post.message+"</div>

"
	    	  ,
	    	  class: "post child"
	     }).
	     append(_this.createReplyDiv()).
	     appendTo("#"+v.Post.parent_id+">ul");
	});
	return this;
}
</pre>
<p>(*)Jeśli zastanawiasz się po co zapamiętujemy `this` w zmiennej `_this` śpieszę z wyjaśnieniem. W kodzie chcemy wywołać, wyodrębnioną dopiero co, metodę createReplyDiv. Niestety w anonimowej funkcji, która jest przekazana do metody each(), `this` wskazuje już na konkretny element z kolekcji po której ów each() iteruje. Musimy zapamiętać &#8220;starą&#8221; referencję `this` (czyli obiekt klasy Thread).</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Long Polling, tutorial, część 0</title>
		<link>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-0/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-0/#comments</comments>
		<pubDate>Tue, 14 Sep 2010 17:00:24 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[JS and friends]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1189</guid>
		<description><![CDATA[Zachęcony dobrym odbiorem mojego pierwszego turoriala zacząłem pracę nad kolejnym. Niestety nie udało mi się skończyć przed urlopem i straciłem nieco wątek. Dlatego zdecydowałem się dostarczyć go Wam w częściach, dzięki temu wykonując kolejne (gotowe już) części będę mógł się &#8230; <a href="http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-0/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<blockquote><p>
Zachęcony dobrym odbiorem mojego <a href="/?p=815">pierwszego turoriala</a> zacząłem pracę nad kolejnym. Niestety nie udało mi się skończyć przed urlopem i straciłem nieco wątek. Dlatego zdecydowałem się dostarczyć go Wam w częściach, dzięki temu wykonując kolejne (gotowe już) części będę mógł się odnaleźć i jednocześnie mam nadzieję na wyłapanie drobnych błędów. Mam nadzieję, że Wam się spodoba.</p></blockquote>
<p><!--pagetitle:idea i przygotowanie projektu--></p>
<h4>Czym jest long pooling</h4>
<p>Long polling jest metodą, która przy pomocy bezstanowego protokołu HTTP (opartego na zasadzie &#8220;żądań wysyłanych do serwera&#8221; <server pull>), symuluje sytuację, w której serwer wysyła zaktualizowaną informację klientowi w momencie jej aktualizacji (&#8220;<a href="http://en.wikipedia.org/wiki/Push_technology">server push</a>&#8220;. W prawdzie dużymi krokami zbliżają się do nas <a href="http://hacks.mozilla.org/2010/04/websockets-in-firefox/">web sockets</a> zaimplementowane w firefozie, ale wydaje mi się, że fajnie jest zapoznać się z tą techniką (i przy okazji &#8220;podostrzyć piłę&#8221;).<br />
<span id="more-1189"></span></p>
<p>Zajmiemy się dość prostym pomysłem. Kombinacja czatu i forum, w którym możemy umieszczać swoje posty i w czasie rzeczywistym, bez przeładowania strony, widzieć pojawiające się posty innych uczestników dyskusji (możesz myśleć o tym jak o okrojonym Google Wave).</p>
<p>Po stronie serwera wykorzystamy <a href="http://cakephp.org/">cakePHP</a>, a po stronie klienta wesprze nas cudowne <a href="http://jquery.com/">jQuery</a>. Zakładam, że jesteś nieco zaznajomiony z tymi narzędziami &#8211; nie będę tutaj zamieszczał instrukcji jak skonfigurować połączenie z bazą w cakePHP itp.</p>
<p>Funkcjonalności w tworzonym systemie:</p>
<ul>
<li>dodawanie nowego wątku</li>
<li>odpowiadanie w wątku bez przeładowania strony</li>
<li>otrzymywanie odpowiedzi innych użytkowników bez przeładowania strony</li>
</ul>
<p>dodatkowo pamiętamy, że jako pasjonaci zaczynamy z niewielką ilością funduszy, więc chcemy aby nasza aplikacja była tak lekka dla serwera jak to możliwe (czyli przeniesiemy tyle pracy na stronę klienta ile się da).</p>
<h4>Przygotowanie projektu</h4>
<p>Pobierz najnowszą wersję cake&#8217;a:</p>
<pre>
$ git clone http://github.com/cakephp/cakephp.git longPolling
</pre>
<p>I stwórz bazę danych, która przechowa nasze dyskusje</p>
<pre name="code" class="sql">
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `lft` int(11) DEFAULT NULL,
  `rght` int(11) DEFAULT NULL,
  `message` text COLLATE utf8_unicode_ci NOT NULL,
  `modified` datetime NOT NULL,
  UNIQUE KEY `id` (`id`),
  KEY `parent_id` (`parent_id`),
  KEY `lft` (`lft`),
  KEY `rght` (`rght`),
  KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
</pre>
<p>Jak łatwo zauważyć będziemy wykorzystywali technikę zwaną &#8220;<a href="http://dev.mysql.com/tech-resources/articles/hierarchical-data.html">Nested set model</a>&#8220;, którą implementuje <a href="http://book.cakephp.org/view/91/Tree">Treebehavior</a>. Dla maksymalnej prostoty wątki nie mają tytułów itd, zawiązanie wątku odbywa się po prostu przed dodanie postu, który nie jest odpowiedzią na żaden inny post.</p>
<p>Teraz przy pomocy narzędzia bake stwórz model, kontroler i widoki dla CRUD &#8211; będzie to podstawą dla dalszej pracy. Nie zawracaj sobie głowy walidacją czy akcjami admina &#8211; nie jest to ważne w tym ćwiczeniu. Dodaj behavior Tree do modelu Post.</p>
<h4>Porządki</h4>
<p>Już mamy trochę bałaganu w naszym kodzie. Widoki `add` i `edit` nawzajem się duplikują, więc usuniemy widok `add.ctp` oraz zmusimy PostsController::add() do korzystania z widoku `edit`:</p>
<pre name="code" class="php">
//posts_controller.php
	function add() {
		if (!empty($this->data)) {
			$this->Post->create();
			if ($this->Post->save($this->data)) {
				$this->Session->setFlash(__('The post has been saved', true));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The post could not be saved. Please, try again.', true));
			}
		}
		$this->render("edit");
	}
</pre>
<p>oraz, żeby zachować nieco estetyki, otoczmy kod wyświetlający link &#8220;delete&#8221; instrukcją if, żeby nie pokazywał się w przypadku dodawania nowego postu:</p>
<pre name="code" class="php">
		&lt;?php if(!empty($this->data["Post"]["id"])): ?&gt;
			&lt;li><?php echo $this->Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value('Post.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $this->Form->value('Post.id'))); ?&gt;</li>

		&lt;?php endif; ?>
</pre>
<p>i jeszcze kosmetyka &#8211; usuńmy pola user_id, lft, rght, modified z formularza (są one wypełniane automatycznie).</p>
<p>I jeszcze jedno: na początku będziemy wybierać w formularzu posty z listy select, żeby wskazać wiadomość na którą odpowiadamy, dodaj w kontrolerze poniższą metodę i wywołaj ją w akcjach `add` oraz `edit`:</p>
<pre name="code" class="php">
//posts_controller.php
function _getParentPosts(){
	$this->set('parents', $this->Post->find("list"));
}
</pre>
<p>Teraz możesz ręcznie dodać dyskusję&#8230; chyba, że jesteś bardzo wygodny &#8211; możesz odpalić poniższe instrukcje sql:</p>
<pre name="code" class="sql">
INSERT INTO `posts` VALUES (1,NULL,NULL,1,8,'first message in the topic.','0000-00-00 00:00:00'),
(2,1,NULL,2,5,'my very first reply to the very first message!','0000-00-00 00:00:00'),
(3,1,NULL,6,7,'I just have some opinion about first message, and will share it with you... soon.','0000-00-00 00:00:00'),
(4,2,NULL,3,4,'I think you shouldn\'t make off-topics here','0000-00-00 00:00:00');
</pre>
<p>Teraz jesteśmy gotowi do zabawy.</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/09/long-polling-tutorial-czesc-0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Kocham UnitTesty*</title>
		<link>http://blog.grzegorzpawlik.com/2010/07/kocham-unittesty/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/07/kocham-unittesty/#comments</comments>
		<pubDate>Thu, 29 Jul 2010 15:21:20 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[Agile]]></category>
		<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1089</guid>
		<description><![CDATA[Dziś stanąłem przed zadaniem poprawienia komponentu, który automatyzuje nam kwestię wyszukiwania elementów w listingach. Jego działanie można opisać z grubsza tak: Na przykład jeśli chcę, żeby moja lista kosztów (Cost) mogła być wyszukiwana po nazwach &#8211; dodaję formularz z Cost.name &#8230; <a href="http://blog.grzegorzpawlik.com/2010/07/kocham-unittesty/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><img src="http://blog.grzegorzpawlik.com/wp-content/uploads/2010/07/test.jpg" alt="" title="" width="400" height="300" class="alignright size-full wp-image-1090" /><br />
Dziś stanąłem przed zadaniem poprawienia komponentu, który automatyzuje nam kwestię wyszukiwania elementów w listingach. </p>
<p>Jego działanie można opisać z grubsza tak:<br />
Na przykład jeśli chcę, żeby moja lista kosztów (Cost) mogła być wyszukiwana po nazwach &#8211; dodaję formularz z Cost.name i w akcji kontrolera wywołuję jedynie</p>
<pre name="code" class="php">
$conditions = $this->Search->getConditions($this->params);
$this->paginate("conditions"=>$conditions);
$this->set('costs', $this->paginate('Cost'));
</pre>
<p>Największy bajer polegał na tym, że jeżeli moje koszty były powiązane z samochodami (Car) to chcą wyszukiwać po nazwie samochodu wystarczyło, że dodałem do formularza pole Car.name. Mój komponent potrafił zorientować się, że to jest powiązany model, wyszukać elementy spełniające warunek, a następnie zwrócić w conditions coś takiego:</p>
<pre>
Array
(
    [and] => Array
        (
            [Cost.id] => Array
                (
                    [0] => 1
                    [1] => 2
                    [2] => 5
                    [3] => 9
                    [4] => 10
                    [5] => 13
                    [6] => 17
                    [7] => 18
                )

        )

)
</pre>
<p>Gdzie cake ładnie sobie to przerabiał na zapytanie (w przybliżeniu)</p>
<pre name="code" class="mysql">
Select * from costs as Cost where Cost.id IN (1, 2, 5, 9, 10, 13, 17, 18)
</pre>
<p>Byłem bardzo zadowolony z komponentu. Służył nam dzielnie przez przynajmniej dwie ostatnie iteracje. Każdą wyszukiwarkę/filtr dodawaliśmy w 5 minut na żądanie klienta (wewnętrznego ;)).</p>
<p>Mało tego, dość dużym wyzwaniem było sporządzenie testów dla tego komponentu (kilka MockObjects nawet się tam pojawiło) &#8211; pewnie dlatego tak sumiennie je wykonałem &#8211; <strong>91% (z hakiem!) pokrycia kodu przez testy</strong>.</p>
<p>Przyszedł jednak dzień, gdy podczas prezentacji iteracji szef (klient wewnętrzny) powiedział (cytat nie dosłowny):<br />
&#8220;Chcę jeszcze filtrować koszty wg numerów faktur (Invoice.number) z którymi te koszty są powiązane&#8221;</p>
<p>Problem w tym, że koszty są powiązane z fakturami poprzez elementy faktur (InvoiceElement), a konkretniej</p>
<pre>
Cost habtm InvoiceElement belongsTo Invoice
</pre>
<p>Zatem przyszło mi poprawić komponent, przy czym szczęśliwie zdążyłem zapomnieć wszystkich niuansów, foreachów i innych takich, które harcowały wewnątrz search_component.php. Myślę sobie &#8211; ok, tak jak poprzednio &#8211; dużo testów. Ale nie wiedziałem jak się do nich zabrać. Wiedziałem jedynie, że nazwa pola dla nowego przypadku powinna zawierać ścieżkę powiązań (np. InvoiceElement.Invoice.number). </p>
<p>Mając jednak w głowie dość przydatną radę na temat unit testów, która brzmi:</p>
<blockquote><p>Najlepsze są testy napisane przed kodem. Jednocześnie napisanie najpierw kodu, a potem testów jest o niebo lepsze od braku jakichkolwiek testów.</p></blockquote>
<p>Dlatego postanowiłem od razu przejść do próby implementacji nowych elementów dbając jedynie o kondycję dotychczasowych testów.</p>
<p>To co się wydarzyło można nazwać chyba &#8220;uprzężą testową&#8221;. Mogłem odważnie śmigać po dotychczasowo napisanym kodzie, bo w tym samym czasie zielone pole w rezultatach testów mówiło mi &#8220;śmiało dalej! niczego do tej pory nie popsułeś!&#8221;. Wspaniałe uczucie.</p>
<p>Wprawdzie po skończeniu pokrycie kodu spadło do 80% (nota bene i tak świetny wynik, 70% uznaje się za przyzwoite pokrycie), ale zaraz po napisaniu tego postu zabieram się za sporządzenie przynajmniej kilku testów. Trzeba zdążyć przed urlopem ;)</p>
<p>(*) Ta miłość jest nieodwzajemniona, gdy temat rozmowy schodzi na Fixture&#8217;y. Pewnie dlatego ten komponent testuje się tak ładnie i szybko &#8211; nie korzysta z bazy, tylko z Mock&#8217;owanych obiektów.</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/07/kocham-unittesty/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Możesz podzielić swój kod na moduły w cakePHP</title>
		<link>http://blog.grzegorzpawlik.com/2010/07/mozesz-podzielic-swoj-kod-na-moduly-w-cakephp/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/07/mozesz-podzielic-swoj-kod-na-moduly-w-cakephp/#comments</comments>
		<pubDate>Mon, 19 Jul 2010 11:10:33 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[Agile]]></category>
		<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[CakePHP 1.3]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=1078</guid>
		<description><![CDATA[Szybki trick dla dużych projektów: używaj podkatalogów. Pisząc rozbudowaną aplikację możesz podzielić kod na moduły: controllers/module/module_things_controller.php class ModuleThingsController extends AppController { } models/module/module_thing.php class ModuleThing extends AppModel{ } (Moduły uznałbym za coś pośredniego między kupą kontrolerów, a pluginami. Nie wymagają &#8230; <a href="http://blog.grzegorzpawlik.com/2010/07/mozesz-podzielic-swoj-kod-na-moduly-w-cakephp/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Szybki trick dla dużych projektów: <strong>używaj podkatalogów</strong>.<br />
<span id="more-1078"></span><br />
Pisząc rozbudowaną aplikację możesz podzielić kod na moduły:</p>
<pre name="code" class="php">
controllers/module/module_things_controller.php
class ModuleThingsController extends AppController {
}
models/module/module_thing.php
class ModuleThing extends AppModel{
}
</pre>
<p>(Moduły uznałbym za coś pośredniego między kupą kontrolerów, a pluginami. Nie wymagają mocniejszego rozdzielenia zależności między kontrolerami w przeciwieństwie do pluginów, dla których jest to dobrą praktyką. Z drugiej strony <strong>wprowadzają nieco porządku w plikach źródłowych</strong>)</p>
<p>Od razu rzuca się w oczy fakt powtarzania nazwy modułu w katalogu i nazwie pliku (oraz w definicji klasy). Wynika to po pierwsze z konieczności jednoznacznego nazywania klas w ramach jednego projektu. Po drugie, jeśli istnieją dwa pliki od tej samej nazwie i definicji klasy (ale jeden w podkatalogu) to domyślnie będzie używany ten z katalogu głównego.</p>
<p>Tak czy siak warto wprowadzić nieco porządku do swoich projektów. Można to zrobić nawet z marszu &#8211; po prostu przenieść pliki do podkatalogów (jednak należy pamiętać o tym co może się wydarzać, gdy przez przypadek zdefiniujemy dwa kontrolery o takiej samej nazwie w różnych podkatalogach.)</p>
<p>Niestety w widokach nie udało mi się jeszcze w ten sposób wykorzystać podkatalogów. Dlatego w naszym przykładzie widoki należy umieścić w </p>
<pre>
/views/module_things/
</pre>
<p>Jednak już w ramach tego katalogu można zadbać o większy porządek:<br />
/views/module_things/forms/add_edit_form.ctp<br />
i explicite wywoływać dany widok:</p>
<pre name="code" class="php">
class ModuleThingsController extends AppController {
 //...
 function add(){
   //...
   $this->render("forms/add_edit_form");
 }
}
</pre>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/07/mozesz-podzielic-swoj-kod-na-moduly-w-cakephp/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Własny pingback &#8211; kiedy wysłać?</title>
		<link>http://blog.grzegorzpawlik.com/2010/05/wlasny-pingback-kiedy-wyslac/</link>
		<comments>http://blog.grzegorzpawlik.com/2010/05/wlasny-pingback-kiedy-wyslac/#comments</comments>
		<pubDate>Tue, 25 May 2010 05:30:40 +0000</pubDate>
		<dc:creator>Greg</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[crontab]]></category>
		<category><![CDATA[pingback]]></category>
		<category><![CDATA[trackback]]></category>

		<guid isPermaLink="false">http://blog.grzegorzpawlik.com/?p=984</guid>
		<description><![CDATA[Ktoś, kogo śmiem nazywać &#8220;stałym&#8221; czytelnikiem tego bloga, czyli Kminek zadał mi ciekawe pytanie : mam pewna zagwozdke – stworzylem mala platforme blogowa w Cake – cos a`la WordPress.com. przymierzam sie do implementacji protokolu Pingback. jest kwestia wysylania pingow – &#8230; <a href="http://blog.grzegorzpawlik.com/2010/05/wlasny-pingback-kiedy-wyslac/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Ktoś, kogo śmiem nazywać &#8220;stałym&#8221; czytelnikiem tego bloga, czyli <a href="http://www.kminek.pl/">Kminek</a> zadał mi <a href="http://blog.grzegorzpawlik.com/2010/05/niespodzianka/comment-page-1/#comment-116">ciekawe pytanie</a> :</p>
<blockquote><p>mam pewna zagwozdke – stworzylem mala platforme blogowa w Cake – cos a`la WordPress.com. przymierzam sie do implementacji protokolu Pingback. jest kwestia wysylania pingow – chyba raczej z punktu widzenia wydajnosci nie mozna tego robic przy zapisywaniu posta tylko uzyc taska shellowego, ktory co pewien czas bedzie to robil? jak sadzisz ?</p></blockquote>
<p>Myślę, że tak jak sugerujesz, wysłanie pinga nie jest dobrym pomysłem. Sam pisząc posty czasem zapisuję je kilka razy zanim staną się one publicznie dostępne. Gdyby już przy pierwszym zapisie wysłać ping to byłby on wadliwy &#8211; prowadziłby do nieistniejącej strony.</p>
<p>Z drugiej strony wspomniany przez Ciebie task shellowy (rozumiem, że chodzi na przykład o crona) jest zadaniem dość kłopotliwym. Moim zdaniem komplikuje on architekturę aplikacji. Oczywiście zależy jak dużo do tego taska chcesz upchnąć? Jeśli parsowanie wpisu w poszukiwaniu linków etc. to moim zdaniem jest to zbyt wiele.</p>
<p>Moim pierwszym pomysłem było wysłanie pinga przy <strong>publikacji</strong> wpisu. Jednak gdyby w tekście znalazło się 20 różnych linków i wysyłać pingi przy publikacji &#8211; mogłoby to zbyt długo trwać i być niewygodne dla autora bloga (wyobrażam sobie sytuację, że przy 18 pingu następuje timeout wykonania skryptu i nie wiadomo czy post się opublikował i czy pingi się udały?).</p>
<p>Dlatego wybrałbym rozwiązanie kombinowane. Tzn. podczas publikowania wpisu (i update&#8217;u już opublikowanego) parsowałbym sobie tekst i znalezione linki umieszczałbym w tabeli pingbacks, która mogłaby wyglądać mniej więcej tak:</p>
<pre>
|----------------------------------------
| pingbacks
|----------------------------------------
| id: INT
| post_id: INT (albo permalink jeśli wolisz)
| uri : varchar (pingowany link)
| sent: tinyint(1) DEFAULT 0
|----------------------------------------
</pre>
<p>oczywiście przed zapisem sprawdzamy, czy już go nie ma w bazie ;). Przy takim rozwiązaniu świetnie powinien sprawdzić się Post::afterSave(). Wystarczy sprawdzić, czy zapisany model został z opublikowany i odpalić metodę Post::findAndSavePingbacks($postId).</p>
<p>Mam problem z tym, gdzie umieścić logikę wysyłania pingów. Myślę, że to też należałoby do warstwy Modelu (Pingback::send($limit)). Metoda taka pobrałaby kilka elementów z tabeli `pingbacks`, które nie zostały jeszcze wysłane i po udanym wysłaniu aktualizował pole `sent` (ustawiając 1).</p>
<p>Na koniec w kontrolerze zdefiniowałbym akcję, która miałaby za zadanie jedynie odpalić metodę Pingbacks::send()</p>
<pre name="code" class="php">
class PingbacksController extends AppController{
  function send($limit=10){
     $this->autoRender = false;
     $this->Pingback->send($limit);
  }
}
</pre>
<p>I teraz jeśli mamy dostęp do crona to możemy zdefiniować na przykład akcję wget http://example.com/pingbacks/send<br />
Jeśli nie mamy dostępu do takich luksusów &#8211; możemy wzbudzać tą akcję np. w AppController::afterFilter().</p>
<p>Co myślicie o tym rozwiązaniu?</p>
<!-- PHP 5.x -->]]></content:encoded>
			<wfw:commentRss>http://blog.grzegorzpawlik.com/2010/05/wlasny-pingback-kiedy-wyslac/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

