Așa cum promiteam într-un articol precedent vom încerca realizarea unei aplicații pe baza API-ului oferit de rețeaua de micro-blogging Twitter și folosind protocolul OAuth.

Twitter

Pentru cei ce nu sunt familiarizaţi cu Twitter, acest serviciu oferă posibilitatea utilizatorilor de a trimite mesaje de maxim 140 caractere (gen SMS) şi în care, teoretic, s-ar transmite mesaje scurte, informative, care ar răspunde la întrebarea "What’s happening?". Prietenii acestuia, sau cei interesati de ce ar avea acea persoană de spus (cazul unei persoane publice) îi pot urmări twitt-urile fiind astfel la curent cu ce face sau cu ce are de zis. Astfel un utilizator poate urmări anumiţi utiliztatori şi poate fi urmarit de către alţii, creându-se astfel conexiunile pe care se bazează orice reţea socială.

Pe langă aplicaţia web de utilizare a sistemului pe care Twitter o ofera pe site-ul twitter.com, aceştia mai pun la dispoziţie dezvoltatorilor de software posibilitatea de a crea aplicaţii diverse folosindu-se de un API, un set de comenzi prin care cei autorizaţi să îl foloseasca pot citi şi scrie date direct în sistemul Twitter. Transmiterea de date se face prin formate standard, asigurându-se astfel că orice aplicaţie-client twitter s-ar folosi, datele transmise pot fi citite din orice altă aplicaţie-client.

Lista funcţiilor oferite de API-ul Twitter cuprinde toate funcţionalităţile oferite şi de site-ul twitter.com astfel că practic vorbind putem crea o aplicaţie identică cu site-ul actual, o clonă sau chiar o versiune mai bună, folosindu-se chiar de sistemul original Twitter, serviciu foarte popular cu un numar necunoscut de utilizatori nici până în ziua de azi.

Twitter, cei care ofera interfata de comunicare a aplicatiilor externe cu sistemul propriu-zis trebuie sa facă diferențierea între actiunile venite din partea site-ului și cele venite de la o aplicație, oricare ar fi aceasta. Mai mult ei trebuie să asigure suportul pentru o comunicatie sigură și să se știe tot timpul despre o acțiune de către ce aplicație este făcută și în numele cărui utilizator.

OAuth

Aici este locul în care intervine OAuth: avem un sistem, avem niste utilizatori, avem pentru fiecare utilizator resurse proprii (mesajele proprii, mesajele prietenilor si posibilitatea de a trimite noi mesaje) și nu în ultimul rând avem mai multe aplicații externe care pot comunica cu Twitter în numele unor utilizatori. Deci putem asocia conceptele OAuth după cum urmează:

  • Resursele: lista de mesaje transmise, mesajele prietenilor, lista de prieteni
  • Furnizorul de servicii: Twitter, cel care implementeaza protocolul OAuth și care ofera acces la serviciile sale.
  • Utilizatorul: orice persoană care și-a creat un cont folosind site-ul twitter.com
  • Consumatorul: aplicația care va utiliza twitter-ul in numele clientului.

Așadar OAuth este folosit pentru autentificarea aplicațiilor twitter, autentificarea utilizatorilor si autorizarea aplicației să efectueze operații asupra Twitter-ului în numele lor. O astfel de autorizare trebuie făcută explicit și pentru fiecare aplicație în parte. Fiind vorba despre o aplicație vom încerca și noi crearea uneia care va permite folosirea sistemului Twitter.

Inscrierea

Inregistrarea unei aplicatii Twitter

Inregistrarea unei aplicatii Twitter

Pentru ca o aplicație să poată funcționa corect și pentru a oferi siguranță și încredere utilizatorilor ea trebuie înregistrată cu furnizorul de servicii. Aceasta se face dintr-un cont existent de twitter la adresa http://twitter.com/apps unde se vor cere informații despre aplicație cum ar fi: nume, descriere, cine o dezvoltă, site-ul de unde se poate procura, tipul aplicatiei (desktop sau web) daca aplicația are nevoie doar de a citi date sau și de a scrie și nu în ultimul rând URL-ul de întoarcere, despre care vom vorbi mai târziu în detalierea aplicației.

După înregistrarea completă a aplicației, Twitter va genera un Consumer Key si un Consumer Secret, ambele reprezentând câte un sir de caractere din care primul este folosit pentru identificarea aplicației iar cel de-al doilea este folosit pentru a coda comunicarea dintre aplicatie si Twitter. Fiind un sir de caractere secret, el trebuie pastrat în siguranță și trebuie cunoscut doar de către dezvoltatorii aplicației.

Aplicația

Aplicația în sine poate fi de orice tip (web sau desktop) și poate fi scrisă în aproape orice limbaj de programare . Pentru demonstrare am ales limbajul PHP cu care vom crea o aplicație simplă de citire a datelor și de actualizare a statusului Twitter. Pentru aceasta am creat o clasă numită TwitterConnector și pentru care am definit câțiva membri privați și câteva constante:

class TwitterConnector
{
    private $_consumerKey = '';
    private $_consumerSecret = '';
    private $_tokenSecret = '';
    private $_token = '';

    private $_version = '1.0';

    const URL_REQUEST_TOKEN = 'https://twitter.com/oauth/request_token';
    const URL_ACCESS_TOKEN = 'https://twitter.com/oauth/access_token';
    const URL_AUTHORIZE = 'https://twitter.com/oauth/authorize?oauth_token=%s';
    const URL_AUTHENTICATE = 'https://twitter.com/oauth/authenticate?oauth_token=%s';
    const URL_DATA = 'http://api.twitter.com/1/';
}

$_consumerKey și $_consumerSecret sunt valorile primite de la Twitter cu identificatorul aplicației și cu cheia secretă de semnare a mesajelor. $_token și $_tokenSecret au aceeași însemnătate ca și consumer key și respectiv consumer secret, însă ele sunt atribuite pentru utilizator. Așadar cu un cod de identificare al aplicației și un token din partea clientului putem detemina ce utilizator a folosit ce aplicație, iar prin codul secret al consumatorului și cel al utilizatorului se poate comunica intr-un mod sigur cu sistemul Twitter. $_version reprezintă versiunea OAuth conform căror specificații realizăm aplicația.

URL_REQUEST_TOKEN, URL_ACCESS_TOKEN sunt URL-urile furnizate de către Twitter și sunt folosite pentru a obține un request token și un access token (pe care le vom detalia mai jos), URL_AUTHORIZE este folosit în cazul în care aplicația folosește sistemul de autentificare al lui Twitter, URL_AUTHENTICATE este folosit pentru a redirecționa utilizatorul pentru a autoriza aplicația iar URL_DATA este URL-ul de la care se vor obține date sau unde vor fi trimise comenzile de actualizare sau ștergere.

Vom crea în continuare constructorul clasei ce va primi ca parametrii consumer key si consumer secret pentru ușurință în dezvoltarea ulterioară. Deasemeni am definit și cate un setter si getter pentru a opera cu parametrii token si token secret.

 public function __construct($consumerKey, $consumerSecret)
    {
        $this->_consumerKey = $consumerKey;
        $this->_consumerSecret = $consumerSecret;
    }

    public function setToken($token)
    {
        $this->_token = $token;
    }

    public function getToken()
    {
        return $this->_token;
    }

    public function getTokenSecret()
    {
        return $this->_tokenSecret;
    }

    public function setTokenSecret($secret)
    {
        $this->_tokenSecret = $secret;
    }

