//DEVGURU

Category archives ‘php’

FirePHP – Firebug Extension for AJAX Development

Saturday, December 27th, 2008

Nie raz pewnie spotkaliście się z problemem debugowania skryptów PHP wywoływanych przez AJAX. O ile w jednorazowych przypadkach wystarczy FireBug w Firefoxie (linia czasowa sieci w Safari) w połączeniu z dumpem interesujących nas zmiennych, to w przypadku zapytań zwracających kod JSON lub XML pojawia się problem. Gdy dopiszemy coś do zapytania, format zwracanej treści przestanie być już poprawny i nasz skrypt JS może nie zadziałać poprawnie.

Rozwiązaniem tego problemu jest właśnie FirePHP. Jest to z jednej strony dodatek do Firefoxa, który rozszerza funkcjonalność znanego wszystkim FireBuga, a z drugiej strony biblioteka PHP umożliwiająca przesyłanie informacji do konsoli FireBuga. Jak to się dzieje? PHP dodaje nagłówek X-FirePHP-Data w którym znajduje się obiekt JSON z informacjami dla konsoli. Dodatek do przeglądarki informacje te przechwytuje i wyświetla.

Szczegółowe informacje o zastosowaniach FirePHP wraz z przykładami znajdują się na stronie. Warto jednak najpierw zajrzeć na stronę Wiki, ponieważ tam znajdują się informacje o dodatkach implementujących wykorzystanie FirePHP w takich frameworkach jak Zend, CodeIgniter, Kohana czy CakePHP. Niektóre z nich – np. Zend pozwala na przesyłanie profilera bazy danych do FirePHP.

Więcej o zastosowaniu FirePHP można było przeczytać również w listopadowym numerze php|architect.

Hoptoad PHP

Tuesday, November 25th, 2008

Zalety korzystania z Hoptoada, jako “narzędzia do obsługi wyjątków w railsowych aplikacjach” wychwalał już na devguru Bartosz. Od niedawna, nie jest on jednak ograniczony do aplikacji Railsowych. Rich Cavanaugh zakodował w PHP obsługę błędów i wyjątków korzystającą z API Hoptoada.

Choć sam autor wspomina, że nie jest to jeszcze wersja finalna, postanowiliśmy skorzystać z niej na Flakerze. Jak na razie, sprawuje się na prawdę dobrze i z czystym sumieniem możemy ją polecić innym programistom PHP.

Jak zintegrować Hoptoada z naszą aplikacją PHP?

1. Wymagania

Do działania potrzebujemy Horde_Yaml i HTTP_Request.

pear channel-discover pear.horde.org
pear install horde/yaml

pear install HTTP_Request

2. Plik Hoptoad.php, który znajdziemy w repozytorium.
Do pliku musimy dodać ładowanie wspomnianych pluginów, czyli np:
require_once "HTTP/Request.php"
require_once "Horde/Yaml.php"

3. Ustawienie klucza API i przypisanie Hoptoada do obsługi błędów i wyjątków. Może to wyglądać na przykład tak:
require_once('Hoptoad.php');
define("HOPTOAD_API_KEY", "YOUR_HOPTOAD_API_KEY");
set_error_handler(array("Hoptoad", "errorHandler"));
set_exception_handler(array("Hoptoad", "exceptionHandler"));

Oczywiście poziom raportowania możemy zmienić dodając drugi parametr do set_error_handler.

Na jakie problemy, możemy napotkać?
Autor wspomina na swojej stronie o możliwych nieścisłościach w wynikach zwracanych przez metodę Hoptoad::tracer() – która generuje informacje o miejscu w którym wystąpił błąd. Na razie jednak wszystkie wyniki, które otrzymałem były precyzyjne, stąd trudno będzie problem ten potwierdzić.

Dodatkowe informacje o licencji i samym pluginie można znaleźć na stronie repozytorium.

UploadPack – łatwy i elastyczny system uploadu plików w CakePHP

Tuesday, November 4th, 2008

Mimo, że upload plików to funkcjonalność obecna w wielu aplikacjach internetowych, do tej pory trudno znaleźć rozwiązanie, które by w znaczący sposób ułatwiało obsługę tego procesu z poziomu aplikacji w CakePHP.

