A minap eszembe jutott, hogy milyen jó is lenne, ha a Sublime jelezné, ha van valami elgépelés az éppen szerkesztett fájlba. És bár létezik elég sok megoldás a problémára, ami ráadásul még több nyelvet is (Python, CSS, JavaScript, stb.) támogat, de azért mégis csak sokkal mókásabb egy sajátot írni.

A megoldás lényege, hogy valahol a vinyón van egy PHP bináris, amit megfuttatunk a -l kapcsolóval, ami nem futtatja a fájlt, hanem csak szintaxisra ellenőriz. A válasz a PHP szokásos "parse error" szövege lesz, ami azt írja ki, hogy hol hasalt el a fordító, ami nem feltétlen esik egybe azzal, hogy hol van a hiba. A kódot többféleképpen meg lehetne valósítani (pl. parancsként, amit menüből, vagy billentyűkombinációra lehet futtatni), én azt választottam, hogy mentés eseményre automatikusan fusson le, ha megfelelő a fájl kiterjesztése:

import sublime, sublime_plugin, os, subprocess, re

class PhpLintEventListener(sublime_plugin.EventListener):
    key = "php_lint_syntax_error"
    
    def on_post_save(self, view):
        if not view.settings().has("php_lint"):
            return
        
        config = view.settings().get("php_lint")
        
        bin_path = config.get("binary", "")
        name, ext = os.path.splitext(view.file_name())
        
        if not bin_path or ext not in config.get("extensions", []):
            return
        
        view.erase_regions(self.key)
        view.erase_status(self.key)
        
        shell = sublime.platform() == "windows"
        p = subprocess.Popen([bin_path, "-l",  view.file_name()], shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (stdout, stderr) = p.communicate()
        
        if "" == stderr:
            data = stdout
        else:
            data = stderr
        
        result = re.match(r"(.+?) in (.+?) on line ([0-9]+).*", data, re.M | re.S)
        if result:
            error = result.group(1)
            line = result.group(3)
            
            view.add_regions(self.key, [view.full_line(view.text_point(int(line) - 1, 0))], "markup.deleted", "dot")
            view.set_status(self.key, error + " on line " + line)

Nem egy bonyolult jószág, ha létezik a config és a fájl kiterjesztése benne van a configban megadott listában, akkor megfuttatjuk a PHP binárist és megkeressük a kimenetében, hogy hányadik sorban volt a hiba. A hibás sort kiemeljük és a hiba szövegét a státuszba ki is írjuk. A szükséges config pedig valahogy így néz ki:

"php_lint" : {
    "binary" : "d:\\php\\5.2\\php.exe",
    "extensions" : [".php"]
}

Ezt lehet pakolni a Preferences > Settings - User részbe vagy akár a projekt beállításai közé is (a .sublime-project fájlon belül a "settings" blokkba).

Épp a sorozatos oldal második verziójának fejlesztésébe készülök komolyabban belevágni. Ennek kapcsán eszembe jutott, hogy milyen jó bejegyzést lehetne ebből kanyarítani. Mármint abból, hogy milyen módjai vannak a kényelmes otthoni projekt fejlesztésnek.

Összepiszkíthatnánk például a jelenlegi operációs rendszerünket mindenféle web- és egyéb szerverekkel, de ez nem egy szép megoldás. Telepíthetnénk egy extra operációs rendszert egy külön partícióra, de az, hogy a jelenlegiből ki kell lépni, oda pedig be... esélytelenné teszi a fejlesztést egy kellően lusta ember számára (én :)). A legjobb lenne egy külön gép, amit hálózaton keresztül elérünk. Ennek a legköltséghatékonyabb változata a virtuális gép, így ezen a vonalon fogunk tovább haladni. Hozzávalók egy személyre:

Pro tipp: az Ubuntu szerver iso-ját ne a VirtualBox telepítése közben töltsük le, mert a VirtualBox telepítés közben hajlamos időlegesen megkavarni az internet kapcsolatot és jól meg fog szakadni a letöltés.

