Although I’d normally write this article in Romanian I expect a wider audience for it, so I will post it in English.

Snippet

If you don’t want to read the entire article here are some shortcuts:

Dance-Tunes.com

Dance Tunes Logo

Dance-Tunes is a music online store, located in the Netherlands, where one can buy their favorite … dance tunes, of course. Organised in eight main genres (Downtempo, Dance, Techno/Minimal, Hardcore, Breaks, Electro/House, Hardstyle/Jump and Trance) Dance-Tunes has an extensive database of tunes, artists and labels.

The Public API

On the 22nd of February 2010, Dance-Tunes released their 1.0 version of the API. The main purpose of the API is to help create rich internet applications that are based on the information of tunes, artists, labels or charts. Developers are encouraged to use the public Dance-Tunes API to get creative and use the extensive library of labels, artists and tracks in their sites or applications.

Dance-Tunes has created a special page for developers where one can find all information about the public API. Each individual API call is explained and examples are given to show the correct structure of the calls and response. One can request an API key by logging in using the Dance-Tunes account details. The API key will be sent to the developer’s email address. One can download the API documentation in pdf format for printing and off-line viewing.

The PHP Client

The Dance-Tunes PHP API Client that I am releasing today, was written with intention of integrating into a Zend Framework application. As of this it follows the PHP File Formatting, Naming Conventions and Coding Style recommended by the Zend Framework Team.

It uses the Zend_Http and Zend_Exception packages from the Zend Framework classes. It can be used either with an entire Zend Framework application or as is. The dependencies can be load by themselves (just the 2 packages), or the Dancetunes class can be modified in order to be used in a simple PHP project. One can replace the use of Zend_Http package with the use of the cURL library and the Zend_Exception with the PHP base Exception.

Usage

Each of the documented function in the Dance-Tunes API has a corespondent wrapper method in the Dancetunes class that accepts the exact amount of parameters that the API requires, except the command and the session(params cmd and session) which are built in. For each session key, as the documentation specifies, a timestamp is updated on each request, and the session key is deleted if no request is performed for 6 hours. So, as a best practice, it is recommended to cache the session key locally for immediate access to the resources on other requests.

$dancetunes = new Dancetunes(DT_API_KEY);

if (!($sessionKey = $cache->load('sessionkey')))
{
    $dancetunes->setUserName(DT_USERNAME);
    $dancetunes->setPassword(DT_PASSWORD);
    
    $sessionKey = $dancetunes->authenticate();
    $cache->save($sessionKey, 'sessionkey');
}
$dancetunes->setSessionKey($sessionKey);

/* Get the first 10 releases */
$releases = $dancetunes->getNewReleases(11, 10, 1, 10);

/* Get a preview */
$audio = $dancetunes->getPreview( 123456 );
/* saving the audio content to disc */
file_put_contents('123456-tune-preview.mp3', $audio);

Responses

Dance-Tunes API will respond either with an XML string or with binary data (for artwork and preview). For the case of XML response I have created a Dancetunes_Vo class that takes a XML string and converts it into an array. Also for each response type I have created an individual class that extends the Dancetunes_Vo class and implements some particular methods for a specific result. For example the Dancetunes_NewReleases class will be the result of the Dancetunes::getNewReleases method and has a method for returning just the tunes info from the response, and another one to return the number of total possible tunes that can be returned on a specific request.

class Dancetunes_NewReleases extends Dancetunes_Vo {
    
    public function getTracks() {
        return $this->_array ['new_releases'] ['tune'];
    }
    
    public function getCount() {
        return $this->_array ['new_releases'] ['total_count'];
    }
}

These classes can be modified or extended in order to deal in a specific way with the XML response.


License

The package is released as an open source project under The New BSD License.

Copyright (c) 2010, George Enciu, ColorSoft System SRL, Romania

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of the ColorSoft System SRL nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Change Log

2010-04-06
  • Version 0.1 BETA released
2010-05-15
  • Added a version written in simple PHP
  • Still in 0.1 Beta

Download