Widziałem i używałem już paru skryptów czy pluginów obsługujących upload po stronie serwera (napisanych nie tylko w PHP), dzięki temu mogłem stworzyć sobie wizję takiego pluginu, dostosować ją do specyfiki frameworka CakePHP i spróbować wprowadzić ją w życie.

Postawione wymagania:

  • zapis rekordu z załącznikiem nie powinien się niczym różnić od zapisu zwykłego rekordu
  • całość powinna działać przy minimalnej konfiguracji, ale również być łatwo dostosowywalna do potrzeb aplikacji
  • możliwość wykonywania dodatkowych czynności z przesłanym plikiem niż tylko zapis na dysk, np. wygenerowanie miniaturek obrazka
  • łatwy dostęp do plików, także do ich różnych wersji (miniaturek) z poziomu widoku
  • naturalna integracja z CakePHP

Efektem pracy jest UploadPack, który w chwili obecnej zawiera:

  • behavior, który podłączony do modelu zajmuje się zapisem plików na dysk i ewentualnie generowaniem miniaturek obrazków
  • helper, który ułatwia wyświetlanie url’i do przesłanych plików i wyświetlanie obrazków

Wydaje mi się, że całość działa dość zgrabnie. Jedyną rzeczą, którą trzeba zrobić jest dodanie pola w bazie danych przechowującego nazwę pliku i podpięcie behaviora do modelu. Reszta idzie automatycznie. Wszystko jest udokumentowane na stronie repozytorium UploadPack.

Na razie dostępna jest wersja 0.1, z którą jednak można już całkiem sporo zdziałać. Prace nad dalszymi funkcjonalnościami trwają :)

Named scope w CakePHP

Sunday, August 31st, 2008

Kilka dni temu napisałem jak ulepszyć system callbacków w kontrolerach CakePHP wzorując się na rozwiązaniu zastosowanym w Ruby on Rails. Dziś przeniesiemy kolejny fajny feature z Railsów do Cake’a – named scope.

Named scope czyli po polsku “nazwany zakres/dziedzina”. Chodzi o definicję podzbiorów rekordów danego modelu w celu późniejszego łatwiejszego ich wyszukiwania.

Zaczniemy znowu od przykładu. Definicja modelu User w RoR:

class User < ActiveRecord::Base
  # podzbiór użytkowników, którzy aktywowali konto
  named_scope :activated, :conditions => "activated_at is not null" 

  # podzbiór użytkoników, którzy są on-line
  named_scope :online, :conditions => "date_add(last_activity, interval 5 minute) > now()"
end

To bardzo prosty przykład, w rzeczywistości named scope nadaje się też do dużo bardziej ciekawych rzeczy. Jednak “wersja Cake’owa”, którą zaprezentuje poniżej jest właśnie ograniczona do definicji warunków.

Zdefiniowane wyżej zakresy możemy później użyć podczas wyszukiwania:

# wszyscy aktywowani użytkownicy
users = User.activated.find(:all) 

# wszyscy aktywowani i on-line użytkownicy z liczbą punktów większą od 10
users = User.activated.online.find(:all, :conditions => "points > 10")

Zalety zapisu widać właściwie od razu. Named scope upraszcza zapis. Często powtarzające się warunki wyszukiwania (dostęp tylko do aktywowanych użytkowników potrzebujemy w wielu miejscach aplikacji) umieszczamy w named scope, a inne umieszczamy “po staremu” w :conditions. Zapis wygodny i bardzo czytelny.

Podobną funkcjonalność w Cake’u zapewni nam NamedScopeBehavior, który przetłumaczy (w callbacku modelu beforeFind) zdefiniowane zakresy na warunki przekazane później do zapytania SQL.

Plik named_scope.php pobrany repozytorium należy umieścić w folderze app/models/behaviors. Później w modelu możemy już definiować zakresy.

class User extends AppModel {
  var $actsAs = array(
    'NamedScope' => array(
      'activated' => array('User.activated in not null'),
      'online' => array('date_add(User.last_activity, interval 5 minute) > now()')
    )
  );
}