Telepítés

A letöltések és a szokásos next-next-finish telepítés befejeztével hozzáadhatunk egy új Linux / Ubuntu típusú virtuális gépet és egy új virtuális merevlemezt a varázsló segítségével. Még mielőtt elindítanánk, a Settings / Shared Folders részben érdemes hozzáadni egy könyvtárat a saját gépünkről, amiben a projekteket fogjuk tárolni, hogy ne kelljen később Samba-t telepíteni. Az Auto-mount részt ne pipáljuk be, azt később kézzel beállítjuk majd. A Settings / Network részbe se árthat benézni és az Adapter 1-et ízlés szerint megpiszkálgatni. Nálam a Bridged Adapter volt a nyerő az Attached to részben. A VirtualBox elég okos és az első indításnál felajánlja, hogy etessünk meg a géppel egy CD-t. Ajánlatos itt beadagolni a korábban letöltött Ubuntu szerver iso-ját, mert az nagyban megkönnyíti a telepítést. :)

Az Ubuntu nem egy bonyolult jószág, így valószínűleg a telepítéssel se lesznek problémák. Amíg fut, tetszés szerint lehet fogyasztani a bespájzolt hideg élelmet illetve sört. Hogy, hogy nem előbb-utóbb eljutunk egy ilyen képernyőhöz:

Itt a LAMP server-t az Apache-PHP-MySQL szentháromság miatt erősen ajánlott bepipálni, mert valószínűleg szükségünk lesz rá. Én telepíteni szoktam OpenSSH server-t is, mert már annyira hozzászoktam a PuTTY használatához. :)
Ha ezzel megvagyunk, az első belépés után rögtön érdemes is nyomni egy frissítést:

sudo apt-get update
sudo apt-get upgrade

Valamint telepíteni a VirtualBox Guest csomagját a jobb támogatás érdekében:

sudo apt-get install virtualbox-guest-utils

A projekt könyvtárak beállítása

A fejlesztés a /var/www/ könyvtárban fog történni a virtuális gép nézőpontjából. Hogy kényelmes legyen, érdemes ezt kivezetni valahova a gazda gép merevlemezére. Ezért állítottuk be korábban a Shared Folders részt. A művelet elvégzéséhez a következő sorokat kell felvenni az /etc/fstab fájl végére (na jó, a komment nem kötelező :)):

# VirtualBox Shared Folder
projects /var/www vboxsf auto,rw,uid=1000,gid=33 0 0

A projects helyére a Shared Folder felvitelénél megadott nevet kell írni. Az uid=1000 a rendszer telepítése során létrehozott felhasználó, a gid=33 a www-data csoport. A módszernek az az előnye is megvan a Samba-féle megoldással szemben, hogy nem kell futnia a virtuális gépnek ahhoz, hogy a fájlokat módosítani tudjuk. Az uid és gid értékeként megadható számok után az /etc/passwd és /etc/group fájlokban lehet érdeklődni.

Szerver elérés

Ha minden jól ment, az ifconfig eth0 által kidobott ip címen el tudjuk érni a gépet. Ha nem, akkor a VirtualBox beállítások Network részénél érdemes a lehetőségeket végigpróbálgatni, hogy melyik jön be (ahány hálózat, annyi beállítás).
Az egyszerűséget szem előtt tartva érdemes valami egyedi, egyébként nem létező domain-el felruházni a gépet, hogy ne ip címeket kelljen mindig írogatni. Nálam a *.vbox domain-ek vannak erre fenntartva. A legegyszerűbb, ha a hosts fájlba felvesszük őket (Windows: c:\Windows\System32\drivers\etc\hosts, Linux: /etc/hosts), pl.:

192.168.1.117 default.vbox
192.168.1.117 projektneve.vbox
192.168.1.117 subdomain.projektneve.vbox

Apache, PHP, MySQL beállítás