All the code and download files can be found at the Google Code Project

Vă doresc un an nou mai bun, sau cel puţin la fel de bun pe cât a fost 2008, plin de realizări şi, evident, cu mult cod.

La multi ani!

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ă.

Un punct de reper pentru a descoperi un programator bun, indiferent de limbajul de programare pe care îl utilizează, este abilitatea acestuia de a aplica tehnici cunoscute de design al aplicaţiei. Design patterns(sau proiectarea modelelor) sunt un un set de soluţii ce pot fi aplicate la diverse probleme ce se întâlnesc frecvent în cadrul programării orientate pe obiect.

În teorie, aceste modele nu au legătură cu codul scris de un programator. Sunt niște soluţii de eficientizare a problemelor des întâlnite, nişte indicaţii pe care programatorul trebuie să le transpună în limbajul de programare pe care îl foloseşte. Ne vom referi în continuare la aceste modele legat de limbajul PHP, modele care deşi pot fi implementate folosind şi programarea procedurală, ele sunt evidenţiate mai bine cu ajutorul programării orientate pe obiect.

Singleton

Modelul Singleton este probabil cel mai simplu model. El este făcut pentru a permite accesul la o singură resursă care nu va fi niciodată duplicată, dar care trebuie făcută disponibilă în orice moment al execuţiei aplicaţiei. Implementarea unui Singleton trebuie să satisfacă cele două condiţii de acces global şi instanţiere singulară a resursei respective. Are nevoie de un mecanism de acces la class Singleton fără a instanţia un obiect şi un mecanism de a păstra informaţia asupra resursei ce urmează a fi folosită. Astfel acest model poate fi cel mai uşor implementat prin crearea unei clase ce va conţine o metodă care va crea o nouă instanţă a clasei, dacă aceasta nu exista deja, iar dacă există o va returna pe aceasta. Pentru a fi siguri că acea clasă nu va fi instanţiată în alt fel, constructorul este declarat protejat. Exemplul cel mai simplu este acela în care se pune problema existenţei unei singure conexiuni la baza de date:

class database 
{
    private static $_singleton;
    private $_connection;
    
    protected function __construct()
    {
        $this->_connection = mysql_connect(SERVER, USER, PASSWORD);
    }
    
    public static function getInstance()
    {
        if (is_null(self::$_singleton))
        {
            self::$_singleton = new database();
        }
        
        return self::$_singleton;
    }
}

$cnx = database::getInstance();

Aşadar constructorul a fost declarat protejat pentru a controla accesul la el şi de a da posibilitatea apelării lui doar din interiorul clasei. Acest lucru este făcut cu ajutorul metodei statice getInstance, care verifică la fiecare apel al ei dacă există definit membrul static $_singleton, caz în care îl va returna, sau va încerca să creeze unul nou. Odată creat, metoda getInstance() nu va mai încerca să creeze unul nou ci-l va retuna pe cel creat tot timpul.

Factory

Modelul Factory se foloseşte în scenariile în care există o clasă generică (fabrica) care are posibilitatea de a crea o instanţă a una sau mai multe clase specializate în rezolvarea unei aceleaşi probleme dar în diferite moduri. Un astfel de exemplu îl reprezintă stocarea datelor de configurare într-o aplicaţie. Datele de configurare, precum credenţialele pentru conectarea la baza de date, sau căile de acces la diverse directoare, pot fi stocate în mai multe feluri: în fişiere XML, in fişiere INI sau chiar în baza de date. Astfel putem crea un mecanism care să returneze un obiect ce va şti să lucreze cu un anume mod de stocare a acestor date de configurare.

class configuration 
{
    const STORE_IN_INI = 1;
    const STORE_IN_DB = 2;
    const STORE_IN_XML = 3;
    
    public static function factory($type = self::STORE_IN_INI)
    {
        switch ($type)
        {
            case self::STORE_IN_INI:
                return new configuration_ini();
            case self::STORE_IN_DB:
                return new configuration_db();
            case self::STORE_IN_XML:
                return new configuration_xml();
            default:
                throw new Exception('Tip de stocare necunoscut');
        }
    }
}