Comunicarea cu sistemul Twitter

Pentru a comunica unitar cu Twitter am creat metoda doRequest, care primește drept parametrii adresa URL unde va face cererea, metoda HTTP prin care se va accesa sistemul Twitter (GET, POST, DELETE etc) precum și parametrii care vor fi trimiși. Pe lângă parametrii specifici fiecărei funcții descrise în documentația API-ului Twitter, sistemul are nevoie de a primi niște parametrii specifici OAuth pentru ca furnizorul de servicii să fie sigur că aplicația care acționeză în numele unui utilizator are acest drept și nu se încearcă accesarea neautorizată a sistemului. Acești parametrii sunt returnați de metoda getDefaultParameters și sunt adaugați la lista de parametrii specifici apelului pe care incercăm sa-l facem, folosind funcția array_merge.

Având acești parametrii putem semna cererea, prin metoda generateSignature (detaliată mai jos), iar apoi folosind funcțiile cURL vom contacta Twitter-ul pentru a transmite sau pentru a obține date. Parametrii pe care ii primim sunt in format array deci vor trebui transformați într-un format ce poate fi trimis atat prin metoda GET (deci adaugat la URL) cât și prin metoda POST. cURL suporta pentru ambele variante transmiterea parametrilor folosind o structură name-value-pair (NVP), adica perechi de tipul atribut1=valoare1&atribut2=valoare2

 protected function doRequest($url, $method, $params)
{
    $params = array_merge($this->getDefaultParameters(), $params);
    $params = $this->encodeParams($params);

    $params['oauth_signature'] = $this->generateSignature($url, $method, $params);

    $ch = curl_init();
    if (strtolower($method) == 'post')
    {
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->toNVP($params, true));
        curl_setopt($ch, CURLOPT_URL, $url);
    }
    else
    {
        curl_setopt($ch, CURLOPT_URL, $url . '?' . $this->toNVP($params, true));
    }

    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    return curl_exec($ch);
}

Observați că am tratat separat cazul în care parametrii se trimit prin POST fată de cel în care se trimit prin GET, cazul în care se doresc apelarea altor metode fiind ușor de adaugat, putând constitui o temă pentru cei care doresc continuarea exemplului pe care il dezvoltăm.

Semnarea cererilor

Orice cerere către furnizorul de servicii, fie pentru a obtine un token fie pentru a obtine accesul la resursele protejate trebuie să fie însoțită de o semnătură, pentru a dovedi autenticitatea celui care o cere. Semnătura este compusă din parametrii cu care se face cererea și din cheia secretă și (dacă există) tokenul secret. Modul de semnare a cererii este definită în secțiunea 9 a documentației OAuth și este la rândul ei realizată în mai multi pași.

Desi specificațiile OAuth permit folosirea mai multor metode de criptarea a semnaturii, de fapt recomandă folosirea câtorva metode, dar nu ingrădește definirea și documentarea altora, Twitter nu sportă criptarea semnăturii decât prin metoda HMAC-SHA1. Avem nevoie așadar de un sir de caractere de bază și de o cheie de criptare. Sirul de caractere este format din concatenarea metodei HTTP folosite, a URL-ului si a listei de parametrii normalizați conform NVP. Acest sir de caractere de bază trebuie codat folosind o cheie, formată din concatenarea consumer secret si (dacă există) token secret.

 protected function generateSignature($url, $method , $params)
{
    $nvp = $this->toNVP($params, true);
    $nvp = $this->encode($nvp);
    $url = $this->encode($url);

    $string = "{$method}&{$url}&{$nvp}";
    $key = $this->encode($this->_consumerSecret) . '&' . $this->encode($this->_tokenSecret);
    return  base64_encode(hash_hmac('sha1', $string, $key, true));
}

Rezultatul funcției hash_hamc fiind unul binar, el trebuie transformat într-unul ASCII folosind funcția base64_encode. Metodele toNVP și fromNVP sunt folosite pentru a obține o codare NVP dintr-un array și un array dintr-un string codat NVP. Metoda toNVP are totuși particularitatea ca va ordona valorile din vector în ordine alfabetică a cheilor fiecărui element (aceasta fiind o cerintă din specificațile OAuth). O altă cerință OAuth este ca orice parametru să fie codat UTF-8 și trebuie să fie codat URL, lucru care se asigură prin metoda encode.

 protected function toNVP(array $array)
{
    ksort($array);

    $nvp = array();
    foreach($array as $name => $value)
    {
      $value = $this->encode($value);
      $nvp[] = "{$name}={$value}";
    }

    return implode('&', $nvp);
}

protected function fromNVP($string)
{
    $list = explode('&', $string);
    $output = array();
    foreach($list as $element)
    {
        $tmp = explode('=', $element);
        $output[$tmp[0]] = $tmp[1];
    }
    return $output;
}

protected function encode($value)
{
    return rawurlencode(utf8_encode($value));
}

Parametrii obligatorii, ceruți la fiecare cerere către Twitter sunt:

  • oauth_consumer_key – parametru consumer key cu care am instanțiat clasa
  • oauth_signature_method – metoda folosită de a semna cererea, in cazul nostru va fi tot timpul HMAC-SHA1
  • oauth_timestamp – reprezintă UNIX TIMESTAMP, sau numărul de secunde trecute din 01.01.1970 și este util pentru a verifica daca o nouă cerere este făcută după precedenta și de a preveni posibile atacuri
  • oauth_nonce – este un string unic, generat aleator și pentru care nu se impune nici o restricție de lungime. Pentru generarea lui am folosit funcția generateNonce care folosește o combinație de funcții de generarea de coduri unice (uniqid) si de codare (md5)
  • oauth_version – versiunea OAuth care se utilizează (poate fi opțional)
  • oauth_token – daca există definit, tokenul va fi inclus.

Parametrul oauth_signature este cerut la fiecare cerere, dar el trebuie omis în construirea semnăturii.

 protected function getDefaultParameters()
{
    $params['oauth_consumer_key'] = $this->_consumerKey;
    $params['oauth_signature_method'] = 'HMAC-SHA1';
    $params['oauth_timestamp'] = time();
    $params['oauth_nonce'] = $this->generateNonce();
    $params['oauth_version'] = $this->_version;
    if ($this->_token != '')
    {
        $params['oauth_token'] = $this->_token;
    }

    return $params;
}

protected function generateNonce()
{
    return md5(uniqid(rand(), true));
}

Autentificarea și autorizarea

În secțiunea 6 a specificațiilor OAuth se definește procesul de autentificare și autorizare ca fiind unul în 3 pași:

  1. Consumatorul obține de la Twitter un token neautorizat (numit request token)
  2. Utilizatorul autorizează tokenul neautorizat spre folosire de către consumator (de regulă utilizatorul este direcționat la Twitter pentru autorizare și posibil autentificare)
  3. Consumatorul schimbă token-ul neautorizat cu unul autorizat (numit access token)