Az egyes projektek a /var/www/projektneve.vbox/ könyvtárban kapnak helyet. Az alap Apache confignak csinálunk egy default.vbox nevet/könyvtárat:

mkdir /var/www/default.vbox
sudo mcedit /etc/apache2/sites-available/default

A megnyíló fájlban a DocmentRoot és a Directory résznél szereplő /var/www/ után kell még egy default.vbox/-ot írni. Aztán pedig

sudo service apache2 reload

A /etc/apache2/httpd.conf-ba esetleg még érdemes egy ServerName default.vbox sort beledobni és újraindítani az Apache-ot, hogy megszűnjön a "Could not reliably determine the server's fully qualified domain name" hibaüzenet.

A projekt VirtualHost beállításai a /etc/apache2/sites-available/projektneve.conf fájlba kerülnek. Egy egyszerű példa, hogy nagyjából mit is tartalmaz egy ilyen:

<VirtualHost *:80>
    ServerName projektneve.vbox
    ServerAlias www.projektneve.vbox

    DocumentRoot /var/www/projektneve.vbox/www
    <Directory />
        Options FollowSymLinks
        AllowOverride All
    </Directory>
    <Directory /var/www/projektneve.vbox/www/>
        Options FollowSymLinks
        AllowOverride All
    </Directory>

    ErrorLog /var/www/projektneve.vbox/log/apache_www_error.log
    CustomLog /var/www/projektneve.vbox/log/apache_www_access.log combined

    LogLevel warn
</VirtualHost>

A kész site beállításokat a következő parancsokkal tudjuk engedélyezni:

sudo a2ensite projektneve.conf
sudo service apache2 reload

A MySQL-en nincs sok beállítani való, telepítéskor elkérte a root felhasználójához az új jelszót, mást meg nem nagyon kell piszkálni. A vizuálisabb típusoknak esetleg egy phpmyadmin telepítése még nem árthat:

sudo apt-get install phpmyadmin

Ha kértük, a csomagból telepítés során be is köti magát az Apache-ba a /etc/apache2/conf.d/phpmyadmin.conf segítségével és a http://ipcím/phpmyadmin/ címen el is érhető az oldal. Ehelyett, ha kedvünk tartja létrehozhatunk neki egy saját VirtualHost-ot is akár.

A PHP beállításánál érdemes arra törekedni, hogy minél jobban hasonlítson az éles rendszerre, így ezzel kapcsolatban nem sokat tudok mondani.

Első körben ennyi, kellemes telepítgetést. :)

Bár nem tegnap történt, hogy az 5.3-as sorozat első verziója kijött, gondoltam kicsit körbejárom a névtelen függvények témakörét az ősidőktől napjainking. Az ősidő alatt itt természetesen a 4-es verziót értem, amikor még csak a call_user_func állt a rendelkezésünkre. Lássuk, hogyan is ment ez.

A create_function módszer (attól tekintsünk el, hogy a __FUNCTION__ varázskonstans csak a 4.3-as verzióban jelent meg):

$f = create_function('', 'echo __FUNCTION__ . "\n";');
call_user_func($f);

A függvény neve stringként módszer:

$f = 'myFunction';
call_user_func($f);

Az osztály és a statikus metódus neve tömbként:

$f = array('MyClass', 'myStaticFunction');
call_user_func($f);

Az objektum és a metódus neve tömbként:

$c = new MyClass();
$f = array($c, 'myFunction');
call_user_func($f);

Az osztály és a statikus metódus neve stringként (5.2.3-as PHP-tól):

$f = 'MyClass::myStaticFunction';
call_user_func($f);

Az objektum meghívása (5.3.0-s PHP-tól):

$c = new MyClass();
call_user_func($c);

És ezzel el is érkeztünk a jelenhez. Az 5.3.0-s verzióval megjelentek a closure-ök (az újfajta névtelen függvények), amolyan JavaScript-es definiálással:

$f = function () {
};