Este evident faptul că toate cele 3 clase specializate de tratare a fișierelor de configurare (ini, xml, db) trebuiesc definite şi implementate, un avantaj fiind definirea lor conform unei clase abstracte. Apelarea “fabricii” se face după cum urmează:

$storage = configuration::factory(configuration::STORE_IN_XML);

Registry

Modelul Registry este un model Singleton mai evoluat, în sensul că permite stocarea mai multor resurse, ca într-un registru. Un exemplu ar fi acela în care, deşi avem conexiunea stabilită către baza de date printr-un Singleton, şi prin această conexiune se fac toate interogările, există posibilitatea ca din aplicaţie să fie nevoie de conexiunea în paralel către o alta bază de date pentru a efectua şi acolo diverse operaţiuni. Cu un simplu Singleton nu s-ar fi putut realiza aşa ceva însă cu un Registry acest lucru este posibil. Un astfel de Registry va trebui să implementeze metode de adăugare, verificare a existenţei şi de returnare a informaţiilor cerute.

class registry
{
    private static $_register;
    
    public static function add(&$element, $name)
    {
        $name = strtolower($name);
        self::$_register[$name] = $element;
    }
    
    public static function exists($name)
    {
        $name = strtolower($name);
        if (array_key_exists($name, self::$_register))
        {
            return true;
        }
        return false;
    }
    
    public static function &get($name)
    {
        $name = strtolower($name);
        if (self::exists($name))
        {
            return self::$_register[$name];
        }
        else 
        {
            throw new Exception('Elementeul cerut nu exista in registru.');
        }
    }
    
}

O folosirea a cestul model este următorul cu precizarea ca în acest exemplu clasa database nu este un Singleton:

$db = new database();
registry::add($db, 'dbCnx');
/*
verificarea conexiunii
*/
if (registry::exists('dbCnx'))
{
    $db = registry::get('dbCnx');
}

Model-View-Controller

Spre deosebire de celelate modele discutate anterior, Model-View-Controller(denumit şi MVC) este un model destul de complex. Scopul lui este de a oferi o metodă de separare dintre logica aplicaţiei (model), modului de afişare (view) şi structura decizională (controller), aplicaţiile MVC fiind astfel usor de modificat atunci când se doreşte modificarea doar a modului de afişare sau a modului în care este tratată o cerere.

Logica unei astfel de aplicaţii este următoare: o cerere este făcută către aplicaţie şi este apelat controller-ul care va decide cum va trata cererea. Controller-ul va chema o clasă din Model, care efectiv va efectua interogări către baza de date, prelucrările necesare ale datelor sau orice alte operaţiuni necesare. Rezultatul, de regulă un set de date, va fi întors către Controller care va transmite aceste date părţii vizuale a aplicaţiei(View) unde deja există o structură de afişare a datelor ce tocmai au fost primite. Avantajul enorm al acestui model este separarea clară între cele 3 compomente şi uşurinţa cu care se pot extinde aplicaţiile dezvoltate pe acest model.

Utilizarea unei zone tampon pentru returnarea rezultatelor este o tehnica de programare care poate aduce avantaje aplicatiei în diverse cazuri. Controlul iesirii (sau Output Control ori Output Buffering) se refera la controlul informatiilor trimise de la server catre client. Este util de cele mai multe ori sa putem controla cum se vor trimite informatiile(de regula codul HTML generat) astfel încât pe parcursul generarii lui sa putem trimite alte diverse header-e.