Așa vom proceda și noi: inițial vom instanția clasa și vom cere un request token, cu care vom redirecționa utilizatorul către site-ul twitter.com pentru a-l autoriza. După ce acesta se autentifică și autorizează aplicația noastră să opereze sistemul twitter în numele lui, twitter.com va redirecționa utilizatorul către URL-ul de întoarcere pe care l-am definit în momentul în care am declarat intenția de a crea o aplicație pe site-ul twitter.com. În acest fel, chiar daca s-ar petrece o scurgere de informații și consumer secret ar ajunge la persoane cu intenții necurate, sistemul twitter va redirecționa clientul înapoi către URL-ul definit la inregistrarea aplicției, eliminând astfel posibila fraudă.

$tw = new TwitterConnector(CONSUMER_KEY, CONSUMER_SECRET);

if (isset($_SESSION['oauth_token']) && isset($_SESSION['oauth_token_secret']))
{
    /**
      * in cazul in care avem stocate pe sesiune informatiile
      * despre token putem instantia clasa
      * si folosi toate functionalitatile twitter
      **/
    $tw->setToken($_SESSION['oauth_token']);
    $tw->setTokenSecret($_SESSION['oauth_token_secret']);

    if (count($_POST) > 0)
    {
        if (isset($_POST['msg']) && strlen($_POST['msg']) <= 140 )
        {
            $tw->updateStatus('json', $_POST['msg'], null);
            header('Location: index.php');
        }
    }
}
else if (isset($_GET['oauth_token']))
{
    /**
      * in cazul in care primim pe GET informatiile despre un token
      * inseamna ca el este unul neautorizat
      * si vom incerca sa-l autorizam dupa care vom scrie pe sesiune
      * informatiile despre access token
      **/
    $tw->setToken($_GET['oauth_token']);
    $tw->setTokenSecret('');

    $tw->getAccessToken();

    $_SESSION['oauth_token'] = $tw->getToken();
    $_SESSION['oauth_token_secret'] = $tw->getTokenSecret();
}
else
{
    /**
      * in cazul in care nu suntem in nici un caz din cele de mai sus
      * inseamna ca utilizatorul a accesat pentru prima oara aplicatia noastra
      * deci vom obtine un reuest token si apoi vom redirectiona utilizatorul
      * catre twitter.com pentru autorizare
      **/
    $tw->requestToken();
    $tw->redirect();
}

Comunicarea

Odata autorizată, aplicația poate accesa resursele protejate ale utilizatorului și poate acționa în numele lui pentru a urmări alți utilizatori, a trimite mesaje noi sau orice alte acțiuni permise prin API-ul Twitter. Evident ca într-o situație reală aceste acțiuni vor fi dictate de către însuși utilizator și aplicația doar le va intermedia și nu să trimită mesaje în neștirea utilizatorului. Deși token-ul de acces este emis pentru fiecare aplicație pentru un termen nelimitat utilizatorul are posibilitatea sa revoce acest acces prin intermediul twitter.com.

Doar pentru scop demostrativ am implementat funcționalitatea pentru a prelua "time-line"-ul utilizatorului (adică ultimele mesaje trimise de către cei pe care utilizatorul îi urmărește) și funcția de actualizare a status-ului. Pentru oricare din funcționalitățile API-ului Twitter oferă răspunsul în 2 variante: JSON și XML. Pentru aceasta am creat funcția returnFormatedReply care va întoarce rezultatul formatat în funcție de cererea făcută

public function getHomeTimeline($format)
{
    $reply = $this->doRequest(
                self::URL_DATA . 'statuses/home_timeline.' . $format,
                'GET',
                array()
    );
    return $this->returnFormatedReply($format, $reply);
}

public function updateStatus($format, $status, $inreply)
{
    $reply = $this->doRequest(
                self::URL_DATA . 'statuses/update.' . $format,
                'POST',
                array('status' => $status, 'in_reply_to_status_id' => $inreply)
    );
    return $this->returnFormatedReply($format, $reply);
}

protected function returnFormatedReply($format, $reply)
{
    switch ($format)
    {
        case 'json':
            return json_decode($reply);
            break;
        case 'xml':
            return new SimpleXMLElement($reply);
            break;
        default:
            return null;
    }
}

Considerente finale

Exemplu folosit este pe departe de a fi un exemplu complet funcțional pentru a fi folosit într-o aplicație Twitter gata de a fi lansată în producție; de exemplu nu tratează în nici un fel erorile posibile de la Twitter. Am creat acest exemplu pentru a demonstra posibilitatea de implementare a specificațiilor OAuth pe partea de consumator în contextul PHP. Totusi reprezintă un bun început pentru a se ajunge la o aplicație twitter de sine stătătoare si care sa funcționeze corect în toate cazurile. O modalitate de a face un cache al mesajelor și adăugarea unor funcționalități JavaScript pentru o experientă a utilizatorului mai placută, ar putea fi un început pentru o posibilă aplicație funcționala 100%. Provocăm pe oricine are timpul și este dispus la o completare și o continuare a codului început aici pentru a realiza o aplicație corectă si completă, și supunem la dezbateore orice idee în acest sens ați avea.

Puteți testa aplicația la adresa http://sandbox.inphpwetrust.com/twitter/, puteți obține codul sursă sau ne puteți urmări pe Twitter

OAuth (sau Open Authentication) este un protocol liber prin care se poate permite accesul la resurse aflate pe un alt site fără a dezvălui datele de conectare (nume utilizator și parolă) către site-ul în care se dorește accesul la acele resurse partajate (imagini, video, adrese de email, conturi bancare etc.)

Este practic o ”cheie pentru valet” sau un ”ecuson pentru vizitator” cu care se acordă un acces mai restrâns la resursele personale aflate pe un site, decât dacă le-am accesat direct din acel site. La fel cum o cheie pentru valet (cel care se ocupa de parcarea mașinii) ar permite accesul doar la anumite funcții ale mașinii și pentru o perioadă restrânsă sau cum un ecuson de vizitator nu ar permite accesul la orice încăpere dintr-o clădire.

Înțelegerea protocolului OAuth se bazează pe cunoașterea următorilor termeni:

  • Resursele – sunt obiectele private ale unui utilizator la care se dorește accesul selectiv de către alte site-uri sau aplicații
  • Furnizorul de Servicii – este cel care suporta toate aspectele ale implementarii protocolului, cel care urmează sa ofere acces la resursele care le are în administrare către alți clienți. Poate fi un site de stocare de poze care oferă acces altor site-uri pentru preluarea imaginilor stocate de către un anume utilizator. Poate fi un site care stochează toate contactele (nume, email, telefon, adresa) personale ale unor utilizatori. Poate fi, în general, orice serviciu care oferă stocarea de informații private și accesibile selectiv. De regulă, acest furnizor de servicii, își oferă serviciile în baza unei înscrieri și în baza unei autentificări folosind un nume utilizator și o parolă.
  • Utilizatorul – este cel pentu care s-a inventat acest protocol. Utilizatorul are resurse în cadrul unui furnizor de servicii (imagini, video, contacte, mesaje) care nu vrea sa le facă publice, dar care vrea sa le folosească și pe alte site-uri. El este cel care va decide cărui site va acorda acces la resursele proprii
  • Consumatorul – este site-ul de pe care se dorește accesarea resurselor aflate la furnizorul de servicii.
  • Jetonul – sau celebrul token, este identificatorul prin care furnizorul de servicii va comunica cu consumatorul. În baza lui se va ști ce resurse și ale cărui utilizator trebuiesc partajate