Természetesen a teljes visszafelé kompatibilitásról ne is álmodjunk ezért kezdjük is el végignézni az előző módokat, hogy mi működik továbbra is:

A create_function módszer megy továbbra is (emlékeim szerint 5.3 előtt is működött így, ezért ez nem túl meglepő):

$f = create_function('', 'echo __FUNCTION__ . "\n";');
$f();

A függvény neve stringként módszer (működik):

$f = 'myFunction';
$f();

Itt sajnos meg is állhatunk. A tömbös rendszerek (PHP Fatal error: Function name must be a string) és az osztály+statikus metódus neve stringként (PHP Fatal error: Call to undefined function MyClass::myStaticFunction()) nem működik.

Maradt még az objektum direkt meghívása, ami működik, de ez sem túl nagy meglepetés, mivel az 5.3.0-s verzióbal vezették be hozzá az új varázsmetódust:

$c = new MyClass();
$c();

Ilyenkor (és a call_user_func-os esetben is) az osztály __invoke() metódusa hívódik meg.

Ezzel azt hiszem nagyjából ki is veséztük a különböző callback típusok és névtelen függvények hívhatóságát. Maradt még némi scope kérdés, de az házi feladat (annyit megsúgok, hogy a use a kulcsszó). Egy kis segítség hozzá:

Úgy tűnik tartom magamat az elmúlt években már megszokott kontent mennyiséghez. Most is csak azért írok, hogy hírt adjak arról, hogy összedobtam egy kis oldalt, aminek segítségével nyomon lehet követni ismerőseink és saját magunk sorozatnézési szokásait. Használjátok egészséggel. :)

Ja... a cím: s01e01.hu

Már lassan egy éve nem volt új bejegyzés, gondoltam ideje írni valamit. :) Régebben már volt szó a PHP objektum-orientált adatbázis elérési felületéről, a PDO-ról. Mivel utóbb inkább csak a használatára tértem ki, nem lett megemlítve, hogy ezekből az osztályokból származtathatjuk saját osztályainkat is, ami alapvetően egy jó dolog tud lenni.

Vegyünk is egy egyszerű példát. Tegyük fel azt, hogy már annyira jók vagyunk, hogy az SQL szerverrel is csak l33t nyelven vagyunk hajlandóak kommunikálni. Persze a mysqld (vagy egyéb tetszőleges daemon) nem ilyen megértő, és folyamatosan panaszkodik, hogy nem ért meg minket. A probléma elsimítása érdekében származtatunk a beépített PDO osztályból egy sajátot:

class L33tPDO extends PDO {
	private static $from = array(
		's3l3ct', 'fr0m', 'wh3r3', 's3t', '1ns3rt', '1nt0', 'upd4t3', '0rd3r', '4sc', 'd3sc', 'l3ft', 'r1ght', '1nn3r', 'j01n'
	);
	private static $to = array(
		'SELECT', 'FROM', 'WHERE', 'SET', 'INSERT', 'INTO', 'UPDATE', 'ORDER', 'ASC', 'DESC', 'LEFT', 'RIGHT', 'INNER', 'JOIN'
	);
	public function query($statement, $type = false, $param1 = false, $param2 = false) {
		$statement = $this->convertStatement($statement);
		
		if ($type && $param1 && $param2) {
			return parent::query($statement, $type, $param1, $param2);
		}
		if ($type && $param1) {
			return parent::query($statement, $type, $param1);
		}
		if ($type) {
			return parent::query($statement, $type);
		}
		
		return parent::query($statement);
	}
	public function prepare($statement, $driver_options = array()) {
		$statement = $this->convertStatement($statement);
		parent::prepare($statement, $driver_options);
	}
	private function convertStatement($statement) {
		return str_ireplace(self::$from, self::$to, $statement);
	}
}

Hogy takarékoskodjunk a hellyel, a kulcsszavak listája nem teljes. Nézzünk is egy példát a használatra:

$conn = new L33tPDO('pgsql:host=localhost port=5432 dbname=test user=test password=test');

$stmt = $conn->query("s3l3ct * fr0m users wh3r3 id > 1 0rd3r by login_name 4sc" );

var_dump($stmt->fetchAll());

Bár a felhozott példa nem igényli, a PDOStatement osztályból is származtathatunk sajátot, csak meg kell mondanunk a saját PDO osztályunknak, hogy az általunk írt PDOStatement-tet adja vissza a megfelelő függvényei. Ehhez bővítsük ki a L33tPDO osztályunkat egy konstruktorral:

function __construct($dsn, $username = null, $password = null, $driver_options = array()) {
	parent::__construct($dsn, $username, $password, $driver_options);
	
	$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('L33tPDOStatement', array($this)));
}

És egy hozzá passzoló statement osztály:

class L33tPDOStatement extends PDOStatement {
	private $dbh;
	protected function __construct($dbh) {
		$this -> dbh = $dbh;
	}
}

Ennyit mára. További kellemes kódolást.

Talán nem túlzás azt állítani, hogy a Firebug a legjobb dolgok közé tartozik, ami manapság egy JavaScript fejlesztővel történhetnek. A több mint 10 millió letöltés, valamint a tény, hogy - bár maga is kiegészítő - kiegészítőket fejlesztenek hozzá elég bizonyítékul is szolgálhat ennek alátámasztására. Egy ilyen kiegészítőbe futottam bele még valamikor a múlt héten, ami a FirePHP nevet viseli (a nevével ellentétben bármely más szerver oldali nyelvvel használható, ami képes header-öket kiküldeni). Nincs is másra szükségünk csak egy Firefox-ra (ez remélhetőleg már mindenkinek van :)), egy Firebug-ra (ez is esélyes lehet, hogy akad a közelben) és a FirePHP-ra. Meg egy kis szerveroldali scriptre, ami szintén a FirePHP oldaláról tölthető le.

FirePHP

A kiterjesztés mögött rejlő ötlet rettentően egyszerű (és talán éppen ezért olyan fantasztikus): küldjünk ki pár speciális fejlécet valamiféle JSON tartalommal, amit kliens oldalon a kiegészítő feldolgoz és a Firebug segítségével szépen meg is jelenít. Ezek lehetnek több prioritásba sorolható egyszerű szöveges üzenetek (hasonlóak a Firebug beépített üzeneteihez), táblázatok, szépen formázott backtrace-ek vagy akár print_r()-hez hasonló információk változókról.

Tovább...

A szokásos "napi" php.net böngészgetésem során bukkantam rá a lentebb olvasható kódrészletre (bár nem egy bonyolult dolog, de mégsem jutott volna magamtól eszembe), amit szerintem kötelezővé kellene tenni minden egyes PHP alapú alkalmazás fejlesztési időszaka alatt. A kód nem csinál mást, mint hogy minden hibát kivétellé alakít át, így ha nincsenek elkapva, akkor egy egyszerű E_NOTICE-tól is lehal az oldal fatal error-ral, az error_reporting beállítástól függetlenül. :)

function error_handler($errno, $errstr, $errfile, $errline)
{
    throw new ErrorException(
        $errstr, 0, $errno,
        $errfile, $errline
    );
}
set_error_handler('error_handler');

Egyéni ízlés kérdése, de én még az alábbi kóddal kiegészíteném, hogy fatal error-ok helyett inkább valami olvasható dolgot kapjak:

function exception_handler($ex)
{
	print('<pre>'.$ex.'</pre>');
}
set_exception_handler('exception_handler');

És hogy miért is lenne hasznos? Elég sokan kikapcsolják az E_NOTICE-ok jelzését még fejlesztés alatt is, mondván hogy az nem számít. Pedig elég sok kisebb hibára (pl. változó név vagy tömb kulcs elgépelésekre) hívhatná fel a figyelmet, amik így csak a tesztelési időszakban derülnek ki (ha kiderülnek). Emellett biztonsági haszna is van, mint az a php.net-en is olvasható.