Protocolul HTTP este un protocol de comunicare pe internet foarte des întalnit, mai ales în cadrul serverelor web. Apache este unul care implementeaza acest protocol la comunicarea cu clientii care fac diverse cereri acestuia. Anatomia sa presupune, atat la cerere cât si la raspuns, trimiterea unor header-e(prin care se vor specifica în ce fel sa fie interpretat mesajul ce urmeaza a fi trimis) si mesajul propriu-zis, care poate fi o cerere sau informatii formatate. Astfel, presupunând ca vrem accesarea site-ului google.com, browser-ul va trimite un mesaj de cerere catre webserver-ul respectiv cerând accesul la o pagina de pe server, în cazul nostru cea implicita. Serverul web va raspunde clientului cu un set de header-e prin care se va specifica ce tip de formatare va avea informatia ce urmeaza sa o primeasca, ce lungime are, ce codare de caractere trebuie sa foloseasca pentru a putea fi interpretata corect etc.

În cazul unui server pe care ruleaza PHP si în cazul în care serverul primeste o cerere pentru un fisier ce contine un script PHP, serverul va începe sa trimita catre client informatiile odata cu:

  • întâlnirea unui tag HTML
  • la orice apel al functiei echo, print, var_dump sau print_r
  • chiar la o linie goala întâlnita în orice fisier php, chiar si între blocurile de cod php


echo "Serverul Apache va incepe trimiterea de informatii din acest moment.";
?>



Corpul fisierului HTML generat pentru IP-ul


În exemplul de mai sus interpretorul de PHP va transmite serverului Apache sa înceapa trimiterea datelor catre client chiar de la instructiunea echo, si nu de la întâlnirea tagului de marcarea a începutului de document HTML cum ar fi fost normal. Sa presupunem însa ca, pentru fiecare accesare a acestui fisier, dorim sa retinem continutul returnat utilizatorului într-un fisier pe server, desi pentru exemplul furnizat, motivatia acestei operatiuni este nu mai mult decât didactica. Vom folosi asadar functiile PHP pentru controlul iesirii, functii fare pot fi recunoscute usor dupa prefixul ob_.


ob_start();
echo "Serverul Apache va incepe trimiterea de informatii din acest moment.";
?>



Corpul fisierului HTML pentru IP-ul


$html = ob_get_contents();
$f = fopen('log.txt', 'w');
fwrite($f, $html);
fclose($f);
ob_end_flush()
?>

Functia ob_start marcheaza începerea memorarii informatiilor generate de scriptul PHP fara însa a le trimite catre client. În orice moment al scriptului, înainte de trimiterea codului HTML generat, se pot accesa informatiile din zona tampon folosind functia ob_get_contents, lucru pe care l-am facut si noi pentru a stoca informatiile pe server. La final functia ob_end_flush va trimite informatiile generate catre client si va opri stocarea lor în zona tampon.

Serverul web are totusi niste header-e standard pe care le trimite odata cu fiecare cerere pe care o primeste. Din PHP aceste header-e se pot modifica sau se pot adauga folosind functia header. Cum aceasta functie va trimite direct header-ul catre client, ea trebuie apelata înainte ca orice continut sa fie trimis catre client, nerespectarea acestei reguli va genera un avertisment. Pentru a evita acest lucru se foloseste controlul iesirii.

Iesirea comprimata

Un avantaj al stocarii unei pagini web la server pâna aceasta a fost generata complet este acela ca, în cazul unui volum mare de date transferat, acestea pot fi comprimate pentru a facilita transportul lui peste internet catre clientul care a facut cererea, obtinând astfel un timp mai bun de raspuns. Functia ob_start poate primi ca prim parametru numele unei functii care va fi aplicata asupra codului generat dupa ce acesta a fost generat complet. Astfel, functia ob_gzhandler a fost special creata pentru a comprima datele generate folosind formatul gzip. Urmatorul exemplu va dispune trimiterea de date comprimate catre client:


ob_start("ob_gzhandler");

Vorbeam într-un articol precedent cum că suportul pentru obiecte a fost regândit şi rescris odată cu versiunea 5 a limbajului de programare PHP, pentru a oferi mai multă flexibilitate programatorilor prin facilităţi noi dar şi prin performanţă sporită.

Două dintre facilităţile nou oferite sunt clasele abstracte şi interfeţele. Acestea derivă din teoria programării orientate pe obiect şi sunt nişte unelte puternice pentru crearea de module integrabile sau de diverse interfeţe ale aplicaţiilor şi sunt foarte utile în cazul proiectelor dezvoltate în echipe numeroase de programatori.

