Evitarea folosirii variabilelor globale in contextul local
<?php
$db = new PDO(...); // conexiunea cu baza de date
function getUsers() {
global $db; // accesarea variabilei $db in contextul
$users = $db->prepare(...);
//...
return $users;
}
$users = getUsers();
Probabil foarte multi ati folosit ( sau inca folositi ) o astfel de metoda pentru a avea acces in interiorul functiilor ( in contextul local ) la anumite variabile declarate in exteriorul functiilor ( in contextul global ).In cazul de fata, accesam variabila $db
declarata in contextul global, in interiorul functiei getUsers()
folosind cuvantul predefinit global
.
Aceasta este doar un exemplu, dar aceasta abordare poate fi folosita in peste tot in codul scris.
Ce este in neregula cu o astfel de abordare?
- Functia
getUsers()
este dependenta de variabila$db
fara de care nu poate functiona. Din cate stim, rolul functiilor este de a grupa mai multe randuri de cod pentru a organiza si evita repetarea scrieii acelorasi randuri de cod in mai multe locuri. In cazul de fata, fiind doar un exemplu, aceasta functie nu poate fi refolosita daca variabila$db
nu exista Iata un exemplu de functie ce poate fi refolosita fiind independenta de mediul exterior ei.
<?php
/**
* Functie folosita pentru a evita XSS
* XSS - utilizatorul rau intentionat injecteaza cod ( JavaScript.. etc )
* in codul aplicatiei folosirea unui form sau unui alt mijloc pentru
* a efectua actiuni daunatoare site-ului.
*/
function escape($string) {
return htmlspecialchars($string);
}
echo escape("<h1>Hello, World!</h1>");
Aceasta functie primeste INPUT si returneaza OUTPUT, aceasta fiind si modul in care o functie trebuie sa functioneze
- Valorea variabilei
$db
se poate schimba oriunde in cod ducand la crearea de probleme serioase.Exemplu de mai sus fiind unul general, in continuare vom intelege despre ce este vorba pe baza unui exemplu mai clar.
<?php
$config = [
'usersPerPage' => 10,
'articlesPerpage' => 5,
'commentsPerPage' => 15,
];
function doSomethingUseful()
{
global $config;
// indeplineste o sarcina necesara folosind variabila $config
return true;
}
function doSomethingMoreUseful()
{
global $config;
// alteram din greseala valoarea elementului
// ce are cheia articlesPerPage din variabila $config
$config['articlesPerPage'] = 100000;
return true;
}
function getArticles()
{
global $config;
// folosindu-ne de $config['articlesPerPage']
// vom returna un numar specific de article
$articles = [];
$articlesPerPage = $config['articlesPerPage'];
//...
// in cazul de fata, valoarea variabilei $config['articlesPerPage'] este 100000
return $articles;
}
doSomethingUseful();
doSomethingMoreUseful();
getArticles();
Fiind doar un exemplu, sa presupunem ca functia getArticles()
chiar va returna 100000 de articole, evident fiind o problema.Acum va trebuie sa cautam si sa testam fiecare functie ce foloseste variabila globala $config
pentru a depista locul unde a fost alterata valoarea elementului ce are cheia ``articlesPerPage```, ceea ce duce la o mare bataie de cap.
-
Dat fiind faptului ca functiile de mai sus nu specifica in nici un mod ca folosesc variabila
$config
, un programator care tocmai ce a aterizat la acest proiect dat ca exemplu, va ateriza pe o alta planeta, intr-o lume necunoscuta :) Va puteti imaginia voi singuri prin ce batai de cap va trece acel programator pentru a depista unde si ce anume a alterat acea valoare. -
Semnatura functiilor ( ceea ce se afla intre parantezele rotunde ) nu ne spune nimic ca aceste functii folosesc variabila
$config
. - Daca va ganditi sa folositi acelasi procedeu, dar de data aceasta folosindu-va de clase, ei bine, in primul rand veti incalca unul din conceptele OOP, si anume,
ENCAPSULATION
.Acest concept spune clar ca doar metodele clasei pot interactiona cu proprietatile acelei clase.
<?php
class User
{
public function register($username, $password)
{
global $db;
$user = $db->prepare(...);
//...
return true;
}
}
$db = new PDO(...);
$user = new User();
// nu avem nici o idee ca aceasta metoda foloseste variabila $db
$user->register('catalin', '1234');
Aceasta abordare va duce la tot felul de probleme, unele enuntate mai sus.
Cum putem evita aceasta abordare?
In primul rand, cand vorbim doar de functii, daca o functie are nevoie de variabile din exterior pentru a indeplini o anumita sarcina, aceste variabile trebuie adaugate la lista de argumente ale functiei.
<?php
$db = new PDO(...);
function getUsers(PDO $db) {
// folosindu-ne de valoarea argumentului $db
// se va efectua sarcina necesara
$users = $db->prepare(...);
return $users;
}
$users = getUsers($db);
Observam clar ca functia getUsers()
foloseste variabila $db
.De asemenea, am folosit Type Hinting
prin specificarea clara a tipului de date al argumentului $db
.Cu alte cuvinte, argumentul $db
poate avea valori ce sunt instante ale clasei PDO
.
<?php
class User
{
protected $db;
public function __construct(PDO $db)
{
$this->$db = $db;
}
public function register($username, $password)
{
// ne folosim de proprietatea $db pentru a interactiona cu baza de date
$user = $this->db->prepare(...);
//...
return true;
}
}
// salvam conexiunea cu baza de date
$db = new PDO(...);
// "injectam" conexiunea cu baza de date
$user = new User($db);
$user->register('catalin', '1234');
Mai sus am folosit modelul de design Dependency Injection
prin care injectam in constructorul clasei toate dependentele necesare clasei...