Néha a lustaságomon felül kerekedik az a vágy, hogy új programozási nyelveket ismerjek meg. Ennek lett a legfrissebb áldozata a Python. Bár már volt vele dolgom régebben is, de körülbelül csak egy Fibonacci-számokat számoló rekurzív függvény megvalósításáig jutottam (az is nagy valószínűséggel valami példakód volt valamelyik segédletből).

Magáról a nyelvről azt lehet elmondani, hogy feltűnően szépnek mondható ahhoz képest, hogy a Pascal-stílusú nyelvek közé tartozik. És nem csak egyszerűen szép, hanem törekszik arra, hogy a benne írt kód is szép legyen (ahogy az a Python filozófiáinak listájában is szerepel: "Beautiful is better than ugly."). Persze ehhez az is kell, hogy ne csak Python nyelven, de Python stílusban is programozunk.
A kinézetet tekintve legszembetűnőbb különbség más nyelvekhez képest a kód kötelező indentálása. Nincsenek sorvégi pontok, pontosvesszők, if-eket és egyéb blokkszerkezeteket lezáró hullámos zárójelek vagy kulcsszavak. Minden az új sor karakterek és az indentálás alapján dől el. De mivel kódolás közben a nagy többség amúgy is szokott új sorokat és tabokat használni (és mivel a kötelező indentálás az esetek közel 100%-ában megegyezik azzal, amúgy is használnánk), ezért elég gyorsan hozzá lehet szokni ahhoz, hogy tulajdonképpen kevesebbet kell gépelni. Persze ez még kevés ahhoz, hogy használható is legyen valamire. Mivel napjaim nagy részét a webprogramozás teszi ki, ezért a szintaktikai ismerkedés után első dolgom az volt, hogy utánanézzek, mire is képes a kígyó a weben...

Tovább...

Régebben írtam a PHP sablonnyelvként való használatáról. Lehet szépíteni a dolgokat, de hosszú távon ennek a használata (főleg megfelelő szerkesztőprogrambeli makrók nélkül) kényelmetlen, hosszadalmas és még a makrók használatával együtt is csúnya, olvashatatlan. A Smarty egyetlen pozitívuma az, hogy egyszerűen és gyorsan lehet írni, kényelmesen lehet használni. Persze ez nem vigasz azoknak, akik nem akarnak egy több ezer kódsorból álló sablon kezelőt használni csak emiatt a kényelem miatt.

Nagyrészt így vagyok vele én is. Gyakorlatilag a szintaktikájának az alapjait leszámítva nem lenne szükségem semmire a Smarty-ból. Nem érdekel, hogy korlátozni lehet a sablon íróját, hogy a PHP eszköztár csak egy részét használhassa (minek is korlátoznám magam...). Nem érdekel, hogy könnyen és gyorsan lehet hozzá plugineket írni (ha az egész PHP használható lenne, erre nincs is túl nagy szükség). Nincs szükségem semmi ilyesmire.
Sokkal inkább vonz az, ha a nyelvet gyorsan lehet gépelni (pl. mert rövid, minimális speciális karaktert használ és logikus a szerkezete). Az se egy hátrány, ha az elkészült sablon valamilyen szinten olvasható lesz. Nem lenne rossz még, ha egy elfogadható minőségű PHP kódot generálna az én sablonomból, ami aztán elfogadható (a lehető leggyorsabb) sebességgel futna. Az első kettőt még csak-csak teljesíti, de az utolsóval eléggé hadilábon áll a Smarty. Így itt az idő valami más után nézni...

Tovább...

Hadd mutassak be néhány trükköt a PHP egy régi, de annál hasznosabb nyelvi elemével, aminek a neve: list(). Valószínűleg sokan vannak, akik még hallomásból sem ismerik ezt a szerkezetet, így az ő kedvükért kezdjük a legelején.

Tovább...