Abstractizarea

Abstractizarea este procesul de simplificare a realităţii complexe prin modelarea de clase cât mai generale şi cât mai apropiate de problema care se tratează. De exemplu, în realitatea de zi cu zi o clasă generală numită maşină are un algoritm general de funcţionare, deşi un BMW s-ar putea sa meargă mai bine decât un Renault deşi ambele au în componenţa lor un motor, 4 roti şi un volan, asemenea unei Dacii.

Revenind la problema programării orientate pe obiect, o clasă abstractă este o clasă care nu se poate instanţia, deci care nu poate avea direct obiecte de sine stătătoare. Aceasta poate fi doar moştenită deci are sens în limbajele de programare care suportă această facilitate. Ca şi în realitate, folosirea acestor clase se face când se doreşte crearea de concepte abstracte, care apoi vor fi extinse prin adăugarea de funcţionalităţi noi şi particularizate pentru fiecare moştenire. Aceste clase vor fi definite în aşa fel încât clasele moştenitoare vor trebui sa implementeze acele metode, creând astfel clase generice, care vor avea de cele mai mult ori metode goale sau parţial implementate. Clasele care vor moşteni aceste clase abstracte sunt clase concrete, şi vor trebui să aibă o funcţionalitate concretă, bazată pe scheletul abstract din clasa părinte.

PHP 5 introduce aşadar, conceptul de abstractizare a claselor şi a metodelor. Astfel, orice clasă care conţine o metodă abstractă va trebui declarată ca fiind abstractă. Aceste metode declarate abstracte sunt doar declarate, nu şi implementate, deci nu vor conţine în corpul lor cod sursă. În cadrul claselor moştenite, toate acele metode declarate abstracte vor trebui redeclarate şi implementate, însă cu o vizibilitatea cel puţin egală cu cea declarată în clasa abstractă. Clasele abstracte pot conţine şi metode neabstracte, care pot fi şi definite în cadrul clasei abstracte şi care vor fi moştenite ca atare în clasele copil.

Vom exemplifica acest concept cu o situaţie întâlnită în practică. Vom considera un magazin virtual care acceptă la plată mai multe modalităţi de plată cum ar fi: plata la livrare, plata cu cardul, plata prin ordin de plata, plata prin rate bancare, plata prin PayPal etc. Se poate observa în situaţia curentă că problema a cărei soluţii încercăm s-o găsim este cea a plăţii, deci putem abstractiza o clasă generică numită plată. Această clasă va defini structura unei astfel de modalităţi de plată şi totodată fluxul care îl va urma orice proces de plată. Moştenitorii acestei clase abstracte vor trebui să implementeze aceste metode generale în stilul specific modalităţii de plată care o reprezintă. Sa considerâm următoarele scenarii:

  • în cazul în care se alege plata la livrare nu se va efectua nici o operaţiunie specială
  • în cazul plăţii folosind un card bancar, clientul va fi redirecţionat către un procesator de plăţi online care îi va procesa cererea de plată şi va reîntoarce clientul la magazin alături de răspunsul de confirmare sau eroare a tranzacţiei
  • în cazul plăţii prin ordin de plată se va mai completa o lista de plaţi prin OP pentru a fi verificate ulterior
  • în cazul plăţii prin rate bancare comanda respectivă va fi pusă într-un status special, de aşteptare, pentru a primi documentaţia necesară aprobării creditului de la client
abstract class payment
{
    protected $info, $customer_id, $amount;
    
    public function __construct($customerID, $amount)
    {
        
    }
    
    abstract public function doBeforeProcess() ;
    
    abstract public function process();
    
    abstract public function doAfterProcess();
    
    public function getInfo()
    {
        return $this->info;
    }
}