Teraz już możemy zacząć korzystać.

$this->User->find('all', array('scope' => 'activated'));
$this->User->find('all',
  array('conditions' => 'points > 10', 'scope' => array('activated', 'online')));

Paginacja działa równie dobrze:

$paginate = array(
  'User' => array(
    'order' => 'created ASC',
    'limit' => 20,
    'scope' => array('online', 'activated')
  )
);

Mam nadzieję, że NamedScopeBehavior ułatwi i uprzyjemni Wam trochę development w CakePHP :)

Ulepszony system callback’ów w kontrolerach CakePHP

Friday, August 22nd, 2008

Callbacki w kontrolerach (beforeFilter, beforeRender lub afterFilter) to bardzo użyteczne narzędzia, pozwalają zaoszczędzić sporo czasu, czynią kod bardziej czytelnym i bardziej DRY. Jednym słowem miód… chyba, że zrobią się zbyt duże.

Sprawa najczęściej wygląda tak:

  • chcemy, żeby jakiś kod wykonał się przed każdym requestem w kontrolerze, więc wrzucamy go do beforeFilter
  • z kolei inne rzeczy mają się wykonywać tylko przed niektórymi akcjami, więc je też wrzucamy do beforeFilter dodając jeszcze blok if-then-else, który zdecyduje kiedy je wykonać
  • mamy jeszcze trochę kodu, który chcemy wykonać przed requestem, ale tylko gdy użytkownik jest zalogowany, kolejny if-then-else ląduje w beforeFilter
  • i tak dalej, aż przestajemy nad wszystkim panować

Można by zrobić to lepiej? No cóż, spójrzmy na poniższy kawałek kodu z Ruby on Rails.

class UsersController < ApplicationController
  before_filter :do_something, :do_always
  before_filter :do_something_else, :only => [:show, :new]
  before_filter :do_something_different, :except => :edit

  # method definitions commented out
end

Oto co tu się dzieje:

  • metody do_something i do_always zostaną wykonane przed każdym requestem
  • do_something_else tylko przed akcjami show i new
  • do_something_different przed wszystkimi akcjami oprócz edit

Prosto i czysto. Każdy kawałek kodu, który chcemy wykonać w before_filter (niezależnie od warunków) ląduje w osobnej metodzie kontrolera. Kontroler sam decyduje co i kiedy wykonać.

Jest to łatwiejsze w zarządzaniu i testowaniu niż podejście zastosowane w CakePHP. Więc… przenieśmy je do Cake’a.

Kod AppControllera do pobrania z pastie.

Teraz możemy już robić takie rzeczy:

  var $beforeFilter = array(
    array(
      'methods' => array('do_something', 'do_always')
    ),
    array(
      'methods' => array('do_something_else'),
      'only' => array('show', 'new')
    ),
    array(
      'methods' => array('do_something_different'),
      'except' => array('edit')
    )
  );

  function _do_something() { }
  function _do_always() { }
  function _do_something_else() { }
  function _do_something_different { }

Powyższy kod jest ekwiwalentem kodu kontrolera w Ruby on Rails z początku postu. Znaki podkreślenia przed nazwami metod pozwalają je odróżnić od regularnych akcji zdefiniowanych w kontrolerze.

Dodatkowo możemy także określić inne warunki wykonania metod przez callback.

  var $beforeFilter = array(
    array(
      'methods' => array('do_something'),
      'if' => array('is_admin')
    ),
    array(
      'methods' => array('do_something_else'),
      'unless' => array('is_logged_in')
    )
  );

  function _logged_in() { }
  function _is_admin() { }

Teraz metoda do_something zostanie wywołana tylko gdy _is_admin zwróci true, a do_something_else gdy _is_logged_in zwróci false. Zresztą kod mówi sam za siebie.

Oczywiście wszystkie opcje (only, except, if, unless) mogą być używane razem lub w dowolnych konfiuracjach.

Definicje w tabeli $beforeFilter (lub $beforeRender, lub $afterFilter) będą analizowane w kolejności w jakiej się się w niej pojawią (o ile oczywiście ich warunki zostaną spełnione).

