Posts Tagged: php

2010
24
jan

Doctrine és Zend Framework integráció


Jó ideje Zend Frameworkkel gyártom a “kontentot” és az elmúlt két év tapasztalatából azt a fájdalmas konzekvenciát kellett levonnom, hogy a Zend MVC-jének modell rétegével kínkeserves meló egy korrekt Domain Modell leprogramozása, a modell változásainak követése és karbantartása. Amikor a feladat szükségessé teszi egy komolyabb adatbázis-struktúra kezelését akkor a Zend_Db időről-időre csődöt mond. Hiába próbáltam hosszú időn keresztül elkészíteni a tökéletes adatbázis absztrakciós réteget, sajnos sosem jártam igazi sikerrel. Kipróbáltam mások elképzeléseit is (Model Infrastructure, Domain Model Programming With the Zend Framework) de hosszabb távon, az igények növekedésével ezek a megvalósítások mindig zsákutcának bizonyultak. Nem arról van szó, hogy a Zend modell rétege nem működne, sőt egyszerű modellek esetén nagyon kényelmes és gyors is. Arról van szó, hogy egy megfelelően bonyolult domain modell karbantartása indokolatlanul sok feladatot hárít a fejlesztőre, így sokszor a napi maintenance munka nagy része azzal megy el, hogy az alkalmazás működésében vagy az adatmodellben bekövetkezett változások miatt az domain modellt kell püfölni. Egy agilis fejlesztési folyamat során ez sajnos nem megengedhető, túlzottan sok időt és energiát emészt fel.

A legutóbbi nagyobb projekt során az egyik kolléga hívta fel a figyelmet arra, hogy a jelenlegi – Zend_Db-vel készült – modell struktúra enyhén szólva is kurva bonyolult és átláthatatlan. Mivel napi szinten benne voltam a modell karbantartásában és fejlesztésében ez szinte fel sem tűnt, de amikor bármi miatt módosítani kellett az egy rémálom volt. A javításról nem is beszélve. Eldöntöttük, hogy ideje nyugdíjazni a Zend_Db-t a projekt keretein belül és alternatíva után kezdtem kutakodni. Így került képbe a Doctrine, amit már jó ideje figyelemmel kísértem és konkrét tapasztalataim is voltak vele, de Zend Framework integrációval még sosem próbálkoztam. Találtam egy remek bejegyzést Eric Leclerc blogján Doctrine 1.2 is Zend Framework friendly címmel, valamint a zendcasts.com-on (Podcast) található egy screencast sorozat is a témában:

A Doctrine egy object relational mapper (meg sem próbálkozok a fordításával) amely tulajdonképp az adatbázis absztrakciós réteg tetején ül és integrálódik az OOP-s környezetbe. A parancssori interfész segítségével képes adatbázis sémákból php modelleket generálni (és vica versa), tesztadatok betöltésére vagy akár unit tesztelésre is. Szóval tényleg nagyon erős kis eszköz, igazán szerethető. A Symfony projekt is ezt használja például alapértelmezett ORM-ként a Propel mellett. Ami pedig a legszebb, hogy nagyon gyorsan megtanulható és üzembe állítható. Ezt mi sem bizonyítja jobban, mint az imént említett projekt, ahol a Zend_Db alapú modellek elkészítése több hétig tartott, a Doctrine migráció pedig mindösszesen egyetlen napig (bár az is igaz, hogy egy meglehetősen hosszú nap volt).

Lássuk, hogyan is történik ez integrálás a gyakorlatban. A standard Zend projekt fában a képen látható mappaszerkezetet kell létrehozni. A Doctrine projekt honlapjáról letöltött csomag lib könyvtárából másoljuk be a komplett Doctrine és vendor foldereket a projektünk library könyvtárába a Zend mellé valamint a doctrine.php-t szintén a library folderbe. Ezt követően az application.ini-ben kell némi konfigurációt elvégezni:

autoloadernamespaces[]      = "Doctrine_"
doctrine.connection_string  = "mysql://user:password@mysqlhost/dbname"
doctrine.data_fixtures_path = APPLICATION_PATH "/../doctrine/data/fixtures"
doctrine.models_path        = APPLICATION_PATH "/models"
doctrine.migrations_path    = APPLICATION_PATH "/../doctrine/migrations"
doctrine.sql_path           = APPLICATION_PATH "/../doctrine/data/sql"
doctrine.yaml_schema_path   = APPLICATION_PATH "/../doctrine/schema"
doctrine.generate_models_options.pearStyle              = true
doctrine.generate_models_options.generateTableClasses   = false
doctrine.generate_models_options.generateBaseClasses    = true
doctrine.generate_models_options.baseClassesDirectory   = null
doctrine.generate_models_options.classPrefixFiles       = false
doctrine.generate_models_options.classPrefix            = 'Model_'
doctrine.generate_models_options.baseClassPrefix        = 'Base_'

Majd a Bootstrap.php-ban kell a Doctrine-hez kapcsolódó initet elkészíteni, hogy az alkalmazás szintjén müködjön:

public function _initDoctrine()
{
    require_once 'Doctrine.php';
    $autoloader = Zend_Loader_Autoloader::getInstance();
    $autoloader->registerNamespace('sfYaml')->pushAutoloader(array('Doctrine', 'autoload'), 'sfYaml');
    $doctrineConfig = $this->getOption('doctrine');
 
    $manager = Doctrine_Manager::getInstance();
    $conn = $manager->openConnection($doctrineConfig['connection_string']);
    $conn->setCharset('utf8');
    return $manager;
}

Az application/cli_bootstrap.php-ba a következő kerül:

<?php
// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', dirname(__FILE__));
 
// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
 
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));
 
/** Zend_Application */
require_once 'Zend/Application.php';
 
// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

Készíteni kell a projekt mappában egy scripts/doctrine-cli állományt a következő tartalommal:

#!/usr/bin/env php
<?php
define('APPLICATION_ENV', 'development');
require realpath(dirname(__FILE__) . '/../application/cli_bootstrap.php');
 
$application->getBootstrap()->bootstrap('doctrine');
$options = $application->getOption('doctrine'); 
 
$cli = new Doctrine_Cli($options);
$cli->run($_SERVER['argv']);

Majd futtathatóvá kell tenni. Ez lesz a parancssori interfész amelyen keresztül utasításokat lehet adni az ORM-nek. Ezzel a Doctrine teljesen beágyazódik a Zend Frameworkbe és a projekt honlapján lévő dokumentációk alapján már lehet is gyártani a szebbnél szebb modelleket és tolni a kódból a DQL-t :)

2009
04
márc

Rowset cache-be pakolása


A Zend Framework egyik igen kedvelt szolgáltatása – amire nap mint nap szoktam támaszkodni és szinte már elkerülhetetlen a használata – a Zend_Cache modul. Ha a szerver erőforrások lehetővé teszik, akkor előszeretettel építkezek memcached backendre, eddig elég jól bevált. Volt már például, hogy egy nagyon nagy látogatottságú portál esetén a memcached segítségével a szerver load-ot kevesebb mint a tizedére sikerült visszavenni.

Visszatérő dolog, hogy a cache-ben egy lekérdezés eredményeként visszakapott Zend_Db_Table_Rowset ovjektumot szeretnék letárolni. Persze általában sokkal praktikusabb és optimálisabb az eredményt tömbként átadni a cache-nek és erre a toArray() metódus egyszerű megoldást kínál, azonban ezzel el is veszítjük a Zend_Db által kínált fantasztikus adatbázis absztrakció nyújtotta kényelmet. Például a tömb használatával el lehet felejteni egy adott adatbázis rekordhoz kapcsolódó rekordok közvetlen elérését a findParentRow() és findDependentRowset() függvényekkel, a rekord módosításáról nem is beszélve. A Zend_Db_Table_Rowset cache-ben történő tárolásával azonban az a baj, hogy a szerializáció alkalmával a rowset un. disconnected állapotba kerül, mivel a szerializált ovjektum stringként tárolódik, ami az adatbázisra nézve komoly biztonsági kockázatokat jelentene (db jelszavakra és hasonlókra). Tehát amikor a cache-ből kiolvassuk az adott rowset-et akkor a rekordok mezői ugyan elérhetőek, de nagyjából ugyanannyit érünk vele mintha tömbről volna szó, mert a disconnected állapotban a módosítás ugyanúgy nem működik és a többi közvetlen adatelérést biztosító funkció is használhatatlan.

A megoldás persze pofon egyszerű, hiszen mindössze annyit kell elérni, hogy a deszerializált rowset adatbázis kapcsolatát “felélesszük”. Erre pedig a setTable() függvény tökéletesen megfelel:

if (!$items = $cache->load('items')) {
	$select = $product->select() /* ... */;
	$items = $product->fetchAll($select);
	$cache->save($items, 'items');
}
$items->setTable($product);

A ZF Programmer’s Reference Guide ide vonatkozó passzusa: Serializing and Unserializing a Rowset .

2009
10
feb

Zend_Paginator bug


A minap komolyan meggyűlt a bajom a Zend_Paginator komponenssel. Egy HAVING utasítással egészítettem ki a Zend_Db_Select-et melyet a paginatornak akartam adni paraméterként, de állandóan arra panaszkodott, hogy ismeretlen oszlopra hivatkozok a having kifejezésben:

SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘c’ in ‘having clause’ 

Lefuttattam a query-t közvetlenül MySQL-ben, de ott nem volt tapasztalható a hiba, így a Zend Framework kódját és a fórumokat kezdtem el bújni, majd rövidesen rá is bukkantam a megoldásra: [Zend_Paginator] Bug when using complex Zend_Db_Select. A rendellenesség a Zend_Paginator_Adapter_DbSelect setRowCount() függvényében keresendő, mivel ez a függvény már “lebutítva” kapja meg a lekérdezést, amely ekkor már csak egy oszlopot tartalmazhat. Ha nem így történik akkor kivételre fut. Megértve, hogy pontosan mi okozza a hibát elég volt annyit alakítani a kódon, hogy a paginatornak nem az eredeti lekérdezést adtam át, hanem egy butítottat:

// $select1-ben van a komplex lekérdezés
$select2 = $company->select()->setIntegrityCheck(false);
$select2-&gt;from($select1, array('COUNT(*)'));
$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_DbSelect($select2));

Álljon hát itt ez a bejegyzés, hogy máskor könnyen visszakereshető legyen a megoldás!

2008
01
szept

SVN statisztikák



A fejlesztett kódok nagy része – amelyben ilyen-olyan módon közreműködök vagy dolgozok  - Subversion tárolóban vannak. Több szempontból is jó ez. Azon túl, hogy több fejlesztő tud egyszerre egyazon törzsön dolgozni vagy saját ágat készíteni, biztonsági mentésként is szolgál, hiszen egy-egy nagyobb mérföldkő elérésekor elegendő egy új tag-et létrehozni és a forráskód megjelölt állapotára bármikor vissza lehet “lapozni”. Ez pedig szükségtelenné teszi az ezredik biztonsági mentés elkészítését, hiszen elegendő napi mentést készíteni az SVN fáról, abban minden korábbi változat megtalálható.

Webes szolgáltatás esetén sokat tud egyszerűsíteni a kiadások és javítások közzétételén is, hiszen ha a szolgáltatás az SVN repository egy adot tag-jéből épül fel, akkor elegendő azon egy update parancsot lefuttatni és az biztosan lefrissíti a tárolóban lévő összes megjelölt váltatást. Nem felejtődhet ki semmi, jó eséllyel elmarad a szopódás. Persze ezt meg lehetne még fejelni a Phingel. A közeljövőben tervezünk megvalósítani az egyik munkához egy komplett Phingre és SVN-re épülő release metódust, ahol a teljes kód Subversion-ben tárolódik. A Phing segítségével kerül majd a kód és az SQL delták a staging majd később a production szerverre, rsync közbeiktatásával. Gyönyörűen automatizált lesz minden és – az előzetes tesztek alapján – a megvalósítás sem tűnik bonyolultnak.

Maga az Subversion tároló centralizált így kiválóan alkalmas arra, hogy bizonyos kimutatásokat készítsünk belőle. A Cacti például remek eszköz az összegyűjtött numerikus adatok megjelenítésére grafikonok formájában, de sajnos nem találtam igazán jó SVN riportáló cuccot, amit felhasználhattam volna grafikonok generálásához. Ez inspirált arra, hogy összedobjak egy minimális, de a célnak megfelelő kis PHP scriptet, ami például Cactiból használható adatbemeneti metódusként (Data Input Method). Tényleg minimál, csak a következő dolgokat tudja:

  • Az SVN-ben tárolt adatok filerendszer statisztikája: forrásfájlok, könyvtárak és az összes állomány száma.
  • Forrásállományokban lévő kódsorok száma.
  • SVN revízió állapota az idősíkon.
  • Tag-ek és branch-ek száma az idősíkon.

Sajnos a script használatához szükség van a Subversion PHP kiterjesztés telepítésére is. Én speciel a PECL tárolóból telepítettem azt. Helyi svn repóból tud dolgozni (file://), de csak egy kicsit kell rajta hegeszteni ahhoz, hogy távoli tárolót is elérjen. Ha felmerül rá az igény és más nem oldja meg, akkor összedobom azt is, csak kérjétek ;p

Kiexportáltam Cactiból az általam készített adat- és diagram mintákat, így még egyszerűbb munkára bírni az információbegyűjtőt. Talán másnak is hasznára válik majd a cucc. A telepítés:

  1. Az svn_stats.php-t be kell másolni a <cacti_path>/scripts/ könyvtárba.
  2. Csipkebokor vessző.
  3. Az svn_stats.php-ben ki kell cserélni a (4. sor) $_countExts változó értékét a kívánt kiterjesztésre (pl.: c, php, js, cs…). Array, tehát több értéket is kaphat. Ezen kiterjesztésű állományok sorait fogja számolgatni. Az $_excludeUrls változó értékében felsorolt elérési utakat figyelmen kívül hagyja (pl.: külső libek). Szintén array.
  4. Az XML állományokat beszippantjuk  a Cacti felületén keresztül: “Import Templates”.
  5. “New Graphs” alatt lehet is készíteni új grafikont, ott fogja bekérni a repo URL-t.

Letöltés: svn_stats-0.1.zip

2008
06
ápr

String típuskonverzió kérdés


Azt olvastam a Zend PHP 5 Certification Study Guide-ban, hogy egy string típusú változó értékének növelésekor 1-et kapunk vissza eredményül, mert a ++ operátor integer típusúvá konvertálja a string értékét (amely így nulla értéket vesz fel) majd azt növeli egyel.

$a = 'Test';
echo ++$a;

Nálam mégis Tesu szerepel a kimenetben. Miért?