Aşadar am definit această clasă abstractă care defineşte modul de funcţionare a unei modalităţi de plată. Plata propiu-zisă va fi efectuată în cadrul metodei process(); metodele doBeforeProcess() şi doAfterProcess() vor fi folosite pentru acontrola diverse acţiuni ce trebuiesc efectuate înainte respectiv după efectuarea plăţii. Iată şi implementările claselor moştenitoare:

class pay_at_delivery extends payment 
{
    public function __construct($customerID, $amount)
    {
        $this->info = "Aceasta metoda presupune achitarea produsului cash la primirea acestuia.";
        $this->customer_id = $customerID;
        $this->amount = $amount;
    }
    
    public function doBeforeProcess()
    {
        return true;
    }
    
    public function process()
    {
        return true;
    }
    
    public function doAfterProcess()
    {
        return true;
    }
}

Modalitatea de plată la livrare nu presupune nimic special, decât inserarea în baza de date a comenzii clientului care vom presupune ca se realizează unitar în mediul în care se apelează modalitatea de plată. Metodele abstracte vor trebui implementate, ca regula a abstractizării, însă ele nu vor realiza nimic concret.

class credit_card extends payment 
{
    const PROCESSOR_SERVER = 'https://secure.credit-card-processor.com/cgi-bin/';
    const MERCHANT_ID = '1234567890';
    protected $post_params;
    
    public function __construct($customerID, $amount)
    {
        $this->info = "Veti fi redirectionati catre un procesator de carduri bancare pentru a finaliza plata.";
        $this->customer_id = $customerID;
        $this->amount = $amount;
    }
    
    public function doBeforeProcess()
    {
        $this->post_params = 'CUSTOMER=' . $this->customer_id . 
                             '&AMOUNT=' . $this->amount .
                             '&MERCHANT_ID=' . self::MERCHANT_ID ;
    }
    
    public function process()
    {
        $this->doPostToGateway($this->post_params);
    }
    
    public function doAfterProcess()
    {
        if ($transactionOK)
    	{
            $this->insertIntoSpecialDb($response);
    	}
    }
}

Şi în cazul plăţii prin card bancar am implementat toate metodele abstracte, însă aici am adăugat şi ceva funcţionalitate acestora. Majoritatea procesatorilor de carduri bancare pun la dispoziţie un gateway (interfaţă de comunicare) prin care se face transferul de informaţii de la comerciant la procesator şi invers. Acesta gateway va trebui accesat cu un set de parametrii pentru a identifica intenţia de tranzacţie şi la rândul lui, va returna un răspuns pentru a confirma(sau infirma) validitatea cardului şi a tranzacţiei efectuate de către client. Metoda de preprocesare va pregăti parametrii de identificare a tranzacţiei, care vor fi accesaţi din metoda de procesare. Metoda de trimitere a parametrilor se poate face prin cURL sau prin redirecţionare, şi am presupus-o funcţională în metoda doPostToGateway(). În final, în cazul în care tranzacţia este reuşită vom insera o înregistrare într-o tabelă separată din baza de date, cu ajutorul metodei insertIntoSpecialDb().

Cazul plăţii prin ordin de plată se va adăuga în postprocesare o înregistrare în tabela destinată acestor documente. În cazul plăţii prin rate, se va stabili o stare specială a comenzii de aşteptare a aprobării creditului. Metoda getInfo() declarată şi definită în cadrul clasei abstracte, va fi moştenită de către toate clasele copil şi va fi accesobilă în orice instanţiere a acestora. Este o metodă care va returna continutul variabilei protejate info care diferă de la o clasă la alta, li setată în cadrul constructorului.

Este evident că, deşi reală, situaţia dezvoltată de noi este una minimală. În cadrul unei procesări de carduri reală procesatorul va dori trimitearea mai multor parametrii, precum şi a unor chei criptate, pentru a evita frauda pe cât de mult posibil. La fel şi în cazul plăţii prin rate, procesarea poate fi constituită dintr-un formular care ar fi completat de către client cu datele lui financiare, iar postprocesarea ar putea însemna trimiterea pe email a formularelor completate şi a acordurilor de credit pentru a fi semnate de către acesta.

Interfeţe