Dodatkowo dzięki rozszerzeniu metody __mergeVars, która jest automatycznie wywoływana przy inicjalizacji kontrolera, możliwe jest definiowanie tablic również w AppControllerze i PluginAppControllerze. Wtedy pierwszeństwo mają te z AppControllera, następnie PluginAppControllera i na końcu ze zwykłego kontrolera.

Enjoy.

Zarządzanie asocjacjami typu Habtm w CakePHP

Sunday, June 1st, 2008

Asocjacje typu has and belongs to many w Cake PHP potrafią czasem nieźle napsuć krwi. Dodawanie i usuwanie powiązań rekordów jest dość pracochłonne, a co gorsze może być źródłem błędów, jeśli robić to za każdym razem ręcznie. Dziś wpadłem na rozwiązanie, które znacząco ułatwia życie w takich sytuacjach. Chodzi o zamieszczony już jakiś czas temu w bakery behavior ExtendAssociations.

Dzięki zastosowaniu tego rozwiązania możliwe są konstrukcje:

$this->Post->habtmAdd('Tag', $postId, $tagId);
...
$this->Post->habtmDelete('Tag', $postId, $tagId);
...
$this->Post->habtmDeleteAll('Tag', $postId);

Więcej szczegółów w podlinkowanym wyżej artykule. Polecam również przeczytanie zamieszczonych tam komentarzy, które sugerują możliwości dodatkowego usprawnienia tego behaviora.

Całość oczywiście nie jest tak fajna i intuicyjna jak np. w Railsach, ale taka już specyfika samego PHP. Mimo to polecam :)

XML + XSL + PHP

Monday, March 31st, 2008

XML plus XSL
Najlepiej się uczy na przykładach. Dlatego na przykładzie pokażę o co chodzi w XSL. Przygotuje plik XML z danymi i dołączę do niego plik XSL. W wyniku chcę mieć tabelkę z danymi z XMLa. Zaczynam od XMLa.

<?xml version=”1.0″ encoding=”utf-8″?>
<?xml-stylesheet type=”text/xsl” href=”samochody-style.xsl”?>
<samochody>
<auto>
<marka>Audi</marka>
<model>A6</model>
<rok_prod>2006</rok_prod>
<cena>110000</cena>
</auto>
<auto>
<marka>Toyota</marka>
<model>Avensis</model>
<rok_prod>2008</rok_prod>
<cena>130000</cena>
</auto>
<auto>
<marka>Honda</marka>
<model>Civic</model>
<rok_prod>2004</rok_prod>
<cena>78000</cena>
</auto>
</samochody>

Plik samochody-style.xsl:

<?xml version=”1.0″ encoding=”utf-8″?>
<xsl:stylesheet version=”1.0″ xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>
<xsl:template match=”samochody”>
<html>
<body>
<h2>Samochody</h2>
<table>
<tr>
<th>Marka samochodu</th>
<th>Model samochodu</th>
<th>Rok produkcji</th>
<th>Cena</th>
</tr>
<xsl:for-each select=”auto”>
<tr>
<td><xsl:value-of select=”marka”/></td>
<td><xsl:value-of select=”model”/></td>
<td><xsl:value-of select=”rok_prod”/></td>
<td><xsl:value-of select=”cena”/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Elementy wymagane
Arkusz XSL jest poprawnym dokumentem XML, więc pierwszą linijką będzie nagłówek XML: <?xml version=”1.0″ encoding=”utf-8″?>.
Główny elementem deklarującym dokument jako styl jest <xsl:stylesheet> lub <xsl:transform> (obie deklaracje są równoważne). Poprawny sposób zdeklarowania stylu XSL według zaleceń W3C wygląda tak: <xsl:stylesheet version=”1.0″ xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”> lub <xsl:transform version=”1.0″ xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

Arkusze stylów XSL
Arkusze stylów składają się ze zbiorów zasad zwanych szablonami. Aby utworzyć taki szablon trzeba użyć elementu xsl:template. Atrybut match jest używany do połączenia szablonu z elementami XML’a (np. match=”/” przypisuje schemat do całego dokumentu).
To co jest pomiędzy <xsl:template> a </xsl:template> zawiera kod HTML, który będzie wyświetlany. Aby wstawić w kod HTMLowy dane z XMLa trzeba użyć polecenia xsl:value-of. Atrybut select określa, które elementy będą odczytywane.