Astfel, presupunem că deținem un cont pe site-ul Flickr în care avem stocate câteva imagini din vacanța de iarna. Aceste imagini le dorim printate folosind un serviciu online. În situația de față furnizorul de servicii este flickr.com deoarece deține niște resurse pe care dorim sa le partajăm folosind protocolul OAuth către un site ce oferă servicii de printare. Pentru a obține imaginile stocate pe flickr.com putem manual să le descărcăm și să le încarcăm pe site-ul de printat fotografii. Dar ar dura prea mult. Site-ul de printare fotografii se oferă să le preia automat în numele nostru. Pentru aceasta noi va trebui să furnizăm adresa de email şi parola pentru ca site-ul să ceară în numele nostru lista de fotografii pe care o avem pe Flickr. Intervine problema securităţii şi a increderii faţă de site-ul care ne va printa pozele: furnizând parola, aceasta poate fi stocată pe serverele lor, iar cineva rău intenţionat poate crea daune.

Aici intervine OAuth. Procesul de obţinere de informaţii de la Flickr fără a dezvălui parola pe alte site-uri decât Flickr.com este următorul

  1. Accesăm site-ul comsumatorului, în cazul nostru cel care ne va oferi servicii de printat fotografii.
  2. În cadrul acestuia vom găsi informatii cum că este dispus să preia imagini de pe site-ul flick.com
  3. Pentru a prelua aceste informatii vom fi redirectionati pe flickr.com
  4. Fiind pe flickr.com, deci într-un loc sigur, putem să ne autentificăm cu emailul şi cu parola persoanlă
  5. După autentificare flickr.com ne va redirecţiona înapoi pe site-ul de la care am pornit (cel de printare imagini) şi îi va furniza acestuia şi un jeton (token)
  6. Folosind acest jeton, site-ul de printare fotografii va face cereri in numele nostru catre flickr.com pentru a accesa fotografiile ce urmeaya a fi printate.

Jetonul este un cod unic, securizat, cu ajutorul acelui caruia se asigură faptul că se vor accesa doar imaginile din contul nostru şi pentru o perioadă finită de timp.

Vom arăta în următorul articol o implementare parctică a protocolului OAuth folosind un serviciu de microblogging. Vom crea o aplicaţie care interacţionează prin OAuth cu o platfomă externă pentru autentificare şi trimitere de mesaje.

 

Cache-ul este o zonă temporară de stocare de informatii, care duplică niste date considerate originale, creat pentru acces direct si rapid asupra acestor date. Aceasta mapare a datelor faţă de locul lor de stocare iniţial, se face pentru  acele date care sunt greu de accesat în mod direct, care se afla în zone partajate, care ar avea un timp de prelucrare ridicat si care ar fi solicitate în mod frecvent, iar rezultatul ar fi de fiecare dată acelaşi.

Sisteme de cache se întalnesc implementate în microprocesoare(exemplul fiind cache-ul de nivel 2), în hard disk-uri(pentru a micşora timpul de citire al datelor), în sisteme de management al bazei de date(MySQL va ţine în cache rezultatul unei interogari, iar la primirea aceleiaşi interogări va returna rezultatul din cache, făra a interoga tabelele în sine), in browser-ele pe car ele folosim zi de zi(Firefox de exemplu îşi va face cache la imagini, ca la un refresh sa nu fie nevoit sa le preia de pe server din nou), chiar şi Google deţine propriul cache din care poate furniza conţinutul paginilor web.

Aşadar, scopul declarat al cache-ului este de a economisi timp.

Teoria Cache-ului

Există cateva concepte cheie în teoria cache-ului care trebuiesc respectate în implementarea unui astfel de sistem

  1. identificatorul unic – care va fi folosit la identificarea elementului în cache
  2. durata de viaţă – defineşte cât timp un element din cache va fi considerat valid
  3. preluarea condiţionată – astfel încat părţile din cod care ar accesa informaţiile originale să fie evitate, dar să si permită reîmprospătarea
  4. resetarea la cerere – pentru păstrarea consistenţei informaţiilor din cache cu cele din locaţia originală, este necesară posibilitatea ca la o modificare a datelor din această locaţie, cache-ul sa fie marcat ca invalid şi reconstruit

Astfel, un algoritm general de folosire a cache-urilor ar fi:

  • dacă elementul din cache cerut de aplicaţie există atunci el va fi returnat intocmai
  • dacă elementul din cache cerut nu există atunci datele acelui cache se vor aduce din locaţia originală, se va crea elementul corespunzător în cache, iar datele vor fi returnate aplicaţiei

Aplicare

Aşadar, vom presupune o apicaţie web, pentru care avem un număr mare de accesări atât din partea vizitatorilor dar şi din partea celor care administrează respectivul website. Pentru o şi mai buna exemplificare, vom considera cazul standard al unui magazin online, în care avem listări de categorii, listări de produse din fiecare categorie şi afisări detaliate de produse(preţ, descriere, detalii tehnice etc). În spatele site-ului, respectiv în aplicaţia de administrare a acestuia, avem un număr de operatori care lucrează necontenit la imbunătăţirea informaţiilor prezentate pe acel site. Mai mult decât atât, să mai luam în calcul existenţa unor aplicaţii care periodic sincronizează preţurile şi stocurile produselor cu cele existente la furnizorii direcţi. Pentru a îmbunătăţi imaginea de ansamblu să considerăm ca magazinul are câteva zeci de mii de produse. În cuvinte mult mai simple şi mai tehnice: o mulţime de interogări sql de tip insert, update dar mai ales select.

Dezavantajul unui astfel de scenariu este evident cel al supraîncărcării bazei de date cu interogări care de cele mai multe ori se vor repeta şi vor furniza acelaşi set de date. Cu toate că, de exemplu, MySQL deţine un cache propriu din care returnează un set de date al unei interogari la o repetare a acesteia, aplicaţia PHP care interogheză baza de date va trebui sa realizeze tot protocolul de comunicare, să furnizeze interogarea şi să primească datele, deci nişte timpi deşi mici, deloc de neglijat în contextul unui volum de trafic ridicat.

Continuând scenariul nostru de “groază” mai trebuie luat în considerare faptul ca la o interogare de tip insert sau update, cele cauzate de aplicaţia de administrare, pot apărea lock-uri pe câmpurile, înregistrările sau chiar tabelele din baza de date, deci până la terminarea execuţiei şi scrierea ori modificarea cu succes a datelor în bază, o instrucţiune select, nu va putea citi baza de date pentru preluarea informaţiilor şi va fi pusă în aşteptare până la terminarea tranzacţiei. Rezultă un timp mort şi mai mare. Cache-ul ar trebui sa intervină în astfel de momente, când putem spune ca majoritatea interogarilor vor furniza acelaşi set de date pentru perioade definite de timp,  iar rularea lor nu ne-ar aduce decât dezavantaje.