O interfaţă nu este o clasă. O interfaţă nu este o clasă. Repetarea nu este o greşeală de tipar. O interfaţă este pur şi simplu o entitate care se defineşte folosind cuvântul cheie interface. O interfaţă nu are o implementare, este doar o semnătură, sau altfel spus are doar definiţia unor metode fără implementarea lor. Ca o asemănare cu abstractizarea, interfatarea crează doar scheletul unei clase ce urmează a fi implementate.

O interfaţă este aşadar un schelet care defineşte un set de metode ce urmează a fi suprascrise şi implementate în clasele copil. Clasa abstractă poate avea şi o funcţionalitate de bază care va fi moştenită în toate implementările ei.

interface iTemplate
{
    public function setVariable($name, $var);
    public function getHtml($template);
}
/*
implementarea interfetei
*/
class Template implements iTemplate
{
    private $vars = array();
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }
        return $template;
    }
}

O clasa poate implementa mai multe interfețe, dar nu poate moșteni mai multe clase abstracte. Implementarea multiplă se face doar dacă nu există conflicte de nume între clasele părinte.

PHP 5 aduce un nou model al programării orientate pe obiect cu o multitudine de îmbunatăţiri şi facilităţi, spre bucuria programatorilor deja obişnuiţi cu pseudo-modelul din PHP 4. O facilitate care poate ajuta în cazul unor proiecte cu module interconectabile, este cea numită metode magice. Acestea sunt un set de metode standard, rezervate în limbajul de programare, care au o aceeaşi semnificaţie şi o aceeşi funcţionalitate în cazul oricăror clase definite de utilizator. Aceşte metode sunt autoapelate în anumite condiţii sau în cazul unor acţiuni externe.

Constructor si destructor

Metodele __construct() şi __destruct(), deşi pot fi numite magice doar prin modul de scriere, ele făcând parte din teoria programării orientate pe obiect, sunt metodele care se vor apela automat la crearea (instanţierea) respectiv distrugerea unui obiect aparţinând unei clase. De regula, în constructor se crează conexiuni, se setează variabile, se preiau variabile externe sau orice altă acţiune de iniţializare a elementelor clasei, pe când în destructor se face curăţenie printre aceste elemente, se închid eventualele conexiuni sau se şterg fişiere.


class math
{
protected $a;
protected $b;

public function __construct($x, $y)
{
$this->a = $x;
$this->b = $y;
}

public function add()
{
return $this->a + $this->b;
}

public function multiply()
{
return $this->a * $this->b;
}

public function __destruct()
{
unset($this->a);
unset($this->b);
}
}

$math = new math(3, 8);
echo $math->add();
unset($math);
?>

Constructorul va fi astfel apelat în momentul creării obiectului folosind operatorul new iar destructorul va fi apelat în la apelul lui unset() asupra obiectului în mod expicit sau în mod implicit la terminarea execuţiei scriptului, daca nu s-a făcut altfel.

Serializare şi deserializare

Serializarea este procesul de a liniariza o variabilă de orice tip într-una de tip string, cu scopul de a pastra informaţiile din interiorul ei(precum si tipul/tipurile de date) şi pentru a le transmite la distanţă fără pierdere de informaţii. Deserializarea este, evident, procesul invers. Daca în cazul unui vector rezultatul serializării este un string ce conţine în ordine şi pe nivele elementele acestuia, în cazul claselor se oferă posibilitatea de a alege ce date să se pastreze sau să se porteze. Pentru acest lucru au fost create două metode magice care vor fi apelate automat la apelul serializării sau a deserializării: __sleep() şi __wakeup().


class math
{
protected $a;
protected $b;

public function __construct($x, $y)
{
$this->a = $x;
$this->b = $y;
}

public function add()
{
return $this->a + $this->b;
}

public function multiply()
{
return $this->a * $this->b;
}

public function __sleep()
{
return array('a', 'b');
}

public function __wakeup()
{
echo 'a=' . $this->a . '; b=' . $this->b;
}

public function __destruct()
{
unset($this->a);
unset($this->b);
}
}