Pętla
Jeśli chcemy aby nasz styl przeszedł przez wszystkie węzły dokumentu XML musimy użyć pętli <xsl:for-each>. Jak w każdym poprawnym dokumencie XMLowym trzeba pamiętać aby każdy otwarty znacznik zamknąć. Pętlę też: </xsl:for-each>.
Możemy dodać warunek do pętli, a przez to filtrować dane. Kryterium filtrujące trzeba umieścić w kwadratowych nawiasach: <xsl:for-each select=”auto[marka='Toyota']“>. Warunek może być złożony. Są do dyspozycji operatory:

= równe
!= różne
< mniejsze niż (less than)
> większe niż (greater than)

Co by było gdyby
Do dyspozycji jest warunek logiczny IF – <xsl:if> </xsl:if>.

<xsl:if test=”warunek”>

…wyrażenia, które są przetwarzane, jeżeli zachodzi warunek

</xsl:if>

Możemy na przykład dodać element <xsl:if> wewnątrz elementu <xsl:for-each>.

Słicz i kejs
Elementu <xsl:choose> odpowiada switch/case z c++. Jeżeli któryś z warunków jest spełniony, to następne nie są sprawdzane.

<xsl:choose>
<xsl:when test=”warunek1″>

… przetwarzane, gdy warunek1 jest prawdziwy

</xsl:when>
<xsl:when test=”warunek2″>

… przetwarzane, gdy warunek2 jest prawdziwy

</xsl:when>
<xsl:otherwise>

… przetwarzane, gdy żadne z powyższych warunków nie były prawdziwe

</xsl:otherwise>
</xsl:choose>

Tak, a gdzie tutaj jest PHP? Otóż jeszcze nie ma :) Nic straconego. Poniżej jest przykład jak to wszystko współpracuje z naszym ulubionym językiem programowania :) Ale zanim zabiorę się za PHP warto jeszcze wspomnieć o pewnej istotne sprawie. W przykładzie z XMLem i XSLem kod strony generowany jest w locie. Ma to plusy (stosunkowo niewielkie obciążenie serwera) i minusy (ryzyko niekompatybilności). I tutaj naprzeciw naszym potrzebom wychodzi PHP.

PHP w wersji 5_ ma wbudowane wsparcie dla transformacji XSL (serwer PHP musi być uruchomiony z opcją – with-xsl, a w systemie konieczna jest instalacja biblioteki libxslt). Można więc bez problemu wygenerować kod HTML i wysłać go do przeglądarki użytkownika. Do tego jest potrzebny jeszcze jeden plik – auta.php.

<?php
//Wczytuje plik XML
$xml = new DOMDocument(’1.0′, ‘utf-8′);
$xml->load(’samochody.xml’);
//Wczytuje arkusz XSL
$xsl = new DOMDocument(’1.0′, ‘utf-8′);
$xsl->load(’samochody-style.xsl’);
//Podczepiam arkusz stylów
$dane = new XSLTProcessor;
$dane->importStyleSheet($xsl);
//Wyswietlam koh HTML wygenerowany z XMLa i stylów XSL
echo $dane->transformToXML($xml);
?>

O co chodzi w users.php?
Pierwszą rzeczą jaką robi skrypt to wczytanie pliku XML oraz XSL (do tego celu wykorzystujemy
jeden z standardowo dostępnych modułów PHP5 – Document Object Model – DOM). Następnie
dokonuje transformacji i ostatecznie wyświetla efekt końcowy w przeglądarce. Całą pracę
wykonuje za nas PHP.
Ta metoda zapewnia pełną kompatybilność z przeglądarkami nieobsługującymi XSLT, jednak
każdorazowe generowanie strony w większym stopniu obciąża serwer.

