<?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>xlab &#187; Programowanie PHP</title>
	<atom:link href="http://xlab.pl/programowanie-php/feed/" rel="self" type="application/rss+xml" />
	<link>http://xlab.pl</link>
	<description>XSolve laboratory - dzielimy się tym co wiemy...</description>
	<lastBuildDate>Mon, 30 Aug 2010 07:34:03 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>pl</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Wywoływanie procedur składowanych MSSQL z poziomu PHP</title>
		<link>http://xlab.pl/2010/07/wywolywanie-procedur-skladowanych-mssql-z-poziomu-php/</link>
		<comments>http://xlab.pl/2010/07/wywolywanie-procedur-skladowanych-mssql-z-poziomu-php/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 07:48:17 +0000</pubDate>
		<dc:creator>Wojtek Sznapka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://xlab.pl/?p=1492</guid>
		<description><![CDATA[Pracując z bazą MS SQL Server z poziomu aplikacji PHP prędzej czy później zajdzie potrzeba używacia procedur składowanych. Aby wywołać taką procedurę z poziomu PHP, należy posłużyć się biblioteką PDO, tak jak pokazano to poniżej:
    $conn = new PDO('dblib:host=127.0.0.1;dbname=database', 'user', 'pass');
    $stmt = $conn-&#62;prepare("EXEC sp_SaveMagicNumber ?");
    [...]]]></description>
			<content:encoded><![CDATA[<p>Pracując z bazą MS SQL Server z poziomu aplikacji PHP prędzej czy później zajdzie potrzeba używacia procedur składowanych. Aby wywołać taką procedurę z poziomu PHP, należy posłużyć się biblioteką PDO, tak jak pokazano to poniżej:<span id="more-1492"></span></p>
<pre class="php" name="code">    $conn = new PDO('dblib:host=127.0.0.1;dbname=database', 'user', 'pass');
    $stmt = $conn-&gt;prepare("EXEC sp_SaveMagicNumber ?");
    $stmt-&gt;bindValue(1, 5, PDO::PARAM_INT);
    $stmt-&gt;execute();</pre>
<p>Kod z powyższego listingu wywoła procedurę sp_SaveMagicNumber z parameterem 5. Jeśli używamy symfony wraz z Doctrine, możemy (a nawet powinniśmy) zamienić pierwszą linijkę na:</p>
<pre class="php" name="code">    $conn = Doctrine_Manager::getInstance()-&gt;getConnection();</pre>
<p>Można też przekazywać parametry jako &#8220;placeholdery&#8221;, jak na listingu poniżej:</p>
<pre class="php" name="code">    $conn = Doctrine_Manager::getInstance()-&gt;getConnection();
    $stmt = $conn-&gt;prepare("EXEC sp_IntroduceYourself :name, :surname");
    $stmt-&gt;bindValue(':surname', 'Sznapka', PDO::PARAM_STR);
    $stmt-&gt;bindValue(':name', 'Wojtek', PDO::PARAM_STR);
    $stmt-&gt;execute();</pre>
<p>Ostatnim punktem programu są parametry typu OUTPUT, czyli zwracające wartości z procedury składowanej (podobnie jak parametry funkcji PHP przekazywanych przez referencję). Niestety biblioteka PDO posiada błąd, który uniemożliwia poprawne działanie metod z takimi parametrami. W idealnym przypadku, jeśli parametr w MSSQL jest zdefiniowany jako OUTPUT powinniśmy go dostać następujący sposób:</p>
<pre class="php" name="code">    $conn = Doctrine_Manager::getInstance()-&gt;getConnection();
    $stmt = $conn-&gt;prepare("EXEC sp_GetCountry :city, :country OUTPUT");
    $stmt-&gt;bindValue(':city', 'Gliwice', PDO::PARAM_STR);
    $country = NULL;
    $stmt-&gt;bindParam(':country', $country, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT);
    $stmt-&gt;execute();
    printf("Dla miasta %s procedura zwraca kraj %s", $city, country);</pre>
<p>Niestety pomimo wielu prób, nie udało się obejść tego błędu i na chwilę obecną wygląda na to, że nie ma sposobu na pozyskiwanie parametrów typu OUTPUT z procedur składowanych MSSQL. Podobne problemy można spotkać na bugtrackerze PHP: http://bugs.php.net/bug.php?id=50555</p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2010/07/wywolywanie-procedur-skladowanych-mssql-z-poziomu-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MongoDB &#8211; baza danych zorientowana dokumentowo. Czy ruch NoSQL ma sens?</title>
		<link>http://xlab.pl/2010/05/mongodb-baza-danych-zorientowana-dokumentowo-czy-ruch-nosql-ma-sens/</link>
		<comments>http://xlab.pl/2010/05/mongodb-baza-danych-zorientowana-dokumentowo-czy-ruch-nosql-ma-sens/#comments</comments>
		<pubDate>Sat, 29 May 2010 10:53:55 +0000</pubDate>
		<dc:creator>Wojtek Sznapka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[nosql]]></category>
		<category><![CDATA[piątek z xsolve]]></category>

		<guid isPermaLink="false">http://xlab.pl/?p=1179</guid>
		<description><![CDATA[Wczoraj miałem przyjemność poprowadzić &#8220;Piątek z XSolve&#8221; prezentując temat &#8220;MongoDB &#8211; baza danych zorientowana dokumentowo. Czy ruch NoSQL ma sens?&#8221;. MongoDB jest bazą nowej generacji, która jest zorientowana na przechowywanie dokumentów JSON, o dowolnej strukturze. Nie mamy w tym przypadku problemów z schamatami bazy, czy migracjami na nowsze wersje jej struktur. MongoDB, jak i inne [...]]]></description>
			<content:encoded><![CDATA[<p>Wczoraj miałem przyjemność poprowadzić &#8220;Piątek z XSolve&#8221; prezentując temat &#8220;MongoDB &#8211; baza danych zorientowana dokumentowo. Czy ruch NoSQL ma sens?&#8221;. MongoDB jest bazą nowej generacji, która jest zorientowana na przechowywanie dokumentów JSON, o dowolnej strukturze. Nie mamy w tym przypadku problemów z schamatami bazy, czy migracjami na nowsze wersje jej struktur. MongoDB, jak i inne rozwiązania nurtu NoSQL, jest stworzona z myślą o pracy w dużych obciążeniach oraz posiada wbudowane mechanizmy skalowania i replikacji. Baza ta posiada bardzo przyjemne API dostępu do danych, które jest podobne do niektórych bibliotek ORM.<br />
Podczas prezentacji i dyskusji jaka się rozwinęła, staraliśmy się odpowiedzieć na pytanie &#8220;Czy ruch NoSQL ma sens?&#8221;. Odpowiedź, zarówno moja, jak i kolegów była jednoznaczna: tak, ale tylko gdy NoSQL, będziemy rozwijać jak Not only SQL. Bazy nierelacyjne mogą być doskonałym uzupełnieniem dla baz SQL, ale raczej mało prawdopodobne, żeby w całości zastąpiły bazy relacyjne w złożonych aplikacjach i systemach.<br />
Zapraszam do obejrzenia slajdów z prezentacji (podczas prelekcji był jeszcze pokaz &#8220;na żywo&#8221; możliwości bazy, którego w slajdach niestety nie ma).</p>
<div id="__ss_4348786" style="width: 600px;"><object id="__sse4348786" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="600" height="501" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><param name="src" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mongodb-nosql-xsolve-100529041429-phpapp02&amp;stripped_title=mongodb-baza-danych-zorientowana-dokumentowo-czy-ruch-nosql-ma-sens" /><param name="name" value="__sse4348786" /><param name="allowfullscreen" value="true" /><embed id="__sse4348786" type="application/x-shockwave-flash" width="600" height="501" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mongodb-nosql-xsolve-100529041429-phpapp02&amp;stripped_title=mongodb-baza-danych-zorientowana-dokumentowo-czy-ruch-nosql-ma-sens" name="__sse4348786" allowscriptaccess="always" allowfullscreen="true"></embed></object>
</div>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2010/05/mongodb-baza-danych-zorientowana-dokumentowo-czy-ruch-nosql-ma-sens/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Symfony sfWidgetFormSelect z nieaktywnymi opcjami</title>
		<link>http://xlab.pl/2010/05/symfony-sfwidgetformselect-z-nieaktywnymi-opcjami/</link>
		<comments>http://xlab.pl/2010/05/symfony-sfwidgetformselect-z-nieaktywnymi-opcjami/#comments</comments>
		<pubDate>Tue, 25 May 2010 08:06:33 +0000</pubDate>
		<dc:creator>Wojtek Sznapka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[sfWidgetFormSelect]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://xlab.pl/?p=1157</guid>
		<description><![CDATA[sfWidgetFormSelect nie daje możliwości wyświetlania nieaktywnych opcji. Jest to rzadko wykorzystywana właściwość elementu select, ale czasami może się na prawdę przydać :-) Pomimo to, możemy uzyskać tą funkcjonalność poprzez stworzenie własnego widgetu, który dziedziczy z  sfWidgetFormSelect. Kod klasy znajduję się poniżej. W tym przypadku nieaktywne opcje, to te które mają ujemne value. Można oczywiście [...]]]></description>
			<content:encoded><![CDATA[<p><strong>sfWidgetFormSelect</strong> nie daje możliwości wyświetlania nieaktywnych opcji. Jest to rzadko wykorzystywana właściwość elementu select, ale czasami może się na prawdę przydać :-) Pomimo to, możemy uzyskać tą funkcjonalność poprzez stworzenie własnego widgetu, który dziedziczy z  <strong>sfWidgetFormSelect</strong>. Kod klasy znajduję się poniżej. W tym przypadku nieaktywne opcje, to te które mają ujemne <strong>value</strong>. Można oczywiście ustawiać inne opcje na nieaktywne, wystaczy odpowiednio zmodyfikować metodę <strong>disableCondition</strong>.<span id="more-1157"></span></p>
<pre class="php" name="code">
  class myWidgetFormSelect extends sfWidgetFormSelect
  {
    public function renderContentTag($tag, $content = null, $attributes = array())
    {
      if ($tag == 'option' &amp;&amp; isset($attributes['value'])
        &amp;&amp; $this-&gt;disableCondition($attributes['value'])
      ) {
        $attributes['disabled'] = 'disabled';
      }
      return parent::renderContentTag($tag, $content, $attributes);
    }   

    protected function disableCondition($value)
    {
      return (int)$value &lt; 0;
    }
  }
</pre>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2010/05/symfony-sfwidgetformselect-z-nieaktywnymi-opcjami/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Wykrywanie przeglądarek na urządzenia mobilne</title>
		<link>http://xlab.pl/2010/05/wykrywanie-przegladarek-na-urzadzenia-mobilne/</link>
		<comments>http://xlab.pl/2010/05/wykrywanie-przegladarek-na-urzadzenia-mobilne/#comments</comments>
		<pubDate>Tue, 18 May 2010 07:09:21 +0000</pubDate>
		<dc:creator>Wojtek Sznapka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[przeglądarki mobilne]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://xlab.pl/?p=1096</guid>
		<description><![CDATA[Często bywa  tak, że chcemy utworzyć mobilną wersję naszego serwisu. Cechuje się ona przeważnie skromniejszą szatą graficzną oraz umiarem w wykorzystaniu JavaScriptu. Aby wykryć przeglądarkę mobilną (używaną w telefonach komórkowych, smartphone lub palmtopach) wystarczy użyć poniższej klasy. W zaprezentowanym rozwiązaniu detekcja przeglądarki oparta jest o nagłówek HTTP_USER_AGENT lub o dodatkowe nagłówki wysyłane przez niektóre przeglądarki.
class [...]]]></description>
			<content:encoded><![CDATA[<p>Często bywa  tak, że chcemy utworzyć mobilną wersję naszego serwisu. Cechuje się ona przeważnie skromniejszą szatą graficzną oraz umiarem w wykorzystaniu JavaScriptu. Aby wykryć przeglądarkę mobilną (używaną w telefonach komórkowych, smartphone lub palmtopach) wystarczy użyć poniższej klasy. W zaprezentowanym rozwiązaniu detekcja przeglądarki oparta jest o nagłówek HTTP_USER_AGENT lub o dodatkowe nagłówki wysyłane przez niektóre przeglądarki.<span id="more-1096"></span></p>
<pre class="php" name="code">class MobileBrowserDetector
{
  private static $mobileAgentPrefixes = array(
    'w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac',
    'blaz','brew','cell','cldc','cmd-','dang','doco','eric','hipt','inno',
    'ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-',
    'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-',
    'newt','noki','oper','palm','pana','pant','phil','play','port','prox',
    'qwap','sage','sams','sany','sch-','sec-','send','seri','sgh-','shar',
    'sie-','siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-',
    'tosh','tsm-','upg1','upsi','vk-v','voda','wap-','wapa','wapi','wapp',
    'wapr','webc','winw','winw','xda','xda-'
  );

  private static $mobileAgentParts = array(
    'up.browser', 'up.link', 'mmp', 'symbian', 'mobile safari',
    'opera mobi', 'android', 'smartphone', 'midp', 'wap', 'phone'
  );

  public static function isMobile($userAgent = '')
  {
    if (!strlen($userAgent)) {
      $userAgent = $_SERVER['HTTP_USER_AGENT'];
    }
    $isMobile = false;
    $isMobile |= in_array(strtolower(substr($userAgent, 0, 4)), self::$mobileAgentPrefixes);
    $isMobile |= preg_match(sprintf('#(%s)#i', implode('|', self::$mobileAgentParts)), $userAgent);
    $isMobile |= strpos(strtolower($_SERVER['HTTP_ACCEPT']),'application/vnd.wap.xhtml+xml') !== false;
    $isMobile |= isset($_SERVER['HTTP_PROFILE']);
    $isMobile |= isset($_SERVER['HTTP_X_WAP_PROFILE']);
    $isMobile |= isset($_SERVER['ALL_HTTP']) &amp;&amp; strpos(strtolower($_SERVER['ALL_HTTP']),'OperaMini') !== false;
    return (bool)$isMobile;
  }
}</pre>
<p>Możemy łatwo użyć tej klasy na przykład w projekcie symfony, podmieniając główny layout na layout mobilny (apps/APLIKACJA/templates/layout_mobile.php) w filtrze, w poniższy sposób.<br />
Zawartość apps/APLIKACJA/config/filters.yml</p>
<pre class="php" name="code">rendering: ~
security:
  class: sfGuardBasicSecurityFilter
mobile:
  class: MobileFilter
cache:     ~
common:    ~
execution: ~</pre>
<p>Klasa filtra lib/MobileFilter.class.php:</p>
<pre class="php" name="code">class MobileFilter extends sfFilter
{
  public function execute($filterChain)
  {
    if (MobileBrowserDetector::isMobile()) {
      $this-&gt;getContext()-&gt;getActionStack()-&gt;getFirstEntry()-&gt;getActionInstance()-&gt;setLayout('layout_mobile');
    }
    $filterChain-&gt;execute();
  }
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2010/05/wykrywanie-przegladarek-na-urzadzenia-mobilne/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Piekło die();</title>
		<link>http://xlab.pl/2009/11/pieklo-die/</link>
		<comments>http://xlab.pl/2009/11/pieklo-die/#comments</comments>
		<pubDate>Sat, 28 Nov 2009 23:40:50 +0000</pubDate>
		<dc:creator>Krzysztof Wawoczny</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[debugowanie]]></category>
		<category><![CDATA[FirePHP]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=676</guid>
		<description><![CDATA[
Natchnęło mnie dziś do pomyślenia jak uchronić się przed niebezpieczeństwami związanymi z stosowaniem die()  w kodzie&#8230;tak wiem że była o tym parę razy mowa, że to zło w najczystszej postaci,  że najlepiej korzystać z xdebug, a najlepiej nigdy nie korzystać z die() w kodzie.
Wszystko ma jednak swoje wady i zalety – o ile w [...]]]></description>
			<content:encoded><![CDATA[<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Natchnęło mnie dziś do pomyślenia jak uchronić się przed niebezpieczeństwami związanymi z stosowaniem die()  w kodzie&#8230;tak wiem że była o tym parę razy mowa, że to zło w najczystszej postaci,  że najlepiej korzystać z xdebug, a najlepiej nigdy nie korzystać z die() w kodzie.</p>
<p style="margin-bottom: 0cm">Wszystko ma jednak swoje wady i zalety – o ile w pracy xdebuga mogę sobie skonfigurować pod netbeansa to w domu nie korzystam z tego IDE (połączenie przez OpenVPN i dość częste skanowanie projektu jest nader uciążliwe). Dlatego zaczerpnąłem nieco mocy googla i po kilku testach przedstawiam: <strong> FirePHP</strong>.</p>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm"><strong>FirePHP</strong> to całkiem zgrabny logger dla PHP wykorzystujący <strong>Firebuga</strong> – ku mojemu zdziwieniu jest to całkiem niezły plugin, do jego działania wystarczy mieć zainstalowaną wtyczkę Firebug z włączoną zakładką sieć i konsola. Instalując FirePHP trzeba jeszcze pamiętać o zaciągnięciu biblioteki FirePHPCore, którą rozpakowujemy do katalogu roota (w moim przypadku to katalog web w symfony).  FirePHP pokaże się jako niebieski robal (widoczny na poniższym zrzucie).</p>
<p style="margin-bottom: 0cm">Mając to wszystko jesteśmy niemal gotowi by bezpiecznie debugować skrypty. Do skryptu dołączamy:</p>
<pre class="php">require_once('FirePHPCore/FirePHP.class.php');</pre>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Teraz już poprzez wywołanie w postaci:</p>
<p style="margin-bottom: 0cm">
<pre class="php">$firephp = FirePHP::getInstance(true);
$firephp-&gt;log($var, 'nieobowiazkowy opis dla loga');</pre>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Zobaczymy w zakładce konsola Firebuga wartość zmiennej $var poprzedzoną opisem podanym jako drugi parametr funkcji log.<br />
Dla własnej wygody napisałem krótką funkcję ustawiającą podstawowe parametry debugowania:</p>
<p style="margin-bottom: 0cm">
<pre class="php">/**
* Function is used for debuging this scrypt, in prod environment change
* $firephp-&gt;setEnabled(true); to false.
*
* @param object $var
* @param string $desc[optional]
*/
public function keep($var, $desc = null)
{
  $options = array(
  'maxObjectDepth' =&gt; 10,
  'maxArrayDepth' =&gt; 20,
  'useNativeJsonEncode' =&gt; true,
  'includeLineNumbers' =&gt; true);

  $firephp = FirePHP::getInstance(true);
  $firephp-&gt;setEnabled(true);
  $firephp-&gt;setOptions($options);
  $firephp-&gt;log($var, $desc);
}</pre>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Ważne aby pamiętać iż dla własnego bezpieczeństwa należy w środowisku produkcyjnym ustawić setEnabled na false.</p>
<p style="margin-bottom: 0cm">Posłużę się Jobeetem, zatem moja funkcja index wygląda tak:</p>
<p style="margin-bottom: 0cm">
<pre class="php">public function executeIndex(sfWebRequest $request)
{
  $vars = 0;
  $this-&gt;categories = Doctrine::getTable('JobeetCategory')-&gt;getWithJobs();
  $this-&gt;keep($vars, "at line: ".__LINE__);
  $vars = 12;
  $this-&gt;keep($vars, "at line: ".__LINE__);
}</pre>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">drugi parametr funkcji keep służy mi jako informacja pozwalająca szybko odnaleźć miejsce z wywołaniem logera. Wynik logowania prezentuje się następująco:</p>
<p style="margin-bottom: 0cm;text-align: center"><a rel="attachment wp-att-677" href="http://www.xlab.pl/2009/11/pieklo-die/attachment/1/"><img class="size-medium wp-image-677 aligncenter" src="http://www.xlab.pl/wp-content/uploads/2009/11/1-300x172.jpg" alt="Debugowanie z użyciem FirePHP" width="300" height="172" /></a></p>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Ciekawsze wyniki można uzyskać deklarując przykładową tablicę z danymi – najeżdżając kursorem myszy na wynik debugowania tablicy ukaże się okienko jak na poniższym zrzucie – możemy tutaj nieco dokładniej przyjrzeć się zwróconym wartościom.</p>
<p style="margin-bottom: 0cm;text-align: center"><a rel="attachment wp-att-688" href="http://www.xlab.pl/2009/11/pieklo-die/attachment/2/"><img class="size-medium wp-image-688 aligncenter" src="http://www.xlab.pl/wp-content/uploads/2009/11/2-300x172.jpg" alt="Debugowanie z FirePHP 2" width="300" height="172" /></a></p>
<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p style="margin-bottom: 0cm">Na koniec chciałbym zauważyć, że odświeżenie strony nie zawsze powoduje odświeżenie wyników debugowania, prowadzi to wręcz do ich utraty – należy wówczas kliknąć w Firebugu opcję Off a następnie On dzięki czemu zobaczymy aktualne dane.</p>
<p style="margin-bottom: 0cm">Zachęcam do korzystania z FirePHP i testowania jego pozostałych funkcjonalności.</p>
<p style="margin-bottom: 0cm"><a href="http://www.firephp.org/HQ/Use.htm">http://www.firephp.org/HQ/Use.htm</a></p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/11/pieklo-die/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Dlaczego kocham PHP!</title>
		<link>http://xlab.pl/2009/11/dlaczego-kocham-php/</link>
		<comments>http://xlab.pl/2009/11/dlaczego-kocham-php/#comments</comments>
		<pubDate>Thu, 19 Nov 2009 17:44:39 +0000</pubDate>
		<dc:creator>Marcin Dryka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[błąd]]></category>
		<category><![CDATA[fail]]></category>
		<category><![CDATA[love]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=636</guid>
		<description><![CDATA[Każdego programistę w życiu spotykają chwile doszukiwania się logiki lub chwile zwątpienia w zasady matematyki. Język PHP w znaczącym stopniu ułatwia zaistnienie takiej sytuacji.
Wyobraźmy sobie, potrzebę napisania klasy umożliwiającej stronicowanie wyników. Metoda calculate służy do policzenia dolnego zakresu wyników (strona 5, wyniki 51-60). Całość wygląda następująco:

class Pager
{
  private
    $page = 1,
 [...]]]></description>
			<content:encoded><![CDATA[<p>Każdego programistę w życiu spotykają chwile doszukiwania się logiki lub chwile zwątpienia w zasady matematyki. Język PHP w znaczącym stopniu ułatwia zaistnienie takiej sytuacji.<br />
Wyobraźmy sobie, potrzebę napisania klasy umożliwiającej stronicowanie wyników. Metoda <em>calculate</em> służy do policzenia dolnego zakresu wyników (strona 5, wyniki <strong>51</strong>-60). Całość wygląda następująco:</p>
<pre name="code" class="php">
class Pager
{
  private
    $page = 1,
    $per_page = 10;

  public function calculate()
  {
    return $this-&gt;page * $this-&gt;per_page - $this-per_page + 1;
  }
}</pre>
<p>Jedna mała pomyłka. Zamiast</p>
<pre name="code" class="php">$this-&gt;per_page</pre>
<p>do kodu wkradło się:</p>
<pre name="code" class="php">$this-per_page</pre>
<p>Co to zmienia? Na pierwszy rzut oka parser PHP powinien poinformować o błędzie&#8230; jednak linia kodu zawierająca błąd interpretowana jest następująco:</p>
<pre name="code" class="php">
$this-&gt;page // 1</pre>
<pre name="code" class="php">
$this-&gt;per_page // 10</pre>
<pre name="code" class="php">
$this  // rzutowane na int to 1, bo obiekt istnieje</pre>
<pre name="code" class="php">
per_page // jako stała niezdefiniowana to 0</pre>
<p>Wykonywane działanie matematyczne wygląda zatem następująco:</p>
<pre name="code" class="php">
1 * 10 - 1 - 0 + 1</pre>
<p>Wynik działania to o dziwo 10&#8230; Dlatego kocham PHP!</p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/11/dlaczego-kocham-php/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Demotywacja demotywatorów II</title>
		<link>http://xlab.pl/2009/10/demotywacja-demotywatorow-ii/</link>
		<comments>http://xlab.pl/2009/10/demotywacja-demotywatorow-ii/#comments</comments>
		<pubDate>Mon, 19 Oct 2009 16:29:16 +0000</pubDate>
		<dc:creator>Marcin Dryka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[demotywatory]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[rss]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=622</guid>
		<description><![CDATA[&#171; Poprzednia część. 
Już po krótkim używaniu skryptu, nie trudno się zorientować, że działa on zbyt wolno. Aby przyspieszyć działanie można użyć zachowywania informacji o już ściągniętych demotywatorach. Zatem tekst o tym jak dodać cache:
Klasa Feed. Różni się nieznacznie  od oryginału &#8211; należy dodać cztery nowe metody:

  public function addToCache($page_url, $image_url)
  {
  [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.xlab.pl/2009/10/demotywacja-demotywatorow/">&laquo; Poprzednia część. </a><br />
Już po krótkim używaniu skryptu, nie trudno się zorientować, że działa on zbyt wolno. Aby przyspieszyć działanie można użyć zachowywania informacji o już ściągniętych demotywatorach. Zatem tekst o tym jak dodać cache:<br />
Klasa Feed. Różni się nieznacznie  od oryginału &#8211; należy dodać cztery nowe metody:</p>
<pre name="code" class="php">
  public function addToCache($page_url, $image_url)
  {
    Debug::Log("Adding to cache");
    $this->cache[$page_url] = $image_url;
  }

  public function loadCache()
  {
    Debug::Log('Reading cache');
    if (file_exists("cache.csv"))
    {
      $fh = fopen("cache.csv", "r");
      while (($data = fgetcsv($fh, 1000, ",")) !== FALSE)
      {
        $this->cache[$data[0]] = $data[1];
      }
      fclose($fh);
      return $this->cache;
    }
  }

  public function saveCache()
  {
    Debug::Log("Saving cache");
    $fh = fopen('cache.csv', 'w');
    foreach ($this->cache as $page_url=>$image_url)
    {
      fputcsv($fh, array($page_url, $image_url));
    }
    fclose($fh);
  }

  public function isCached($page_url)
  {
    Debug::Log("Checking cache");
    if (empty($this->cache))
    {
      $this->loadCache();
    }
    if (isset($this->cache[$page_url]))
    {
      Debug::Log("Found in cache");
      return $this->cache[$page_url];
    }
    Debug::Log("Not found in cache");
    return false;
  }</pre>
<p>oraz użyć ich w odpowiedni sposób. Cały plik wygląda następująco:</p>
<pre name="code" class="php">
<?php
require_once 'Remote.php';        //Read remote files
require_once 'FeedException.php'; //Implementation of Feed Exception
require_once 'Debug.php';         //Debug message

/*
 * Class to convert demotywatory.pl rss
 *
 * @author drymek
 *
 */
class Feed
{
  private
    $url,             //rss.xml file url
    $orginalContent,  //rss.xml file content
    $transformedDom,  //rss.xml file DOMDocument
    $cache = array(); //Keep cache 

  /*
   * Set feed url
   */
  public function setUrl($url)
  {
    $this->url = $url;
  }

  /*
   *  Read remote rss file
   */
  public function getOrginalContent()
  {
    if (is_null($this->url))
    {
      throw new FeedException("You have to set url first!");
    }

    $this->orginalContent = Remote::get_remote_file_content($this->url);
    return $this->orginalContent;
  }

  /*
   * Create a DOMDocument from rss.xml file
   */
  public function getDomOrginalContent()
  {
    if (false === $this->orginalContent || is_null($this->orginalContent))
    {
      throw new FeedException("Can not download rss file");
    }

    $domDocument = new DOMDocument();
    $domDocument->loadXML($this->orginalContent);
    return $domDocument;
  }

  public function addToCache($page_url, $image_url)
  {
    Debug::Log("Adding to cache");
    $this->cache[$page_url] = $image_url;
  }

  public function loadCache()
  {
    Debug::Log('Reading cache');
    if (file_exists("cache.csv"))
    {
      $fh = fopen("cache.csv", "r");
      while (($data = fgetcsv($fh, 1000, ",")) !== FALSE)
      {
        $this->cache[$data[0]] = $data[1];
      }
      fclose($fh);
      return $this->cache;
    }
  }

  public function saveCache()
  {
    Debug::Log("Saving cache");
    $fh = fopen('cache.csv', 'w');
    foreach ($this->cache as $page_url=>$image_url)
    {
      fputcsv($fh, array($page_url, $image_url));
    }
    fclose($fh);
  }

  public function isCached($page_url)
  {
    Debug::Log("Checking cache");
    if (empty($this->cache))
    {
      $this->loadCache();
    }
    if (isset($this->cache[$page_url]))
    {
      Debug::Log("Found in cache");
      return $this->cache[$page_url];
    }
    Debug::Log("Not found in cache");
    return false;
  }
  /*
   * Get image URL from remote webpage
   */
  public function getImage($item)
  {
    if ('link' == $item->nodeName)
    {
      if ($cache = $this->isCached($item->nodeValue))
      {
        return $cache;
      }
      $htmlContent = Remote::get_remote_file_content($item->nodeValue);
      if (false !== $htmlContent)
      {
        //Search image tag
        $pattern = "/bigurl=\".*\"/";
        preg_match($pattern, $htmlContent, $urls);

        //Finally url to the image
        $url = substr($urls[0], 8, -1);
        $this->addToCache($item->nodeValue, $url);
        return $url;
      }
      return 'http://demotywatory.pl/res/img/demotywatory-logo.gif';
    }
  }

  /*
   * Set new description for current item
   */
  public function setImage($item)
  {
    //Find element link
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('link' == $rssDomItemChild->nodeName)
      {
        //Download demot page
        $url = $this->getImage($rssDomItemChild);
        break;
      }
    }

    //Find element description
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('description' == $rssDomItemChild->nodeName)
      {
        $rssDomItemChild->nodeValue = '&lt;img src="'.$url.'"&gt;';
        break;
      }
    }
    Debug::Log('Found url: ['.$url.']');
  }

  /*
   * Convert rss.xml to rss.xml with images in description
   */
  public function transform()
  {
    //Get DOMDocument
    $this->transformedDom = $this->getDomOrginalContent();
    //Find all items
    $rssItems = $this->transformedDom->getElementsByTagName('item');

    //Foreach feed
    Debug::Log('Starting retriving feeds');

    foreach ($rssItems as $rssItem)
    {
      //Set image in description for node
      $this->setImage($rssItem);
    }
    Debug::Log('Finished retriving feeds!');
  }

  /*
   * Get transformed XML
   */
  public function getResults()
  {
    return $this->transformedDom->saveXml();
  }

  /*
   * Save results to file (xml)
   */
  public function save($filename)
  {
    Debug::Log('Saving file ('.$filename.')');
    $fh = fopen($filename, 'w');
    if (!$fh)
    {
      throw new FeedException('Can not open file ('.$filename.') to write!');
    }
    fwrite($fh, $this->getResults());
    fclose($fh);
  }
}</pre>
<p>Należy jeszcze zmienić skrypt wywołujący, czyli index.php:</p>
<pre name="code" class="php">
<?php
define('DEBUG', true);    //Set debug on
require_once 'Feed.php';  //Include Feed class

//Create new Feed instance
$feed = new Feed();
//Set feed url
$feed->setUrl('http://demotywatory.pl/rss.xml');
//Get rss file content
$feed->getOrginalContent();
//Convert to new format
$feed->transform();
//Save to file
$feed->save('result.xml');
$feed->saveCache();</pre>
<p>I to wszystko. Teraz skrypt pobierze informacje jedynie o nowych rssach. Informacje które już posiada w razie potrzeby zostaną użyte ponownie.<br />
Efekt działania można zobaczyć na <a href="http://drymek.hostei.com/">http://drymek.hostei.com/</a>.<br />
Gotowy do użycia plik rss znajduje się tutaj: <a href="http://feeds.feedburner.com/demotywatorator?format=xml">http://feeds.feedburner.com/demotywatorator?format=xml</a></p>
<p>Zestaw wszystkich plików do ściagnięcia: <a href="http://www.xlab.pl/wp-content/uploads/2009/10/Demotywatorator2.zip">Demotywatorator2</a></p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/10/demotywacja-demotywatorow-ii/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Demotywacja demotywatorów</title>
		<link>http://xlab.pl/2009/10/demotywacja-demotywatorow/</link>
		<comments>http://xlab.pl/2009/10/demotywacja-demotywatorow/#comments</comments>
		<pubDate>Fri, 16 Oct 2009 18:16:14 +0000</pubDate>
		<dc:creator>Marcin Dryka</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[demotywatory]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[rss]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=601</guid>
		<description><![CDATA[Demotywatory.pl, znany polski serwis humorystyczny. Jednak serwis ma jedną niedogodność &#8211; trzeba wchodzić na strone internetową aby zobaczyć obrazki. Podobnie w przypadku kanału RSS &#8211; nie ma możliwości obejrzenia obrazka w treści wiadomości. Jest problem jest i fix:
5 plików rozwiązujących problem:

Remote.php
FeedException.php
Debug.php
Feed.php
index.php

Pierwszy z nich, rozbudowany aż nadto co powinien być, jednak faza testowania wymagała porównania prędkości [...]]]></description>
			<content:encoded><![CDATA[<p>Demotywatory.pl, znany polski serwis humorystyczny. Jednak serwis ma jedną niedogodność &#8211; trzeba wchodzić na strone internetową aby zobaczyć obrazki. Podobnie w przypadku kanału RSS &#8211; nie ma możliwości obejrzenia obrazka w treści wiadomości. Jest problem jest i fix:<br />
5 plików rozwiązujących problem:</p>
<ol>
<li>Remote.php</li>
<li>FeedException.php</li>
<li>Debug.php</li>
<li>Feed.php</li>
<li>index.php</li>
</ol>
<p>Pierwszy z nich, rozbudowany aż nadto co powinien być, jednak faza testowania wymagała porównania prędkości działania kilku rozwiązań. Curl został domyślnym, ze względu na jego szybkość i niezawodność.</p>
<pre class="php" name="code">
&lt;?php

/*
 * Read remote file with some methods
 *
 * @author drymek
 *
 */
class Remote
{
  /*
   * Read file with default method
   */
  public static function get_remote_file_content($url)
  {
    return self::get_with_curl($url);
  }

  /*
   * Curl - default method for Remote
   */
  public function get_with_curl($url)
  {
    // create a new cURL resource
    $ch = curl_init();

    // set URL and other appropriate options
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // grab URL and pass it to the browser
    $content = curl_exec($ch);

    // close cURL resource, and free up system resources
    curl_close($ch);
    return $content;
  }

  /*
   * file_get_contents use buildin PHP function
   */
  public function get_with_file_get_contents($url)
  {
    return file_get_contents($url);
  }

  /*
   * fopen and fgets functions to read file
   */
  public function get_with_fopen($url)
  {
    $fh = fopen($url, 'rb');
    if (!$fh)
    {
      return '';
    }

    $content = '';
    while (!feof($fh))
    {
      $content .= fgets($fh, 1024);
    }
    fclose($fh);
    return $content;
  }
}</pre>
<p>Drugi z plików zapewnia jedynie możliwość wyrzucania wyjątków z klasy Feed.</p>
<pre class="php" name="code">
&lt;?php
/*
 * Feed Exception class
 *
 * @author drymek
 *
 */
class FeedException extends Exception
{
}</pre>
<p>Klasa Debug umożliwia wypisywanie informacji kontrolnych podczas pisania kodu:</p>
<pre class="php" name="code">
&lt;?php
/*
 * Simple debug class
 *
 * @author drymek
 *
 */
class Debug
{
  /*
   * Print debug message on default output
   */
  public static function Log($message)
  {
    //Check debug mode
    if (DEBUG)
    {
      echo $message."\n";
    }
  }
}</pre>
<p>Klasa Feed! Czyli mięso właściwe. To co pobiera wszystkie informacje, przetwarza je i zapisuje:</p>
<pre class="php" name="code">
&lt;?php
require_once 'Remote.php';        //Read remote files
require_once 'FeedException.php'; //Implementation of Feed Exception
require_once 'Debug.php';         //Debug message

/*
 * Class to convert demotywatory.pl rss
 *
 * @author drymek
 *
 */
class Feed
{
  private
    $url,             //rss.xml file url
    $orginalContent,  //rss.xml file content
    $transformedDom;  //rss.xml file DOMDocument

  /*
   * Set feed url
   */
  public function setUrl($url)
  {
    $this-&gt;url = $url;
  }

  /*
   *  Read remote rss file
   */
  public function getOrginalContent()
  {
    if (is_null($this-&gt;url))
    {
      throw new FeedException("You have to set url first!");
    }

    $this-&gt;orginalContent = Remote::get_remote_file_content($this-&gt;url);
    return $this-&gt;orginalContent;
  }

  /*
   * Create a DOMDocument from rss.xml file
   */
  public function getDomOrginalContent()
  {
    if (false === $this-&gt;orginalContent || is_null($this-&gt;orginalContent))
    {
      throw new FeedException("Can not download rss file");
    }

    $domDocument = new DOMDocument();
    $domDocument-&gt;loadXML($this-&gt;orginalContent);
    return $domDocument;
  }

  /*
   * Get image URL from remote webpage
   */
  public function getImage($item)
  {
    if ('link' == $item-&gt;nodeName)
    {
      $htmlContent = Remote::get_remote_file_content($item-&gt;nodeValue);
      if (false !== $htmlContent)
      {
        //Search image tag
        $pattern = "/bigurl=\".*\"/";
        preg_match($pattern, $htmlContent, $urls);

        //Finally url to the image
        return substr($urls[0], 8, -1);
      }
      return 'http://demotywatory.pl/res/img/demotywatory-logo.gif';
    }
  }

  /*
   * Set new description for current item
   */
  public function setImage($item)
  {
    //Find element link
    foreach ($item-&gt;childNodes as $rssDomItemChild)
    {
      if ('link' == $rssDomItemChild-&gt;nodeName)
      {
        //Download demot page
        $url = $this-&gt;getImage($rssDomItemChild);
        break;
      }
    }

    //Find element description
    foreach ($item-&gt;childNodes as $rssDomItemChild)
    {
      if ('description' == $rssDomItemChild-&gt;nodeName)
      {
        $rssDomItemChild-&gt;nodeValue = '&lt;img src="'.$url.'"&gt;';
        break;
      }
    }
    Debug::Log('Found url: ['.$url.']');
  }

  /*
   * Convert rss.xml to rss.xml with images in description
   */
  public function transform()
  {
    //Get DOMDocument
    $this-&gt;transformedDom = $this-&gt;getDomOrginalContent();
    //Find all items
    $rssItems = $this-&gt;transformedDom-&gt;getElementsByTagName('item');

    //Foreach feed
    Debug::Log('Starting retriving feeds');

    foreach ($rssItems as $rssItem)
    {
      //Set image in description for node
      $this-&gt;setImage($rssItem);
    }
    Debug::Log('Finished retriving feeds!');
  }

  /*
   * Get transformed XML
   */
  public function getResults()
  {
    return $this-&gt;transformedDom-&gt;saveXml();
  }

  /*
   * Save results to file (xml)
   */
  public function save($filename)
  {
    Debug::Log('Saving file ('.$filename.')');
    $fh = fopen($filename, 'w');
    if (!$fh)
    {
      throw new FeedException('Can not open file ('.$filename.') to write!');
    }
    fwrite($fh, $this-&gt;getResults());
    fclose($fh);
  }
}</pre>
<p>I ostatni z plików który wszystko uruchamia:</p>
<pre class="php" name="code">
&lt;?php
define('DEBUG', true);    //Set debug on
require_once 'Feed.php';  //Include Feed class

//Create new Feed instance
$feed = new Feed();
//Set feed url
$feed-&gt;setUrl('http://demotywatory.pl/rss.xml');
//Get rss file content
$feed-&gt;getOrginalContent();
//Convert to new format
$feed-&gt;transform();
//Save to file
$feed-&gt;save('result.xml');</pre>
<p>Teraz wystarczy umieścić pliki na serwerze lub jeśli takiego nie posiadamy to można skorzytać np. z hostei.com (webhosting umożliwiający otwieranie plikow curl/fopen).<br />
I magia! Mamy rss z obrazkami demotywatorów w wiadomości! (Należy oczywiście odświeżać co jakiś czas nasze domotywatory, jednak użycie crona i wgeta w tej sytuacji jest w 100% wystarczające</p>
<p><a title="Demotywatorator" href="http://www.xlab.pl/wp-content/uploads/2009/10/Demotywatorator.tar.gz">Demotywatorator</a></p>
<p><a href="http://www.xlab.pl/2009/10/demotywacja-demotywatorow-ii/">&laquo; Druga część. </a></p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/10/demotywacja-demotywatorow/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Programistyczny fail: &#8220;argument musi być stringiem, przekazano string&#8221;</title>
		<link>http://xlab.pl/2009/08/programistyczny-fail-argument-musi-byc-stringiem-przekazano-string/</link>
		<comments>http://xlab.pl/2009/08/programistyczny-fail-argument-musi-byc-stringiem-przekazano-string/#comments</comments>
		<pubDate>Thu, 06 Aug 2009 16:58:55 +0000</pubDate>
		<dc:creator>Mateusz Kubiczek</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programistyczny fail]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=397</guid>
		<description><![CDATA[Dosyć ciekawy błąd przytrafił się koledze piszącemu w PHP:

Catchable fatal error:
Argument 1 passed to foo() must be an instance of string, string given, called in
/Users/madmatt/Documents/Projects/foobar/
dosyć mocny WTF. Kod, który to powoduje:

function foo(string $s)
{
}
foo("bar");
Próbujemy tutaj ograniczyć przyjmowane argumenty przez funkcję foo do zmiennych typu string. Co ciekawe, to nie bug, tylko feature (lub też brak tegoż [...]]]></description>
			<content:encoded><![CDATA[<p>Dosyć ciekawy błąd przytrafił się koledze piszącemu w PHP:</p>
<pre name="code">
Catchable fatal error:
Argument 1 passed to foo() must be an instance of string, string given, called in
/Users/madmatt/Documents/Projects/foobar/</pre>
<p>dosyć mocny WTF. Kod, który to powoduje:</p>
<pre name="code" class="php">
function foo(string $s)
{
}
foo("bar");</pre>
<p>Próbujemy tutaj ograniczyć przyjmowane argumenty przez funkcję <em>foo</em> do zmiennych typu string. Co ciekawe, to nie bug, tylko feature (lub też brak tegoż feature). Okazuje się, że php tego nie obsługuje. Powie nam to linijka na samym końcu <a href="http://us2.php.net/manual/en/language.oop5.typehinting.php">rozdziału o Type Hinting:</a></p>
<blockquote><p>
Type Hints can only be of the object and array (since PHP 5.1) type. Traditional type hinting with int and string isn&#8217;t supported.</p></blockquote>
<p>Czyli tablice, klasy &#8211; tak, podstawowe typy nie?<br />
Too bad, ale z treścią błędu mogli się bardziej postarać. A to jeszcze inny<a href="http://en.wikipedia.org/wiki/Scope_resolution_operator"> klasyk z PHP:</a></p>
<pre name="code">
Parse error: syntax error, unexpected ')', expecting T_PAAMAYIM_NEKUDOTAYIM in
Command line code on line 1</pre>
<p>To po hebrajsku. Znacie inne tego typu niespodzianki rodem z PHP?</p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/08/programistyczny-fail-argument-musi-byc-stringiem-przekazano-string/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Połączenie sfGuardUser oraz sfGuardUserProfile</title>
		<link>http://xlab.pl/2009/08/polaczenie-sfguarduser-oraz-sfguarduserprofile/</link>
		<comments>http://xlab.pl/2009/08/polaczenie-sfguarduser-oraz-sfguarduserprofile/#comments</comments>
		<pubDate>Tue, 04 Aug 2009 09:03:01 +0000</pubDate>
		<dc:creator>kibao</dc:creator>
				<category><![CDATA[Programowanie PHP]]></category>
		<category><![CDATA[admin generator]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[sfGuardPlugin]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.xlab.pl/?p=162</guid>
		<description><![CDATA[Przy ostatnich pracach związanych z przeniesienie naszej aplikacji z symfony 1.0 na symfony 1.2 natknęliśmy się na kilka problemów z pluginem sfGuardPlugin, a dokładniej z połączeniem administracji sfGuardUser oraz sfGuardUserProfile.
Chcieliśmy aby podczas listowania użytkowników, poprzez moduł sfGuardUser, można było wyświetlać nie tylko pola znajdujące się w tabeli sf_guard_user ale także wybrane pola z profilu usera, [...]]]></description>
			<content:encoded><![CDATA[<p>Przy ostatnich pracach związanych z przeniesienie naszej aplikacji z symfony 1.0 na symfony 1.2 natknęliśmy się na kilka problemów z pluginem <a href="http://www.symfony-project.org/plugins/sfGuardPlugin" target="_blank">sfGuardPlugin</a>, a dokładniej z połączeniem administracji sfGuardUser oraz sfGuardUserProfile.<br />
Chcieliśmy aby podczas listowania użytkowników, poprzez moduł sfGuardUser, można było wyświetlać nie tylko pola znajdujące się w tabeli sf_guard_user ale także wybrane pola z profilu usera, a także mieć możliwość filtrowania po nich.</p>
<p>Na początek stworzymy sobie możliwość wyświetlania pól z profilu użytkownika podczas listowania. Otwieramy <em>generator.yml</em> z modułu sfGuardUser, który w oryginale wygląda tak:</p>
<pre name="code">generator:
  class: sfPropelGenerator
  param:
    model_class:           sfGuardUser
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          sf_guard_user
    with_propel_route:     1

    config:
      fields:
        password_again: { label: "Password (again)" }

      list:
        title:   User list
        display: [=username, created_at, last_login]

      form:
        class: sfGuardUserAdminForm
        display:
          "NONE":                   [username, password, password_again]
          "Permissions and groups": [is_active, is_super_admin, sf_guard_user_group_list, sf_guard_user_permission_list]

      edit:
        title: Editing User "%%username%%"

      new:
        title: New User</pre>
<p>Interesuje nas dokładnie sekcja</p>
<pre name="code" class="YML">list:
  title:   User list
  display: [=username, created_at, last_login]</pre>
<p>Do wyświetlania informacji z pól z profilu użytkownika, będziemy korzystać z możliwości użycia własnych partiali.<br />
Nas interesuje np. imię, nazwisko, oraz adres e-mail użytkownika, dlatego wprowadzamy takie modyfikacje:</p>
<pre name="code" class="YML">list:
  title:   User list
  display: [=username, created_at, last_login, _first_name, _last_name, _email]</pre>
<p>Następnie tworzymy 3 pratiale w katalogu<em> sfGuardUser/templates/</em> :<br />
<em>_first_name.php</em></p>
<pre name="code"  class="php">&lt;?php echo $sf_guard_user-&gt;getProfile()-&gt;getFirstName(); ?&gt;</pre>
<p><em>_last_name.php</em></p>
<pre name="code"  class="php">&lt;?php echo $sf_guard_user-&gt;getProfile()-&gt;getLastName(); ?&gt;</pre>
<p><em>_email.php</em></p>
<pre name="code"  class="php">&lt;?php echo $sf_guard_user-&gt;getProfile()-&gt;getEmail(); ?&gt;</pre>
<p>Po tym</p>
<pre name="code"  class="bash">symfony cc</pre>
<p>I po odświeżeniu strony z listowaniem użytkowników będziemy mieli także informacje o imieniu, nazwisku oraz adresie email.<br />
Wszytko pięknie tylko po takich modyfikacjach nie można sortować po dodanych polach. A to dlatego, że generator podczas tworzenia kodu naszych template&#8217;ów sprawdza czy pole które ma być wyświetlone istnieje w modelu.<br />
Dlatego musimy obejść to sprawdzanie poprzez zmodyfikowanie informacji o polach w listowaniu (<em>generator.yml</em>):</p>
<pre name="code"  class="YML">list:
  title:   User list
  display: [=username, created_at, last_login, _first_name, _last_name, _email]
  fields:
    last_name:      { is_real: true }
    first_name:     { is_real: true }
    email:          { is_real: true }</pre>
<p>Teraz po wyczyszczeniu cache i odświeżeniu strony mamy już możliwość sortowania po polach z profilu.</p>
<p>Jeżeli przejdziecie teraz w środowisko dev zauważycie, że nagle zwiększyła się bardzo ilość zapytań do bazy.<br />
A to ze względu, że dla każdego użytkownika pobierany jest osobno jego profil.<br />
Należałoby to zoptymalizować, aby nie wywoływać niepotrzebnie tylu zapytań. Tutaj natknęliśmy się na kilka problemów, ponieważ klasa sfGuardUserPeer nie posiada metody doSelectJoinsfGuardUserProfile.<br />
Otwieramy plik <em>sfGuardUserPeer.php</em> i wprowadzamy pewne modfikacje:</p>
<pre name="code"  class="php">&lt;?php
class sfGuardUserPeer extends PluginsfGuardUserPeer
{
  public static function doSelectJoinsfGuardUserProfile(Criteria $c, $con = null, $join_behavior = Criteria::LEFT_JOIN)
  {

    foreach (sfMixer::getCallables('BasesfGuardUserPeer:doSelectJoin:doSelectJoin') as $callable)
    {
      call_user_func($callable, 'BasesfGuardUserPeer', $c, $con);
    }

    $c = clone $c;

    if ($c-&gt;getDbName() == Propel::getDefaultDB()) {
      $c-&gt;setDbName(self::DATABASE_NAME);
    }

    sfGuardUserPeer::addSelectColumns($c);
    $startcol = (sfGuardUserPeer::NUM_COLUMNS - sfGuardUserPeer::NUM_LAZY_LOAD_COLUMNS);
    sfGuardUserProfilePeer::addSelectColumns($c);

    $c-&gt;addJoin(array(sfGuardUserPeer::ID,), array(sfGuardUserProfilePeer::USER_ID,), $join_behavior);
    $stmt = BasePeer::doSelect($c, $con);
    $results = array();

    while ($row = $stmt-&gt;fetch(PDO::FETCH_NUM)) {
      $key1 = sfGuardUserPeer::getPrimaryKeyHashFromRow($row, 0);
      if (null !== ($obj1 = sfGuardUserPeer::getInstanceFromPool($key1))) {
      } else {

        $omClass = sfGuardUserPeer::getOMClass();

        $cls = substr('.'.$omClass, strrpos('.'.$omClass, '.') + 1);
        $obj1 = new $cls();
        $obj1-&gt;hydrate($row);
        sfGuardUserPeer::addInstanceToPool($obj1, $key1);
      }
      $key2 = sfGuardUserProfilePeer::getPrimaryKeyHashFromRow($row, $startcol);
      if ($key2 !== null) {
        $obj2 = sfGuardUserProfilePeer::getInstanceFromPool($key2);
        if (!$obj2) {

          $omClass = sfGuardUserProfilePeer::getOMClass();

          $cls = substr('.'.$omClass, strrpos('.'.$omClass, '.') + 1);
          $obj2 = new $cls();
          $obj2-&gt;hydrate($row, $startcol);
          sfGuardUserProfilePeer::addInstanceToPool($obj2, $key2);
        }
        $obj1-&gt;setProfile($obj2);

      }
      $results[] = $obj1;
    }
    $stmt-&gt;closeCursor();
    return $results;
  }
}</pre>
<p>A w pliku <em>sfGuardUser.php</em>, dodajemy funkcję setProfile :</p>
<pre name="code"  class="php">&lt;?php
class sfGuardUser extends PluginsfGuardUser
{
  public function setProfile( $profile )
  {
    $this-&gt;profile = $profile;
  }
}</pre>
<p>Na koniec musimy tylko zmienić peer_method wywoływaną przez generator do pobierania wyników.<br />
Otwieramy plik <em>sfGuardUser/lib/sfGuardUserGeneratorConfiguration.class.php</em> i dodajemy funkcję getPeerMethod:</p>
<pre name="code"  class="php">&lt;?php

/**
 * sfGuardUser module configuration.
 *
 * @package    sfGuardPlugin
 * @subpackage sfGuardUser
 */
class sfGuardUserGeneratorConfiguration extends BaseSfGuardUserGeneratorConfiguration
{
  public function getPeerMethod()
  {
    return 'doSelectJoinsfGuardUserProfile';
  }
}</pre>
<p>Teraz po wyczyszczeniu cache, odświeżeniu strony ilość zapytań do bazy powinna spaść.</p>
<p>Na dzisiaj to tyle. Zostało jeszcze opisanie jak filtrować po polach z profilu użytkownika ale to już innym razem.</p>
]]></content:encoded>
			<wfw:commentRss>http://xlab.pl/2009/08/polaczenie-sfguarduser-oraz-sfguarduserprofile/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