$mathAdd = new math(3, 8);
echo $mathAdd->add(); /* va afisa 11 */
$tmpData = serialize($mathAdd);

$mathMultiply = new math(0, 0);
$mathMultiply = unserialize($tmpData);
echo $math->multiply(); /* va afisa 24 */
?>

De menţionat este că la apelul unserialize() şi implicit a lui math::__wakeup() scriptul va printa valorile primite de la prima instanţiere a clasei.

String

Există şi o metodă simplă, numită __toString() care va fi apelată la fiecare apel al lui echo, print sau încercare de folosire a obiectului ca un string.


class hello
{
protected $foo;

public function __construct($bar)
{
$this->foo = $bar;
}

public function __toString()
{
return "Hello " . $this->foo;
}
}

$hi = new hello('george');
echo $hi; /* va tipari "Hello george" */

?>

O altă metodă de transmitere a datelor între obiecte, posibil mai convenabilă decât serializarea şi deserializarea, poate fi făcută cu ajutorul funcţiei var_export() şi a metodei magice __set_state(). var_export() va returna un string care va conţine, fie valoarea numerica (în cazul variabilelor scalare de tip numeric), fie o valoare de tip string (cazul variabilelor string), fie stringul care ar crea un vector în cazul acestora, fie valoarea returnată de metoda magica __set_state() în cazul obiectelor. Această metodă magică a fost introdusă odată cu versiunea 5.1.0 a PHP.


class A
{
public $var1;
public $var2;

public static function __set_state($an_array)
{
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}

$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';

eval('$b = ' . var_export($a, true) . ';');
// $b = A::__set_state(array(
// 'var1' => 5,
// 'var2' => 'foo',
// ));

?>

Autoîncărcarea

Deşi nu este o metodă, funcţia __autoload() poate fi de mare ajutor atunci când aplicaţia dispune de un numar considerabil de clase care nu sunt folosite toate în acelaşi timp. Astfel, aceasă funcţie se va apela şi va include, dacă este nevoie, un fişier care va conţine definiţia clasei, în momentul instanţierii unui obiect. În scenariul de faţă, este de preferat ca toate definiţiile clasei să fie situate într-un acelaşi director, iar fiecare clasă situată într-un fişier separat al cărui nume sa fie identic cu al clasei.


function __autoload($className)
{
if (!class_exists($className))
{
include("includes/classes/" . $className . ".php");
}
}

Se evită in acest fel încărcarea tuturor claselor existente în aplicaţie şi îngreunarea acesteia luând în considerare presupunerea ca nu toate clasele vor fi folosite în oricare acţiune a aplicaţiei, prin faptul ca o clasa va fi încarcată doar dacă este nevoie si doar atunci când este nevoie.

Începem prin a propune ceva usor şi … teoretic.

Standardele de codare, sau coding styles, reprezintă un set de reguli care trebuiesc aplicate atunci când un programator îşi scrie propriul cod. Aceste reguli sunt făcute pentru a uşura comunicarea într-o echipă relativ mare de programatori care lucrează la acelaşi proiect, dar totodată asigură că orice viitor programator nou va putea înţelege intr-un timp cât mai scurt ideile codului.

Majoritatea companiilor mari au deja un standard de codare stabilit pe care îl impun noilor angajaţi. De altfel pentru fiecare limbaj de programare exista un set de astfel de reguli oarecum standardizate, cum ar fi C++, PHP, C#, Java, Python.

Chiar şi ca free-lancer este bine să-ţi creezi propriul stil de codare, pentru a preveni neplacerile cauzate de revenirile la un cod pe care nu ai mai lucrat un timp îndelungat.

Preferaţi un anume standard? De ce?

Salut!

Începem cu celebrul “Hello world” pentru a vă saluta şi pentru a vă dori o experienţă cât mai plăcută cu noi.

[code lang="php"]
echo 'Hello World!';
[/code]

sau

[code lang="sql"]
SELECT 'Hello World' AS greeting;
[/code]