Ot i cała sztuka :) Pierwszą rzeczą jaką robi skrypt to wczytuje XML i XSL. Później dokonuje transformacji i wyświetla wynik w przeglądarce. Całą pracę wykonuje PHP. Dzięki temu mamy pełną kompatybilność z przeglądarkami bez wsparcia dla XSLT, jednak każdorazowe generowanie strony w większym stopniu obciąża serwer.
Do tego można jeszcze dołożyć odrobinę MySQLa aby pozyskiwać dane z bazy a nie z pliku i mamy wszystko aby napisać ekonomiczną aplikację webową :) O MySQLu troszkę później :)

CakePHP – dobre praktyki

Monday, February 25th, 2008

Pisząc małe aplikacje nie musimy się przejmować zbytnio jakością kodu, ale już nawet średnie projekty wymagają tego, by je w jakiś sposób zaplanować, żeby je w ogóle skończyć. Po napisaniu kilku rzeczy w Cake’u mam parę spostrzeżeń na to, jak pisać, by było dobrze (a przynajmniej by nie było źle). Część z poniższych punktów odnosi się do programowania w ogóle, część jest stricte Cake’owa.

1. Dobre zaplanowanie struktury aplikacji.

Rzecz najważniejsza. Przeanalizować założenia, zidentyfikować wszystkie obiekty, które będą występować w systemie. Dobrze rozbić aplikację na wiele rozłącznych modułów, które będą mogły być oddzielnie implementowane i testowane, a dopiero później ze sobą łączone. Korzyści są oczywiste – mniejsze powiązanie różnych części aplikacji ze sobą (łatwiejsza implementacja, testowanie), łatwość podziału prac pomiędzy wielu programistów.

2. Podejście abstrakcyjne do implementacji funkcjonalności

Poszczególne moduły aplikacji mogą zostać tak wykonane, żeby mogły być użyte w wielu kontekstach bez dokonywania w nich żadnych, bądź tylko kosmetyczno/konfiguracyjnych zmian. Dla przykładu funkcjonalność oceniania obiektów przez użytkownika można wykonać następująco:

tabela w bazie danych ratings:

  • id – primary key
  • user_id – id oceniającego użytkownika
  • model – nazwa modelu obiektu ocenianego
  • foreign_key – id obiektu ocenianego
  • rating – ocena

Taka struktura pozwala na ocenę każdego obiektu w serwisie. Do tego można dopisać sobie odpowiedni Behavior udostępniający w wybranych modelach metodę realizującą zapis oceny do bazy i mamy prosty ogólny system oceniania, do którego aktywacji wystarczy dodanie behaviora do $actsAs modelu.

3. Uniezależnienie kontrolerów od interfejsu użytkownika

Akcje w kontrolerach powinny być tak zaimplementowane, by nie było różnicy czy są wywoływane poprzez AJAX czy normalnie. To widoki (a właściwie w tym przypadku layouty) decydują jak zostaną zwrócone dane wynikowe przez akcję.

4. Przeniesienie większości logiki do modeli

To w modelach, nie w kontrolerach powinna być zaimplementowa większość logiki biznesowej systemu – zgodnie z koncepcją “Fat model, skinny controller”. Często wiele kontrolerów korzysta z tych samych modeli wykonując na nich te same funkcje. Przeniesienie tych funkcji do modelu pozwala łatwiej później wprowadzać zmiany i poprawiać ewentualne błędy, ponieważ musimy tego dokonywać tylko w jednym miejscu – w modelu. Podobnie implementacja takich mechanizmów jak caching czy logowanie zdarzeń jest dużo łatwiejsza do wykonania i utrzymania.

5. Elementy

Podobnie jest z elementami. W wielu widokach występują te same elementy, więc powinny być tworzone właśnie w takiej formie. Dla przykładu: formularze dodawania i edycji obiektu z reguły różnią się od siebie co najwyżej wartością atrybutu action, zatem idealnie nadają się, by zrobić z nich element.

To pierwsze modele i kontrolery aplikacji determinują jej strukturę i to, w jaki sposób jest dalej rozwijana. W połowie prac, bardzo trudno zmienić założenia co do struktury kodu, zatem analiza i planowanie powinny odbyć się jeszcze przed rozpoczęciem kodowania. I myślę, że nie należy na nie przesadnie szczędzić czasu, gdyż zwróci się on później na pewno.