Interogând baza de date pentru a obţine informaţiile despre produsele din categoria “Monitoare LCD” vom obţine unset de date reprezentat printr-un vector cu produsele din acea categorie. Datele din acest vector pot fi introduse în cache. Conform algoritmului general descris mai sus putem scrie urmatorul cod PHP

if (!($data = loadFromCache('cache_for_category_id_' . $categoryId)))
{
    $data = loadDataFromDatabase($categoryId);
    saveToCache('cache_for_category_id_' . $categoryId, $data, 3600);
}

Plecăm astfel de la premisa ca informaţia căutată se afla în cache şi chiar încercăm să o preluăm. Dacă functia loadFromCache() va întoarce o valoare nulă atunci înseamnă ca datele nu se află în cache şi ele vor trebui aduse din baza de date, lucru ce se va face prin functia loadDataFromDatabase() iar apoi salvate în cache cu ajutorul funcţiei saveToCache(), cache valabil o ora. Chiar dacă presupunerea noastră iniţială referitoare la existenţa datelor în cache este adevărată sau nu, după executarea acestei porţiuni de cod vom avea în variabila $data informaţiile necesare.

Trebuie avut în vedere faptul că în tot acest timp datele considerate valide sunt cele din baza de date, cache-ul fiind doar o copie locala a acesteia. Deşi sistemul ne va reseta automat cache-ul după expirarea perioadei de viaţă, vor exista situaţii când cache-ul va deveni inconsistent, adică nu va mai reflecta realitatea din baza de date. Deci, la adăugarea unui produs nou în baza de date în categoria “Monitoare LCD”, cache-ul construit mai devreme nu mai este consistent(nu conţine si acest nou produs). Cum varianta în care aşteptăm trecerea celor 3600 de secunde pentru a se recrea cache-ul nu ne multumeşte(perioada putând fi mult mai mare), aplicaţia de administrare va trebui sa intervină asupra cache-ului şî să invalideze înregistrarea ce conţine datele din această categorie. În acest mod vom forţa recreerea cache-ului cu noile informaţii la următoarea accesare a categoriei respective.

resetCache('cache_for_category_id_' . $categoryId);

În tot scenariul de mai sus am considerat crearea de cache-uri pe categorii şi nu unul global care să conţină toate categoriile existente pe site, din considerente de acces si de resetare. Este mai simplu sa alegem direct cache-ul categoriei pe care dorim să o afişăm decât sa încărcăm toate categoriile prin care să o căutăm pe cea dorită, precum este mai normal ca la introducerea produsului nou în categorie să resetăm doar cache-ul categoriei respective şi nu cel al tuturor produselor.

Unelte

PHP nu deţine nativ funcţii de lucru cu cache-ul, însă există extensii PECL care pot fi instalate şi cu care se pot lucra, printre care enumerăm Memcache şi APC.

Folosirea extensiei Memcache:

$cache = new Memcache();
$cache->addServer('localhost');

