Időnként azóta is előveszem a Got, most épp az egyik tervezett melóhelyi átállás adta az ötletet a következő játékhoz.
Van egy solr core-unk és egy nodejs microservice-ünk, amik ugyanazokat az adatokat tárolják, csak picit máshogy, és másutt is használjuk őket. Ezt akarjuk kiváltani egyetlen kibővített core-ral, ami mindkét célra alkalmas.
Ma egy éve, hogy autótulajdonos lettem. Ezalatt beleraktam 14.475km-t, és most kicsit számolgatni fogok, mennyibe került nekem ez az egész, és hogy vajon megérte-e.
A kocsi egy idén 14 éves, benzines, 1.4-es Corolla, most már bőven 245 ezer km felett van benne összesen. A vételár, átírás, első szervíz együtt majdnem 1.1M Ft volt.
32 alkalommal tankoltam az egy év alatt, a maival együtt összesen 365.432Ft-ért 1064 litert. Összesítve azért durvának tűnik, de mentem is vele rendesen. Eleinte Lukoilhoz jártam, aztán átálltam az OMV Super 95-ére, mert bár drágább, érezhetően erősebb vele az autó, és csendesebb is. Ezzel az átlagfogyasztása 7.3 l/100km.
Komoly baja nem volt. A hátsó lambda-szondát kellett cserélni, ez volt az egyetlen nagy és váratlan kiadás. A nem tankolás jellegű kiadások összesen 395.299 forintot tettek ki, ebben benne van az első szervíz (70k), egy téligumi szett (71k), a lambda-szonda (40k), két gumiszerelés futómű-beállítással (egyenként 18k) valamint egy kormány- és váltóbőrözés (30k). A többi ezekhez képest apróság, mint az izzócsere egy ezresért, vagy a fagyálló 400-ért, de ilyesmikből sok van. Felírtam még azt az ecsetet is, amivel a festéket kentük az olyan helyekre, ahol már elkezdett rozsdállni, és nem látványos helyen van. Meglepődtem azért, hogy ekkora lett az összeg.
Az első évi biztosításom 35k volt, a második évi 37k, baleseti adóval. Gépjárműadó évi 13k.
És akkor most nézzük a mérleg másik oldalát: összejön-e annyi, hogy azt mondhassam, megérte?
Az biztos, hogy bár egyik oldalról nagy kötöttség, a másik oldalt nagy szabadságot ad. Kirándulni van kedvem, vagy vasárnap délután át kell mennem a város másik végére, ami BKV-val három óra? Beülök és megyek. Számtalan egynapos, és számos (hosszú)hétvégés kirándulást csináltam vele, általában nem egyedül, bár ilyen is volt, mint például múlt kedden, mikor láttam, hogy Dobogókőn havazik, csak bevágtam magam és mentem. Tervezett utak voltak Veszprémbe, Egerbe, Debrecenbe, Tokajba, Nyíregyházára, Szombathelyre – tényleg sokfelé. Meg persze haza Dunaújvárosba. Akkor is jól jött, amikor valakit hirtelen kórházba kellett vinni.
Bő egy hónapja ezzel járok munkába is, mikor 300Ft volt a benzin literje úgy döntöttem, kipróbálom. Elég kényelmes egyébként, bár persze tudom, hogy drága, és hiányzik az a napi bő óra olvasás. De így könnyebben megtehetem, hogy hétfőnként a Városligetben és az Andrássny úton sétálok egy nagyot, illetve megtehetem, hogy pénteken melóból hamarabb lelépve felmegyek valamelyik környéki hegyre élvezni a napsütést és a kilátást. Azóta jobban ismerem, és több részén jártam a városnak, mint előtte összesen.
Háromszor segítettem költözni, lehajtott hátsó üléssorral hatalmas helyem van, egyik alkalommal egy aránylag nagy szekrény tűnt el a gép gyomrában, és még pakoltunk mellé.
Szóval rengeteg olyan élményt adott, ami kocsi nélkül nem lett volna meg, gondolok itt főleg a kirándulásokra, de Ingresses eseménynél is nemegyszer voltam sofőr. Nyúlott az arcom, amikor az összegeket megláttam, de ezen kalandokra visszagondolva mégis azt mondom: megérte megvenni és fenntartani az autót.
A Go egy viszonylag új nyelv a Google-től. Típusos, egy darab binárisba fordított (azaz piszok gyors) és nyelvi szinten támogatja a párhuzamosságot. Szemeztem vele egy ideje, aztán nemrég kipróbáltam.
Az első program, amit írtam benne egy twitter értesítő volt, mert miért ne? Két elég egyszerű dolog kombinációja, vannak rájuk kész libek, és életszagú.
Választott twitter libem az anaconda, libnotify-ra pedig a go-notify. A projekt mappa létrehozása után gyorsan go geteltem is őket; a twitter fejlesztői oldalán pedig regisztráltam egy új appot, szerezve négy tokent. Ezeket env változóban passzolom át a kódnak (mert sehogy nem sikerült json-ból beolvasnom, azóta leesett, hogy azért, mert kisbetűs névvel voltak a structomban), így:
Amivel aztán initelhetjük az anacondát, és rögtön próbáljuk is ki, minden stimmel-e:
A notificationöket megjelenítő kód külön függvénybe került már csak azért is, mert egy bug miatt minden notif előtt initelni, utána pedig uninitelni kell a libraryt - a kettő között pár pásodperc késleltetéssel. Extraként bekerült a user képének letöltése, rengeteg hibakezeléssel :)
Mivel go prefixszel hívom mindenütt, külön szálon indul, a sleep véletlen sem fogja meg a fő threadet.
Szóval már bejelentkeztünk, megvan az első notifünk is, még hátravan a tweetekre várás és azokról értesítés összerakása. Ezzel a résszel szórakoztam a legtöbbet, az anaconda dokumentációja nem épp részletes a streamekkel kapcsolatban, úgyhogy a kódból szedtem össze az infot - nem volt egyszerű egy tökúj nyelven.
Először a twittertől kell elkérnünk a user streamjét - ez azokat a tweeteket jelenti valós időben, amiket az általa követettek írnak. A lib elfedi ennek a részleteit, és a végén egy go channelbe pakolja nekünk, ebből kell végtelenciklusban olvasnunk. Ide jöhet sokfajta üzenet, minket ezek közül csak a tweetek érdekelnek - a típus eldöntése a goban elsőre nem egyértelmű, de utólag nagyon kellemes és praktikus megoldás.
A teljes kód fenn van githubon. Az egészet kb. egy délután alatt raktam össze, és nagyon megtetszett a nyelv. Azóta nekiestem mégegy projektnek, az melóhoz kapcsolódik, egy nodejs microservice-t kezdtem újrairni goban, egy hétvége alatt összeálltak a fő funkciók, azonos körülmények között hatszor gyorsabb (ab-val mérve) és jelentősen kevesebb ramot eszik. Biztosan fogok még ezzel a nyelvvel játszani.
A HHVM a Facebook alternatív PHP interpretere, ami azzal az ígérettel érkezik, hogy jelentősen gyorsabb a klasszikusnál. A héten tartottam erről előadást a budapest PHP meetupon, alant ennek összefoglalója jön.
Történeti összefoglaló
Az első változat, akkor még HPHP néven, 2008-ban jelent meg. Ez egy php -> c++ fordító volt, közel a teljes 5.2-es nyelvet ismerte, a két főbb kivétel az eval és a create_function. A saját szervereiken eddigre már ezt használtál élesben, drámai sebességnövekedést tapasztalva. Komoly hátránya volt az előállt, esetenként többgigás binárist rövid idő alatt kiszórni a szerverekre.
A HPHPc (compiler) nem volt alkalmas hibakeresésre, ennek megoldására jött létre a HPHPd (debugger) változat, ebben lehetett watch-okat és breakpointokat beállítani, mint ma egy modern IDE + xdebug kombinációban lehetséges.
2010-ben kezdődött a munka a következő változaton, ez már HHVM néven futott. Ahogy a neve is mondja, ez egy virtuális gép, ami a PHP kódot egy köztes bytekódra fordítja, majd ebből just-in-time készít natív x64 kódot, amit futtat végül. Hatalmas előnye a HPHPc-hez képest, hogy itt nincs hatalmas bináris, amit terjeszteni kéne; viszont a teljesítménybeli javulás nagyrészt megmaradt. Ráadásul az időigényes fordítást sem kell kivárni. A HHVM ismeri közel a teljes 5.4-es nyelvet, beleértve az eval és create_function függvényeket.
2013-ban a HPHPc deprecated lett. (Van erre értelmes magyar kifejezés?)
2014-ben bemutatták a Hack nyelvet, ami egy erősen bővített PHP, erről később írok bővebben.
A github wikijén számos disztróra találunk forgatási útmutatót, mindenféle verzióra. Debianra (tehát Mintre és Ubuntura is) valamint Centosra vannak előre elkészített csomagok. Az én laptopomon ennyi volt az egész:
wget http://dl.hhvm.com/conf/hhvm.gpg.key
sudo apt-key add hhvm.gpg.key
echo deb http://dl.hhvm.com/ubuntu saucy main |
sudo tee /etc/apt/sources.list.d/hhvm.list
sudo apt-get update
sudo apt-get install hhvm
Az utolsó parancs végeztével rögtön elérhető a hhvm parancs:
$ hhvm -–version
HipHop VM 2.4.2 (rel)
Compiler: tags/HHVM-2.4.2-0-g432ecffa04b21c60953bb236a9db8278f4650537
Repo schema: 1be260b29a71097b5d1f78c6e4dcbb981ba03bde
$ hhvm -m d
Welcome to HipHop Debugger!
Type "help" or "?" for a complete list of commands.
hphpd> phpinfo()
HipHop
Aki a saját rendszerén nem szeretne turkálni, vagy nem támogatott oprendszert használ, létezik vagrant box, ami egy ubuntu guestre forrásból forgatja a legfrissebb HHVM-et: javier/hhvm-vagrant-vm
Aki pedig azon gondolkodik, hol tudja ezt saját szerver nélkül hostolni: van már Heroku buildpack
, ami push után HHVM-el szolgál ki.
Extensionök
A legfontosabb core extensionök out-of-the-box jönnek a HHVM-el beépítve, és várhatóak újak. [Itt a teljes lista][extensions], a szerintem legfontosabbak pedig:
Az előadásban erről túl sok szót nem ejtettem, és okkal: annyi beállítási lehetőség van, hogy azok megérnének több saját előadást. Itt is csak a szerintem legfontosabbakról írok. A teljes és részletes leírást Runtime options címen githubon találjátok.
A NoSilencer kapcsolóval lehet logoltatni a @ operátorral elnyomott hibákat is. Az AlwaysLogUnhandledExceptions akkor is logolja a fatal errorokat, ha arra van user-defined error handler. Az utolsó kettő pedig a logba írt stack tracke-ek formátumát szabályozza, illetve hogy a php vagy a c++ kódé legyen-e.
Eval.Debugger {
EnableDebugger = true
EnableDebuggerServer = true
Port = 8089
DefaultSandboxPath = path to source files
}
Alapból a debugger (hphpd) nem figyel, így lehet bekapcsolni. Indítás után a logból is látszik, hogy várja a kapcsolatokat.
HHVM mint FCGI
Amikor az előadást tartottam akkor még lehett önálló szerverként futtatni a HHVM-et, nem kellett elé külön http szerver. Ezzel járt az, hogy rengeteg konfigurációs paraméter az egyéb file-okkal foglalkozott, és nem a php értelmezővel. 2013 decemberében bemutatták az új, FastCGI felületét, amivel ezt a problémát megoldották, és a HHVM már tényleg csak a php kód kiszolgálásáért volt felelős.
Időközben kijött a 3.0-ás verzió, amiben ez a működési mód már nem létezik. Jóideje ez volt az ajánlás, és production/staging környezetben valószínűleg nem okoz meglepetést, debug során viszont olyan hasznos volt, mint a php beépített szervere.
A FCGI mód bekapcsolásához ezt kell beleírnunk a konfigunk Server blokkjába:
Server {
// ...
Type = fastcgi
Post = 9000
// vagy
FileSocket = /var/run/hhvm.sock
}
Nginx példa:
root /var/www;
fastcgi_pass unix:/var/run/hhvm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME$document_root$fastcgi_script_name;
include fastcgi_params;
És hogy milyen teljesítménybeli javulást hoz ez? A gyári tesztek szerint egy stock wordpress telepítéssel ennyit (ab-val tesztelve):
PHP-FPM
Requests per second: 23.17 [#/sec] (mean)
Time per request: 2157.579 [ms] (mean)
Time per request: 43.152 [ms] (mean, across all concurrent requests)
Transfer rate: 275.42 [Kbytes/sec] received
HHVM + FastCGI
Requests per second: 184.71 [#/sec] (mean)
Time per request: 270.689 [ms] (mean)
Time per request: 5.414 [ms] (mean, across all concurrent requests)
Transfer rate: 2194.38 [Kbytes/sec] received
Már itt hatalmas a javulás, de aztán amikor rendesen beindul a JIT, még látványosabb:
Requests per second: 949.21 [#/sec] (mean)
Time per request: 52.676 [ms] (mean)
Time per request: 1.054 [ms] (mean, across all concurrent requests)
Transfer rate: 11276.46 [Kbytes/sec] received
Bár teljesen mesterséges teszt, a Fibonacci-számolás szépen kihozza az interpretált és a natív (JIT-fordított) kód teljesítménye közti különbséget:
PHP-FPM
Requests per second: 13789.24 [#/sec] (mean) Fib(5)
Requests per second: 3202.31 [#/sec] (mean) Fib(15)
Requests per second: 118.94 [#/sec] (mean) Fib(25)
Requests per second: 8.40 [#/sec] (mean) Fib(30)
HHVM + FastCGI
Requests per second: 8842.70 [#/sec] (mean) Fib(5)
Requests per second: 8892.66 [#/sec] (mean) Fib(15)
Requests per second: 5581.37 [#/sec] (mean) Fib(25)
Requests per second: 737.56 [#/sec] (mean) Fib(30)
Hack
Kicsit több, mint egy hete jelentették be a HHVM-re épülő Hack nyelvet. Ez egy jelentősen kibővített PHP, ezekkel a fontos újításokkal:
Skaláris típusok
Objektumokra, interfészekre, tömbökre most is lehet typehintelni, de skalárokra nem. A Hack ezt lehetővé teszi osztályváltozókra, konstansokra, függvényparaméterekre és -visszatérési értékekre:
<?hh
class MyClass {
const int MyConst = 0;
private string $x = '';
public function increment(int $x): int {
$y = $x + 1;
return $y;
}
}
Vegyük még észre, hogy a megszokott <?php helyett <?hh a nyitótag.
Genericek
Részben a típusosságból következik, hogy a C++ template-es osztályaival analóg módon tudunk általánosan, típusfüggetlenül saját osztályokat leírni, aztán JIT-fordítás időben létrejön belőle a konkrét típushoz való változat:
<?hh
class Box<T> {
protected T $data;
public function __construct(T $data) {
$this->data = $data;
}
public function getData(): T {
return $this->data;
}
}
Collection-ök
A php tömbjei nagyon sokoldalúak, de pont emiatt bizonyos célokra nem ideálisak. A Hack behozta a C++ STL-jéből ismert Vector, Map, Set és Pair osztályokat, melyek egy része bár elérhető SPL-ből, ezek az új változatok mégis jóval többet tudnak - a típuskényszerítésen kívül is. Részletes bemutatás a HHVM dokumentációjában.
Új lamda syntax
Ez csak syntax sugar, de jóval egyszerűbben lehet névtelen függvényeket definiálni, mint php-ban:
Ezt még nem sikerült teljesen felfognom. A lényege annyi, hogy aszinkron függvényhívást tesz lehetővé, amely a futása után meghív egy Callbacket. A node.js-re hasonlít, de további kísérletezést igényel. Részletes leírás.
Célként tűzték ki, hogy minnél több frameworkkel legyenek kompatibilisek, az aktuális állás megtekinthető itt: frameworks. Eszerint például laravelt vagy codeignitert ma már nyugodtan futtathatunk rajta, nem fogunk hibába futni. Van azonban valami, ami a mindennapjainkat nagyban javíthatja:
Composer HHVM-el
$ alias hhvm-composer="hhvm -v ResourceLimit.SocketDefaultTimeout=30
-v Http.SlowQueryTreshold=30000
-v Eval.Jit=false /usr/local/bin/composer`
Az intl-függő csomagokkal probléma lehet, de ezen túllépve négy-ötszörös sebességnövekedést tapasztaltam. A beállítások a lassú hálózaból fakadó problémák elkerülésére szolgálnak, your mileage may vary.
PHPUnit HHVM-el
$ composer global require "phpunit/phpunit=4.0.*"
$ alias hhvm-phpunit="hhvm -v Eval.Jit=false ~/.composer/vendor/bin/phpunit"
Nagyon csábító dolog, de valamire nagyon figyelni kell: ekkor a HHVM stack ellen teszteljük a kódunkat.
A napi munka során gyakran előkerül az a probléma, hogy mindhárom nagy operációs rendszer használatban van a cégnél. Hosszadalmas munka mindegyiken egy ugyanolyan fejlesztőkörnyezetet beállítani, és még így sem ideális a helyzet: a Windows kis/nagybetűre érzéketlen filerendszerén történő fejlesztés után könnyen problémákba ütközhetünk egy Linuxos case-sensitive rendszerre történő deploy során. És ekkor még nem esett szó az olyan, egyes alkalmazások működéséhez szükséges külső komponensekről, mint egy queue szerver, MTA vagy adatbázis.
A Vagrant egy olyan program, melynek célja az, hogy könnyen és gyorsan tudjunk több gépen (és több operációs rendszeren) azonos virtuális gépeket előállítani. Így például a repository-nk klónozása és egy vagrant up után máris lehet dolgozni anélkül, hogy akár órákat kellene eltöltenünk egy virtuális gép telepítésével és beállításával. Alapértelmezetten VirtualBoxot használ a háttérben (mert minden platformon fut és ingyenes), de van lehetőség VMWare használatára is.
Ha ez a gép elkészült, onnantól az futtatja a kódunk, de a saját operációs rendszerünk böngészőjével, fejlesztői eszközeivel tudunk továbbra is dolgozni.
Hogyan működik?
A rendszer lelke a repositoryban levő Vagrantfile, amiben a gépünk főbb paramétereit adhatjuk meg, például hogy milyen alaprendszerre épül, legyen-e hálózati elérése és hogyan, illetve hogy milyen módszerrel történjen a provisioning (a telepítés utáni konfiguráció). Ez egy egyszerű, ruby szintaxist követő file, például valami hasonló:
Ez egy Ubuntu Precise-ra (12.04 LTS) épülő gépet ír le, aminek van egy belső hálózati címe, a host gép 8080-as portja át van irányítva a guest 80-as portjára, van internet-elérése, 512MB RAM-ja, egy szinkronizált mappája (a Vagrantfile mappája a guest gép /vagrant útvonalán), valamint puppettel történik a konfigurációja.
Használata
Az alapvető műveletekhez csak néhány parancsot kell megtanulnunk:
vagrant up: elindítja a gépet, szükség esetén a megadott url-ről letöltve a boxot és létrehozza, konfigurálja indítás előtt.
vagrant ssh: ssh-val bejelentkezik a gépre - ez Windowson nem működik, mert nincs ssh parancs, de ott a putty mint workaround.
vagrant suspend: hibernálja a gépet, hogy később pontosan a mostani állapotból tudd folytatni
vagrant halt: leállítja a gépet.
vagrant destroy: megsemmisíti a gépet, azaz töröl minden file-t, ami hozzá tartozik. A végén pontosan abban az állapotban leszünk, mintha nem is futtattuk volna az első vagrant up-ot.
Puppet
A használata önmagában is megérne egy bejegyzést, ezért itt csak a fő configfile-on megyek végig, ami jelen esetben a vagrant/default.pp. Akit érdekel a teljes beállítás, githubon megtekintheti.
Alapbeállítások. Legyen egy puppet nevű csoport a gépen, ez legyen a PATH, valamint az új file-ok alapértelmezett tulaja a root, 644-es engedélyekkel.
class {'apt':
always_apt_update => true,
}
Class['::apt::update'] -> Package <|
title != 'python-software-properties'
and title != 'software-properties-common'
|>
Futtasson egy apt-get update-et minden csomagra, ami nincs a felsoroltak közt.
class { 'apache': }
apache::dotconf { 'custom':
content => 'EnableSendfile Off',
}
apache::module { 'rewrite': }
file { '/etc/apache2/sites-enabled/000-default':
ensure => absent,
}
apache::vhost { 'localhost':
server_name => false, # hogy ez legyen a default
serveraliases => [
],
docroot => '/vagrant/',
port => '80',
env_variables => [
],
priority => '1',
}
Legyen apache-unk, amiben tiltva van a sendfile, engedélyezve a rewrite modul. Szintén tiltva legyen az alapértelmezetten létrehozott virtualhost, mert sajátot akarunk csinálni localhost néve, és ezt szeretnénk a defaultnak; a DocumentRoot pedig legyen a korábban beállított /vagrant, ami a host gépen azzal a mappával van szinkronizálva, amelyben a Vagrantfile található.
class { 'php':
service => 'apache',
service_autorestart => false,
module_prefix => '',
}
php::module { 'php5-mysql': }
php::module { 'php5-cli': }
php::module { 'php5-curl': }
php::module { 'php5-gd': }
php::module { 'php5-intl': }
php::module { 'php5-mcrypt': }
class { 'php::devel':
require => Class['php'],
}
class { 'php::pear':
require => Class['php'],
}
class { 'xdebug':
service => 'apache',
}
Legyen telepítve PHP az adott modulokkal, fejlesztésre előkészítve, xdebuggal.
class { 'composer':
require => Package['php5', 'curl'],
}
Az xdebug legyen engedélyezve, és lehessen távolról kapcsolódni hozzá. Ez a gyakorlatban a helyi hálózatot jelenti, így lehet a host gépről, vagy akár egy másik gépről is debuggolni.
Legyen MySQL a gépen, a root jelszó legyen root, ezen felül jöjjön létre egy silex-alap adatbázis ugyanilyen nevű és jelszavú felhasználóval, akinek mindenre van benne joga, és rögtön importálja is ide a db.sql file-t.
A Doctrine2 nagyon szép és jó, viszont a platformfüggetlenség jegyében (helyesen) nem támogatja
beépítve a csak MySQL-ben létező enum mezőt. Számunkra viszont ez elég hasznos volna, lássuk hát,
hogyan tudjuk belevarázsolni Symfony2 keretek közt.
Az osztályok felépítését úgy oldottam meg, hogy van egy ős, amiben le van írva az összes művelet,
valamint igény szerinti számban ennek a gyerekei egyedi névvel és a hozzájuk tartozó értékekkel.
Új típust úgy tudunk létrehozni, hogy a Doctrine\DBAL\Types\Type osztályból származtatunk. Íme
az ősünk:
(A kódba plusz töréseket raktam, hogy kiférjen.)
Részletesen, hogy mi mit csinál:
a getSqlDeclaration mondja meg, hogyan legyen a mező az adatbázisban deklarálva. Megkapjuk
hozzá, hogy a mezőnk milyen opciókat kapott, illetve hogy milyen platformon vagyunk épp. Mi
ezektől függetlenül szöveges mezőt fogunk létrehozni.
a convertToPHPValue alakítja az adatbázisbeli értéket arra, amit a php-ban akarunk látni.
Mivel közvetlen az értéket tároljuk, ezért egyszerűen visszaadjuk.
a convertToDatabaseValue az előző ellentéte, itt ellenőrizzük azt, hogy érvényes-e az
egyáltalán.
a getName() adja meg a mezőnk nevét, amit a kommentben használ. Bővebben erről később.
a getValues() csak visszaadja a lehetséges értékeket, erre a formokban lesz szükség.
Az utolsó függvényről picit bővebben szeretnék írni. Amikor migrálást csinálunk, akkor a Doctrine az
adatbázis állapota alapján és a kódunk alapján készít egy-egy sémát, aztán ezeket hasonlítja össze.
Az enum mezőnk az elsőben varchar, a másodikban saját típus lesz, ezért minden alkalommal módosítaná,
mert nem tudja megkülönböztetni. E függvényünk igaz visszatérési értéke miatt viszont a mezőhöz
hozzácsap egy “(DC2Type:valamilyenenum)” kommentet, ami alapján később pontosan tudja, milyen
típusúnak is kell lennie.
Konkrét típust két lépésben tudunk létrehozni: származtatunk kell az előző osztályból, valamint
regisztrálni kell azt. A gyerekre egy példa:
A regisztráció pedig csak ennyi:
És máris tudjuk használni az entity-jeink leírásához:
Illetve a formjainkban:
Ez utóbbi megoldás nem a legszebb, de még nem találtam rá jobbat.
A PHP 5.5-ös verziójától kezdve elérhető lesz néhány új függvény, amelyek a jelszavak kezelését segítik. Ezekről fogok írni most, hogy miért volt rájuk szükség, miért kerültek be a PHP magjába, és hogyan lehet őket használni.
Háttér
Hiába ismert az MD5-ben egy hiba 1996, a SHA1-ben pedig 2005 óta, a mai napig ezek azok a függvények, amelyeket a PHP-ban programozók többsége használ a jelszavak egyirányú kódolására (hash-selérére). Rossz esetben salt nélkül, kicsit jobb esetben egy jelszavanként különböző salttal.
Akárhogy is, ezekben egy hiba közös: sem az MD5, sem az SHA1 nem erre lett megalkotva. Mindkettőnek eredeti célja az, hogy üzenetek gyors integritás-ellenőrzését tegyék lehetővé – tehát fontos szempont volt tervezésükkor, hogy gyorsak legyenek, és egyszerűen meg lehessen őket valósítani áramkörök szintjén. Pontosan ez a sebesség teszi őket alkalmatlanná arra, hogy jelszavakat biztonságosan tároljunk benne.
2012 májusában Anthony Ferrara hozta létre a problémát megoldani célzott RFC-t, amit érdemes végigolvasni. Ebben részletesen leírja, mi a probléma a jelenlegi helyzettel. Mindössze négy új függvényt ír le azzal a céllal, hogy olyan egyszerű legyen a használata: balgaság legyen bárhogy máshogy csinálni.
A függvények
password_hash
Ezzel tudod hashelni a jelszót új felhasználó létrehozásakor.
string password_hash(string $password, int $algo, array $options = array())
Az első paraméter maga a kódolandó jelszó, a második a használandó algoritmus. Kettő konstans van erre definiálva, a PASSWORD_BCRYPT és a PASSWORD_DEFAULT. Jelenleg mindkettőnek ugyanaz az értéke, de ha később egy erősebb algoritmus is beépítésre kerül, a default arra fog módosulni.
Az opciók közt két értéket adhatunk meg: az egyik a salt, amit ha kihagyunk, random generál egyet; a másik a cost, ami az algoritmus műveletigényére van hatással. Ezt érdemes úgy belőni, hogy egy másodperc körüli idő alatt számoljon egy hasht. Érvényes bármely 4 és 30 közti egész, az alapértelmezett a 10.
Visszatérési értéke bcrypt esetén egy fix 60 karakteres string, ami tartalmaz mindent, amire ellenőrzéshez szükségünk van, azaz elég ezt mentenünk.
password_verify
Ezzel lehet ellenőrizni bejelentkezéskor, helyes jelszót adott-e meg a felhasználó.
bool password_verify($password, $hash)
Két paramétert vár, az első a kapott jelszó, a második a korábban mentett hash. Azt adja vissza, stimmel-e.
Érdekesség, hogy a háttérben egy olyan string-összehasonlító függvényt használ, ami ellenálló az időzítéses támadásokkal szemben: nem tér vissza hamissal az első eltérésnél, hanem mindenképp végignézi mindkét stringet.
password_needs_rehash
Ezzel azt tudod eldönteni, szükséges-e a jelszó újrakódolása azért, mert a rendszer közben átállt új algoritmusra, vagy nagyobb költségre. A bejelentkezési folyamat részeként lehet használni, amikor megvan a kódolatlan jelszó is.
bool password_needs_rehash(string $hash, int $algo, array $options = array())
Paraméterei annyiban térnek el a password_hash-étől, hogy ez egy már kódolt jelszót vár, nem egy eredetit. Azt adja vissza, gyengébb paraméterekkel (algoritmus, költség) lett-e hash-elve, mint amit kapott.
password_get_info
Ezzel tudod egy hash kódoláskor megadott paramétereit.
array password_get_info(string $hash)
A visszatérési értéke egy tömb algo és options kulcsokkal, az első a használt algoritmust adja meg, a második az annak átadott paramétereket, mint például a költség.
Használati példa
Az alábbi példa a fentebb linkelt RFC-ből származik. Megmutatja, hogy kell egy kapott jelszót hash-elni adott paraméterekkel, azt ellenőrizni, illetve megnézni, szükséges-e az újbóli kódolás.
Az jól látható, hogy innentől felesleges egyéb megoldásokat használni, ennél tényleg nem lehet egyszerűbb.
És 5.5 előtt?
Nem kell félni attól, hogy ezek csak évek múltán lennének használhatók, amikor már a szolgáltatók többsége átállt 5.5-re. Az eredeti, C nyelvű patch szerzője megvalósította ugyanazt PHP-ban is password_compat néven, ezt már 5.3.7-től kezdve lehet használni. Működése pontosan ugyanaz, mint a majdani beépített függvényeké.
Két héttel ezelőtt, május 6-án, leadtam a szakdolgozatom.
A témám egy olyan rendszer készítése volt, mely egy adott szervezetnek tud segíteni az
ülései szervezésében, illetve azok jegyzőkönyveinek elkészítésében. Mindezt
webalkalmazásként, a Symfony2 keretrendszer használatával műveltem.
A kód még nem publikus, de a dokumentáció igen - jó kockaként LaTeX-ben
írtam, hogy lehessen verziókövetni. A teljes forrás, valamint a pdf kimenet
elérhető a githubon.
A közelmúltban szükségem volt egy olyan backup megoldásra, ami akkor is működik, ha a tárhelyre csak
ftp elérésem van - a phpmyadmin is csak a szolgáltató saját adminfelületén keresztül érhető el.
A háttér az, hogy jóatyám céges joomlás oldalát felnyomták. Mivel nem saját kód, nem volt verziózva.
A szolgáltató backupolt, de ez nem letölthető, hogy összehasonlítsd korábbi változattal, hanem vissza
lehet állni rá.
A megoldás egy php és egy bash scriptből áll. A php maga mellé egy file-ba menti a teljes DB dumpot,
a felhasznált függvény David Walsh blogjáról való.
Ez a mappa .htaccess-el védve van, csak jelszó ismeretében érhető el (tudom, hogy nem az igazi, de nem
találtam jobb megoldást). Cirka egymegás dumpot csinál egy másodpercen belül.
A lényegi munkát a shellscript végzi. A környezet: a mappa, amiben dolgozik, egyaránt egy checkoutolt
git repo, valamint a teljes ftp tartalmat letöltő wget parancs kimenete. A lépések:
a .git-en kívül minden törlése
adatbázis backup triggerelése
teljes ftp tartalom mentése
ha az előbbi meghiúsult, értesítő levél és kilépés
ha nem változott semmi file, kilépés
minden változás commitolása, pusholása
értesítő levél
A központi szerver egy saját hostolású GitLab, így weben keresztül is
vissza lehet nézni, mikor mi változott. Gittel természetesen az adott időpontra visszaállás is könnyen
megoldható.