CakePHP – Odc. I – Wariacje na temat ‘_queryCache’…

Saturday, October 13th, 2007

…czyli jak zadać dwa razy to samo pytanie i uzyskać inną odpowiedź.

akt 1. Cache’owanie zapytań

Cake zapamiętuje wynik każdego zapytania o czym nie można się dowiedzieć z manuala. Zapamiętuje jednak tylko w pamięci podręcznej, a więc jedyna korzyść z tego taka, że jak w tej samej metodzie (akcji) controllera wywołam np.

$user = $this->User->findAllByName('Alojzy');
...
$user = $this->User->findAllByName('Alojzy');

to zapytanie do bazy danych zostanie wykonane tylko raz. Dzieje się tak nawet, jeśli zamiast findAllByName skorzystamy z
$this->User->query(…)

Problem jednak pojawia się, gdy między tymi wywołaniami znajduje się coś jak:

$this->User->save($this->data);


wtedy drugie findAllByName zwróci nieaktualne dane (oczywiście o ile $this->data będzie zawierało informacje o jakimś Alojzym).

Sytuacji takiej oczywiście zwykle można uniknąć, bo jak odczytam dane z bazy, a potem je zmienię, to znaczy, że posiadam komplet wiedzy, żeby zbudować wynik kolejnego odczytu bez odwoływania się do bazy.

akt 2. Jak być powinno

Zgodnie z ideą CakePHP programista powinien dążyć, do traktowania modelu jako raczej reprezentanta konkretnego rekordu, a nie tabeli (oczywiście findAll i temu podobne to wyjątki). Dlatego przy zapisie danych ustalany jest klucz rekordu i wywołując ’saveField’, czy ’save’ bez klucza, odwołujemy się ciągle do tego samego rekordu. Ponieważ bywa to uciążliwe, gdy zapisuje się więcej niż jeden rekord (np. w pętli), Cake’owcy proponują w takim przypadku wywoływać:

$this->[nazwa_modelu]->create();


za każdym razem gdy skończymy odwoływać się do konkretnego rekordu (np. w tej pętli).

Wydaje sie naturalne, że wywołanie takiego resetu na modelu powinno wyczyścić jego cache (czy tylko mi się to wydaje naturalne?)

Niestety cache nie jest trzymany w klasie AppModel (model_php5.php), tylko w klasie DataSource (datasource.php i dbo_source.php). Dzięki temu zapytanie jest cache’owane jako jedno i to samo nawet gdy wywoływane jest poprzez różne modele (IMHO nieco wątpliwa korzyść). Utrudnia to jednak operowanie na cache’u.

akt 3. Rozwiązanie.

W przypadku, gdy chcę odczytać dane z bazy (select) przed i po modyfikacji danych (update), muszę po updacie wyczyścić cały cache zapytań. Na stronie: http://www.benjiegillam.com/serendipity/categories/11-CakePHP Benjie Gillam proponuje dodać do app/app_model.php następującą funkcję:

 function clearAllDBCache() {
   $db =& ConnectionManager::getDataSource($this->useDbConfig);
   $db->_queryCache = array();
  }

i wywoływać ją za każdym razem, gdy w środku controllera zapragniemy aktualnych danych.

Krzysiek Heród

kilka słów o… SimplePie

Wednesday, October 3rd, 2007

logo_simplepie_horizontal1.png

Co jakiś czas będę pisał o dojrzałych php’owych bibliotekach, które warto wykorzystywać w swoich projektach…

W tym poście będzie trochę o SimplePie:

SimplePie to bardzo dobra biblioteka do parsowania źródeł XML (RSS, Atom), która nadrabia braki Magpie RSS.

Główne cechy Simplepie to:

  • obsługa wielu feedów w formacie RSS i ATOM (możemy parsować kilkanaście feedów – docelowa lista wpisów może być uszeregowana według daty wpisu lub źródła)
  • filtracja postów (m.in. z reklam, z atrybutów tagów HTML, całkowita filtracja z HTML)
  • caching
  • obsługa podcastów