if(!($data = $cache->get('cache_id'))
{
    $data = getData();
    $cache->add('cache_id', $data);
}
$cache->delete('cache_id');

Folosirea extensiei APC:

if(!($data = apc_fetch('cache_id'))
{
    $data = getData();
    apc_add('cache_id', $data);
}
apc_delete('cache_id');

Diferenţa dintre cele 2 extensii este aceea că Memcache va stoca informaţiile în memoria RAM a serverului, pe când APC le va stoca in fişiere pe hard disc.

UPDATE:

A Practical Guide to Data Caching with Zend Server scrisă de Shahar Evron, Product Manager la Zend Technologies, Inc., este o lucrare apărută recent şi pe care o recomandăm celor interesati de acest subiect.

Până la un subiect mai serios, care se află deja în scriere, vin cu o problemă, ce se adresează celor care se considera începători. Mi s-a întamplat sa dau această “problemă” unora de nivelul celui menţionat mai devreme, iar răspunsurile să  mă surprindă de fiecare dată.

Aşadar, dându-se un array (vector) cu 2 milioane de elemente, indexate corespunzător de la 0 la 1.999.999 cu valori de tip string, să zicem sub 1 KB ca dimensiune. 

Scrieţi o funcţie care să determine dacă vectorul în cauză este sau nu mulţime. Evident, se cere un timp de execuţie al funcţiei minim

Un vector este considerat mulţime dacă are toate valorile distincte

Citeam zilele tecute pe The PHP Benchmark un articol  care compară diversele metode prin care se poate parcurge un vector asociativ. În mod normal, un foreach simplu care ne-ar funriza cheia şi valoarea aflată la cel index ar fi de ajuns la fel şi soluţia unui for cu indice de la zero la numarul maxim de elemente, în cazul în care cheile vectorului ar fi numere întregi. Cu toate acestea însă, în cazul unui volum mare de date utilizarea unui foreach, uşoară şi comodă, poate duce la un timp de execuţie foarte mare

reset($aHash);
foreach($aHash as $key => $val)
{
    $aHash[$key] .= "a";
}

Exemplul de mai sus, are timp de executie 409 µs pe un vector de 100 de elemente.

$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++)
{
    $aHash[$key[$i]] .= "a";
}

Deşi cel de-al 2-lea exemplu presupune mai multe opreaţii decât primul, timpul de execuţie în conditiile în care furnizăm acelaşi vector de 100 de elemente este de 38 µs ... de 10 ori mai putin.

Una din primele întrebări pe care le adresăm unui programator începător dornic de cunoaştere aprofundată a programării web este: “Ce este web-ul?”. Deşi o întrebare cu un răspuns aparent simplu, rar ni se întamplă să primim un acelaşi răspuns. Pentru fiecare dintre noi web-ul înseamnă altceva, şi se referă întotdeauna la motivele pentru care îl utilizăm. Poate fi un mediu de socializat, un bun suport de promovare, de comunicare, de făcut marketing şi implicit bani. Pentru unii însă, web-ul este o reţea interconectată de calculatoare, de maşini care interacţionează între ele pe principiul client-server.

Arhitectura client-server este o arhitectură de bază în funcţionare internetului, şi neînţeleasă de la început, poate face ca aprofundarea programării web şi a conceptelor din aceasta sa fie foarte greu de înţeles şi poate duce la confuzii care pot avea efecte devastatoare asupra unor aplicaţii lansate publice.

Client

Clientul este partea din această arhitectură care va iniţia comunicarea. Este cel care va trimite o cerere către un server pentru a primi un răspuns, nişte informaţii peronalizate pentru cererea care tocmai a făcut-o, date in general. Clientul poate fi un browser de web care se conectează la un server web, poate fi un client de e-mail(gen Thunderbird sau Microsoft Outlook) care se conectează la un server de email, trimite datele de autentificare pentru un cont de e-mail si cererea de primire a mesajelor noi, poate fi un client de FTP(gen Smart FTP sau Cute FTP) care va trimite unui server de FTP cererea de stocare pe acel server a unui fişier, urmat de fişierul în sine şi va primi ca răspuns confirmarea primirii acestuia sau un mesaj de eroare corespunzător. Aşadar clientul este cel care va acţiona şi va determina un întreg lanţ de acţiuni din partea server-ului: iniţiază cererea către server, aşteaptă raspunsul de la server, primeşte răspunsul de la server şi în final îl returnează utilizatorului posibil într-un mod formatat.

Server

Un server, în general, este probabil cea mai pasivă, indolentă şi comodă invenţie de pe internet. Serverul nu are niciodată iniţiativă, principala lui activitate este de a sta şi de a aştepta. Serverul nu va acţiona niciodată din cont propriu, nu va transmite date decât dacă este întrebat şi decât dacă sunt urmate anumite reguli de comunicare. Cu toate acestea, odată “deranjat” un server va face tot posibilul să mulţumească cererea clientului. Când este pornit, un server va lua poziţia de aşteptare de conexiuni (numită mai tehnic: listening state), de regulă acesta ascultă pe un anume port primirea conexiunilor. La primirea unei astfel de conexiuni, deci implicit a unei cereri, el va face toate demersurile necesare pentru a returna rezultatul aşteptat. Dacă este un server web, va întoarce clientului (browserul web) codul html al paginii care a fost cerută, dacă este un server de e-mail va returna clientului o listă cu toate email-urile pe care le-a primit de la ultima cerere, daca este un server de MySQL va prelua interogarea SQL primită o va executa şi va returna setul de date rezultat. Aşadar un server stă şi aşteaptă conexiuni pe care le va servi cererile de îndată ce au fost primite.

Diferenţe

Nu trebuie înţeles că toate calculatoarele personale, laptopurile sau orice maşină folosită în mod frecvent de o fiinţă umană este un client şi restul sunt servere. Noţiunea de client-server este întâlnită între oricare două maşini între care exista o cale de acces, o conexiune directă sau indirectă. De exemplu, să presupunem ca avem o maşină pe care ţinem o bază de date MySQL, o maşină pe care o folosim ca web server (deci unde stocăm fişierele unui web site) şi o maşină care va fi folosită drept client. Astfel, iniţiem o cerere de la client către serverul web pentru a primi o anumită pagina în care vom vizualiza, de exemplu, produsele de pe un magazin online. Serverul web primeşte cererea şi va acţiona întocmai, însă datele necesare generării acestei pagini HTML care va conţine informaţiile despre produse, se află în baza de date, deci pe un alt server. Serverul web, prin intermediul librariilor din PHP va iniţia acum o cerere către serverul de MySQL pentru a întoarce informaţiile despre produse, pe care cel din urmă le va furniza. În această ultimă situaţie serverul web este client pentru serverul de MySQL pentru că el este cel care iniţiază o cerere de date iar baza de date va furniza raspunsul la această cerere. În final, cu datele primite se va furniza pagina HTML complet formatată.

Protocolul HTTP este poate unul dintre cele mai simple protocoale. Este un protocol care tăieşte în prezent, nu are amintiri şi nici vise care speră să i se îndeplinească în viitor. Tradus în limbaj tehnic acest lucru înseamnă că un server web (care se foloseşte de protocolul HTTP) nu va şti şi nici nu-i va păsa dacă o cerere va veni de la acelaşi utilizator precum precedenta sau de la un utilizator nou. Acesta tratează fiecare cerere ca şi cum ar fi unică şi ca şi cum nu ar avea nici o legătură cu posibile cereri precedente sau viitoare. Acest lucru ar fi un dezavantaj evident, dacă nu ar exista diverse căi de a suplini acest neajuns. O astfel de cale se numeşte sesiune, iar limbajul PHP dispune de o serie de funcţii pentru a uşura lucrul cu sesiuni.

Sesiunile sunt folosite aşadar pentru a crea continuitate între accesările utilizatorilor unui site, chiar dacă aceste au loc la intervale relativ ridicate de timp. Sesiunile sunt stocate pe server, iar la cel mai jos nivel de reprezentare ele sunt nişte fişiere ce sunt stocate într-o zonă sigură a serverului de unde PHP-ul poate avea acces pentru citire, modificare sau creare. Ele sunt menţinute în relaţie cu clientul printr-un identificator de sesiune care va fi stocat la client de regulă într-un cookie(poate fi transmis şi printr-un parametru din GET).

Sesiunile de deschid în două modalităţi: imiplicit şi explicit. Implicit se face prin directiva de configurare aflată în fişierul php.ini numită session.auto_start, care setată pe valoarea 1 va porni implicit sesiunea la începutul rulării oricărui script PHP. Explicit, aceasta se realizează la cererea programatorului folosind funcţia session_start() de regulă la începutul fiecărui script. Avantaje si dezavantaje există de ambele parţi, prin pornirea implicită se evită apelarea în fiecare script al aplicaţiei a funcţiei session_start() însă datorită faptului că o sesiunea se va crea înainte de încărcarea fişierelor auxiliare folosite într-o aplicaţie, cum ar fi cele de definire a claselor, facând astfel imposibilă stocarea unor obiecte în sesiuni. Pornită explicit, funcţia session_start() va trebui apelată întotdeauna înainte de a trimite orice informaţie către client, deoarece aceasta va încerca stabilirea cookie-ului de sesiune, care va fi transmis printr-un header la client.

Informaţiile stocate pe sesiunie se pot regăsi în variabila superglobala $_SESSION care va fi creată automat la pronirea sesiunii şi care va conţine toate informaţiile deja existente pe sesiune. Se pot scrie, citi şi sterge informaţii din această variabilă, care beneficiază de toate avantajele oferite de vectorii limbajului PHP.

session_start();
$_SESSION['isLogged'] = true;
session_start();
if ($_SESSION['isLogged'])
{
    echo "Utilizatorul este autentificat";
}

Presupunând că cele 2 coduri reprezintă fişiere diferite, apelate în ordine vor genera afişarea mesajului de autentificare iar variabila se va păstra pe sesiune până la ştergerea ei sau pănă la distrugerea sesiunii.

O sesiune este persistentă până când aceasta este ştearsă sau până când ea expiră. Timpul de expirare este dat de către timpul de expirarea al cookie-ului păstrat în browser-ul clientului. Directiva session.cookie_lifetime din php.ini este setată implicit pe valoarea zero, şi înseamnă că sesiunea va expira în momentul în care utilizatorul va închide browser-ul. În cazul în care se doreşte forţarea unei sesiuni persistente se poate folosi funcţia session_set_cookie_params(). Distrugerea manuală a datelor existente într-o sesiune se poate face prin functia session_destroy()

cURL

cURL este un utilitar din linie de comandă folosit pentru transferul de fişiere ce pot fi accesate printr-un URL. Principalul scop şi utilizare a acestui program este automatizarea transferului de fişiere sau secvenţelor de operaţii, fiind o unealtă excelentă de a simula un client web şi acţiunile pe care le poate întreprinde un utilizator în browser-ul lui. cURL suportă transferul de date folosind mai multe protocoale cum ar fi: HTTP, HTTPS, FTP, FTPS, TFTP, Telnet, SCP precum şi altele.

Pentru ca această unealtă să poată fi folosita în diverse aplicaţii, a fost dezvoltată o librărie numită libcurl care poate fi inglobată în diverse aplicaţii şi care oferă aceeaşi funcţionlitate ca şi utilitarul în sine. Limbajul PHP deţine şi el o astfel de librărie, scrisă de către Daniel Stenberg, şi care oferă conectarea cu diverse servere folosind următoarele protocoale: http, https, ftp, gopher, telnet, dict, file şi ldap. Deasemenea libcurl suportă certificate de securitate, POST si PUT prin HTTP, încărcări de fişiere prin FTP sau din formularele HTTP, proxy-uri, cookie-uri sau autentificări folosind nume de utilizator şi parolă.

Un exemplu de folosire simplă este următorul:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://www.google.com');
curl_exec($ch);
curl_close($ch);

PHP are implementate o serie de funcţii pentru lucru cu această librărie. Un algoritm general de funcţionare este descris şi în exemplul de mai sus care nu face altceva decât să returneze acceseze pagina google.com. Astfel, la început am creat o nouă sesiune de cURL folosind funcţia curl_init, şi am stocat-o în variabila $ch. Acesteia i se pot adăuga diverse opţiuni care să personalizeze sesiunea respectivă. În final sesiunea se execută, adică cererea către google.com este făcută, iar la final se eliberează resursa creată.

Într-o sesiune de cURL se pot adăuga mai mulţi parametrii care vor determina comportamentul acesteia. Iată câteva dintre cele mai uzuale opţiuni, care vor fi setate folosind funcţia curl_setopt:

  • CURLOPT_URL – reprezintă URL-ul pentru care se crează sesiunea şi cu care se va încerca trimiterea/primirea de date
  • CURLOPT_USERAGENT – va seta semnătura pe care o va lăsa accesarea la server
  • CURLOPT_HTTPHEADER – reprezintă un vector în care se pot defini header-ele de HTTP care se vor folosi la accesarea serverului
  • CURLOPT_REFERER – reprezintă URL-ul de pe care vine cererea curentă, cel care a referit acest nou URL
  • CURLOPT_ENCODING – reprezintă tipul de arhivare aplicat transferului curent, de regula gzip
  • CURLOPT_RETURNTRANSFER – va face ca funcţia curl_exec să nu afişeze aceste rezultatul sesiunii ci sa-l returneze într-o variabilă.
  • CURLOPT_HTTPGET – indică faptul că se modalitatea de transmitere a datelor este HTTP GET; aceasta este modalitatea implicită de transmitere a dateleor, astfel că setarea ei este necesară doar dacă într-o aceeaşi sesiune a fost făcută anterior o conexiune diferită
  • CURLOPT_POST – va determina trimiterea unei cereri de tip HTTP POST, cea implicită fiind GET
  • CURLOPT_POSTFIELDS – poate fi folosit în cazul unei cereri de tip POST şi aici se vor specifica variabilele pentru POST
  • CURLOPT_TIMEOUT – reprezintă numărul de secunde pentru care se va reîncerca o conexiune în caz ca cea iniţială a eşuat
  • CURLOPT_PORT – este portul pe care se va face cererea şi este implicit 80 pentru o conexiune folosind HTTP şi 21 folosind FTP
  • CURLOPT_PROXY – este numele serverului de proxy prin care se va face conexiunea
  • CURLOPT_PROXYUSERPWD – numele de utilizator şi parola pentru acces la serverul proxy, dacă sunte necesare
  • CURLOPT_FOLLOWLOCATION – va transmite librăriei de cURL să urmeze un redirect în cazul în care serverul o va dicta
  • CURLOPT_SSL_VERIFYPEER – verifică valabilitatea certificatului de securitate în cazul unei conexiui folosind SSL
  • CURLOPT_SSL_VERIFYHOST – indică faptul că în cazul unei conexiuni securizate, certificatul SSL trebuie să fie emis pentru host-ul care a fost contactat de catre cURL
  • CURLOPT_UPLOAD – va fi folosit în cazul unui upload
  • CURLOPT_FRESH_CONNECT – indică faptul că se va folosi o conexiune nouă în loc de cea existentă în cache
  • CURLOPT_HTTP_VERSION – indică versiunea protocolului HTTP care va fi folosită pentru comunicare
  • CURLOPT_MAXREDIRS – numărul maxim de redirectări pe care conexiunea le va urma
  • CURLOPT_COOKIE – reprezintă cookie-urile care vor exista în momentul cererii către server
  • CURLOPT_COOKIEFILE – reprezintă un fişier în care sunt stocate unul sau mai multe cookie-uri care vor insoţi cererea către server
  • CURLOPT_COOKIEJAR – reprezintă un fişier în care se vor stoca cookie-urile provenite de la server

Pe lângă aceşti parametrii care pot fi aplicaţi asupra cererii, există şi diverşi parametri care pot furniza informaţii referitoare la răspunsul venit din partea serverului în urma executării cererii de către cURL. Acestea pot fi acceste folosind funcţia curl_getinfo:

  • CURLINFO_HTTP_CODE – va furniza codul de răspuns al cererii
  • CURLINFO_TOTAL_TIME – timpul total afecatat sesiunii
  • CURLINFO_EFFECTIVE_URL – reprezintă ultimul URL care a fost folosit in sesiunea cURL
  • CURLINFO_REDIRECT_TIME – timpul total afectat redirectărilor dictate de server
  • CURLINFO_SIZE_UPLOAD – numărul de bytes încărcaţi către server
  • CURLINFO_SIZE_DOWNLOAD – numărul de bytes primitţi de la server
  • CURLINFO_CONTENT_TYPE – tipul de conţinut primit de la server; este util pentru a distinge între un conţinut HTML şi unul care în mod normal ar fi oferit către download

O atenţie sporită trebuie avută la aceste opţiuni referitor la versiunea de PHP pe care o folosiţi, întru-cât ele au fost introduse de-a lungul timpului sau chiar recent şi nu pot exista în versiunea pe care o utilizaţi.

Trimitere de POST cu cURL

Vom încerca în cele ce urmeză să oferim câteva exemple de folosire a cURL, pentru a exemplifica puterea de lucru care o poate oferi acesta.

Să presupunem, aşadar, existenţa unui formular de autentificare pe un site care va cere utilizatorului adresa de email cu care acesta s-a înregistrat şi parola lui, după cum urmează:

Email: Password:

Putem crea astfel un script care să simuleze o autentificare întocmai precum un utilizator s-ar fi autentificat direct pe site.

$ch = curl_init("http://www.example.ro/post.php");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "email=blog@inphpwetrust.com&pass=foobar");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION , true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3");
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
curl_close($ch);

Am început prin a crea o nouă sesiune care se va conecta la adresa http://www.example.ro/post.php şi pentru care am stabilit că va fi de tip HTTP POST, prin optiunea CURLOPT_POST, am mai setat o pseudo semnătură a “browser-ului” care se va conecta la server prin opţiunea CURLOPT_USERAGENT ca fiind Mozilla Firefox versiunea 3.0.3, varianta pentru Microsoft Windows XP, am indicat ca orice redirecţionare indicată de către server să fie urmărită şi de cURL prin optiunea CURLOPT_FOLLOWLOCATION, CURLOPT_HEADER indică faptul că nici un header primit ca răspuns nu va fi stocat, iar CURLOPT_RETURNTRANSFER va detemina ca doar corpul mesajului să fie preluat, iar în final după execuţia sesiunii de cURL va fi afişat în browser. Opţiunea CURLOPT_POSTFIELDS este cea mai importantă din aceast exemplu şi este cea care va determina autentificarea efectivă, deci într-un final returnarea unui conţinut care ar putea fi văzut doar de o persoană care s-ar autentifica pe un site. Valorile pentru elementele HTML de tip input email şi pass au fost setate aici, ele fiind credenţialele pe care aplicaţia de pe server va efectua autentificarea. Evident acesta este un exemplu simplu şi uşor de “hack-uit”, în ziua de azi multe din site-uri verifică veridicitatea utilizatorului folosindu-se de mai multe elemente sau afişează o imagine cu nişte caractere de recunoscut care pot fi mai greu sau chiar imposibil de nimerit folosind un script PHP.

Upload prin FTP folosind cURL

Deşi sunt şi alte metode de a încărca un fişier pe un server folosind protocolul FTP, sau simplu pentru a citi conţinutul unui director FTP, chiar şi limbajul PHP deţine niste funcţii de lucru cu FTP, cURL poate fi de ajutor atunci când acestea nu sunt dispobilie sau când se doreşte o mai mare flexibilitate în lucrul cu acest protocol. Următorul exemplu va folosi un handler de fişier creat cu funcţia fopen care va fi pasat parametrului CURLOPT_INFILE.

$fp = fopen('image.jpg', 'r');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'ftp://ftp.domain.com/');
curl_setopt($ch, CURLOPT_USERPWD, "ftpuser:ftppass");
curl_setopt($ch, CURLOPT_UPLOAD, true);
curl_setopt($ch, CURLOPT_INFILE, $fp);
curl_setopt($ch, CURLOPT_INFILESIZE, filesize($localfile));
curl_exec ($ch);
curl_close ($ch);
fclose($fp);
PHP RefCard

PHP RefCard

Am aflat zilele trecute citind feed-ul de la Zend Developer Zone cum că DZone a lansat mult aşteptatul PHP RefCard. Pentru cei care nu ştiu, acest RefCard reprezintă o broşură frumos formatată şi ordonată cu informaţii utile referitoare la un anume limbaj de programare.

PHP RefCard este realizat de către Jason Glimore, autorul a câtorva cărţi de referinţă cum ar fi Beginning PHP and MySQL: From Novice to Professional, Beginning PHP and Oracle: From Novice to Professional (Expert’s Voice) sau Beginning PHP and PostgreSQL 8: From Novice to Professional.

Recomand acest RefCard tuturor începătorilor ca un mic ghid asupra problemelor comune ce pot apărea referitor la configurare, programarea orientată obiect, stringuri, vectori, expresii regulate sau integrarea cu MySQL, precum şi diverse informaţii utile.

Un trigger reprezintă o “subrutină” stocată în baza de date care conţine cod executabil, al cărei execuţii se va declanşa automat la întâmplarea unui anume eveniment. Spre deosebire de procedurile stocate sau funcţiile definite de utilizator care sunt definite global într-o bază de date, trigger-ii sunt strict legaţi de tabele, ei se definesc în relaţie cu acestea iar acţiunile lor sunt menite să aibă sens asupra lor sau pornind de la fiecare în parte.

Există două tipuri de trigger-i, cei care afectează toate înregistrările dintr-o tabelă şi cei care afecteză doar o anume înregistrare. Ambele însă au efect(se declanşează) atunci când una din următoarele operaţii este lansată asupra tabelei: INSERT, UPDATE, DELETE, iar pentru aceste operaţii se vor defini trigger-i care au efect ÎNAINTE sau DUPĂ ce instrucţiunea a fost executată pe baza de date.


CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt

În MySQL trigger-ii au fost introduşi în versiunea 5.0.2 iar pentru crearea unuia este necesară deţinerea de drepturi corespunzătoare. Clauza DEFINER va stabili nivelul de drepturi necesare pentru a se putea executa trigger-ul;trigger_name reprezintă numele trigger-ului, iar trigger_time şi trigger_event definesc situaţia în care trigger-ului se va executa.

trigger_time reprezintă momentul în care se va executa trigger-ul relativ la operaţia ce s-a lansat pe acea tabela, şi poate avea următoarele valori: BEFORE şi AFTER. trigger_event reprezintă însuşi acţiunea la care se face referire, iar acest parametru va avea una din următoarele valori:

  • INSERT caz în care trigger-ul va fi lansat la execuţia unei instrucţiuni INSERT, LOAD DATA sau REPLACE pe baza de date;
  • UPDATE caz în care trigger-ul se va lansa la apelul unei instrucţiuni UPDATE
  • DELETE caz în care trigger-ul va fi lansat la ştergerea de înregistrări folosind instrucţiunea DELETE; trigger-ul nu va fi lansat la execuţia instrucţiunii TRUCATE sau DROP TABLE

tbl_name reprezintă numele tabelei la care trigger-ul va reacţiona iar trigger_stmt reprezintă instrucţiunea SQL ce va fi lansată; în caz că se doresc execuţia mai multor instrucţiuni acestea vor fi introduse intre cuvintele BEGIN şi END. Toate restricţiile discutate în cadrul articolului Subrutine în MySQL sunt valabile şi pentru trigger-i.


/*
trigger ce va fi lansat inaintea unei instructiuni INSERT
*/
CREATE TRIGGER trTestInsert BEFORE INSERT ON products
FOR EACH ROW BEGIN
SET NEW.products_guid = UUID();
INSERT INTO audit (user_id, products_guid, audit_action) VALUES (NEW.users_id, NEW.products_guid, 'insert');
UPDATE categories SET categories_count = categories_count + 1 WHERE categories_id = NEW.categories_id;
END;
/*
trigger ce va fi lansat dupa o instructiune DELETE
*/
CREATE TRIGGER trTestDelete AFTER DELETE ON products
FOR EACH ROW BEGIN
SET NEW.products_guid = UUID();
INSERT INTO audit (products_guid, audit_action) VALUES (OLD.products_guid, 'delete');
UPDATE categories SET categories_count = categories_count - 1 WHERE categories_id = OLD.categories_id;
END;

În definirea celor 2 trigger-e am folosit două cuvinte rezervate MySQL, şi anume: OLD şi NEW. Prin aceste două referinţe vă puteţi adresa la câmpurile din înregistrările care au fost sau urmează a fi procesate. Astfel la o inserare în baza de date NEW va conţine toate câmpurile din înregistrarea pentru care a fost declanşat acel trigger, pe când la un trigger pe o instrucţiune UPDATE, în OLD veţi găsi toate informaţiile legate de înregistrarea ce a fost modificată.

Trigger-ii ajută la creşterea gradului de automatizare a unor procese, pot fi folosiţi foarte bine la logarea de date sau informaţii, ori folosit la crearea datelor de audit. Nu în ultimul rând pot fi folosiţi şi la aplicarea unor restricţii suplimentare asupra operaţiunilor făcute asupra bazelor de date.