Dacă construiți un CMS, probabil că veți avea nevoie de roluri de utilizatori diferite - superuseri, administratori, utilizatori - cu diferite niveluri de permisiune. Prea complicată la cod? Introduceți ACL (Liste de control acces) al CakePHP. Cu configurarea corectă, veți verifica permisiunile utilizatorului cu o singură linie.
ACL vă permite să creați o ierarhie a utilizatorilor cu rolurile lor respective. Iată un exemplu rapid.
În acest tutorial, vom crea un ACL pentru un blog simplu. Dacă încă nu ați făcut check out de la CakePHP (și Partea 2) aici pe Nettuts +, vă rugăm să faceți acest lucru și apoi să vă întoarceți, deoarece vom lua de la sine cadrele de bază.
Cu această ierarhie, putem atribui mai multe permisiuni pentru fiecare rol:
Fiecare permisiune va fi acordată grupului, nu utilizatorului; astfel că dacă utilizatorul # 6 este promovat la Admin, el va fi verificat împotriva permisului de grup - nu lui. Aceste roluri și nodurile copil (utilizatori) sunt numite Obiecte de solicitare de acces sau ARO.
Acum, pe de altă parte, avem Obiectele de control al accesului sau ACO-urile. Acestea sunt obiectele care trebuie controlate. Mai sus am menționat posturile și utilizatorii. În mod normal, aceste obiecte sunt legate direct de modele, deci dacă avem un model Post, vom avea nevoie de un ACO pentru acest model.
Fiecare ACO are patru drepturi de bază: creați, citiți, actualizați și ștergeți. Le puteți aminti cu ajutorul cuvântului cheie CRUD. Există oa cincea permisiune, asteriscul, care este o comandă rapidă pentru accesul complet.
Vom folosi doar două ACO pentru acest tutorial: Post și Utilizator, dar puteți crea cât mai multe de care aveți nevoie.
Să continuăm să creăm tabelele bazei de date. Puteți găsi acest cod în db_acl.sql
în interiorul aplicației config / sql
director.
(10) DEFAULT NULL, model VARCHAR (255) DEFAULT ", foreign_key INTEGER (10) UNSIGNED DEFAULT NULL, alias VARCHAR (255) DEFAULT", lft INTEGER 10) DEFAULT NULL, INTEGER (10) DEFAULT NULL, KEY PRIMARY (id)); CREATE TABLE aros_acos (id INTEGER (10)) NOTĂ NULL AUTO_INCREMENT, aro_id INTEGER (10) UNSIGNED NOT NULL, aco_id INTEGER (10) UNSIGNED NOT NULL, _creare CHAR (2) NOT NULL DEFAULT 0, 0, _update CHAR (2) NOT NULL DEFAULT 0, _delete CHAR (2) NOT NULL DEFAULT 0, KEY PRIMARY (id)); (10) DEFAULT NULL, model VARCHAR (255) DEFAULT ", foreign_key INTEGER (10) UNSIGNED DEFAULT NULL, alias VARCHAR (255) DEFAULT", lft INTEGER 10) DEFAULT NULL, INTEGER (10) DEFAULT NULL, KEY PRIMARY (id));
Putem începe acum să creăm noduri ARO și ACO, dar hei, nu avem utilizatori! Va trebui să creăm un sistem de autentificare de bază.
Deoarece acest tutorial este destinat dezvoltatorilor CakePHP cu o cunoaștere de bază și moderată a cadrului, voi furniza codul și o scurtă explicație. Cu toate acestea, sistemul de autentificare nu este scopul acestui tutorial.
Tabela MySQL:
CREATE TABLE utilizatori (id INTEGER (10) AUTO_INCREMENT UNSIGNED KEY, numele de utilizator TEXT, parola TEXT);
Modelul utilizatorului (modele / user.php
)
Controlorul utilizatorilor (controlere / users_controller.php
)
Auth-> userModel = 'Utilizator'; $ This-> Auth-> permite ( '*'); function register () if (! empty ($ this-> data)) // Aici trebuie validat numele de utilizator (min lungimea, lungimea maxima pentru a nu include caracterele speciale, ca parolă dacă ($ this-> User-> validează ()) $ this-> User-> salvează ($ this-> data); // Să citim datele pe care tocmai le-am introdus $ data = $ this-> User-> read (); // Utilizați-l pentru autentificarea utilizatorului $ this-> Auth-> login ($ data); // Apoi redirecționați $ this-> redirect ('/'); funcția de conectare () if (! empty ($ this-> data)) // Dacă numele de utilizator / parola se potrivesc dacă ($ this-> Auth-> -> redirect ( '/'); altceva $ this-> User-> invalidate ('username', 'Combinația de nume de utilizator și parolă este incorectă!'); funcția logout () $ this-> Auth-> logout (); $ This-> redirecționeze ( '/'); ?>
Deoarece avem elemente noi, să le examinăm. În primul rând, noi stabilim o componente $
variabil. Această variabilă include toate componentele din matrice. Vom avea nevoie de Auth componentă, care este o componentă de bază, ca și ajutoarele HTML și Form, dar deoarece nu este inclusă implicit de Cake, va trebui să o includem manual.
Componenta Auth gestionează câteva mecanisme de autentificare de bază: ne ajută să conectăm un utilizator și gestionează o sesiune de utilizator autentificată pentru noi, precum și manipularea autentificării și a autorizației de bază pentru oaspeți. De asemenea, acesta dispune de parola în mod automat. Voi explica cum să apelați fiecare funcție în următoarele paragrafe.
Apoi, creăm o funcție numită beforeFilter
. Aceasta este o funcție de apel invers și ne permite să setăm câteva acțiuni înainte ca toate logica controlerului să fie procesată. Componenta Auth ne obligă să specificăm un model care trebuie utilizat, în acest caz Utilizatorul. Apoi, în mod implicit, va refuza tot accesul utilizatorilor care nu sunt conectați. Va trebui să suprascrieți acest comportament permite()
care necesită un parametru. Parametrul poate fi un asterisc, specificând că toate metodele din interiorul respectivului controler pot fi accesate de utilizatori neautorizați. Sau poate fi trecut un tablou cu funcțiile care pot fi accesate de utilizatori neautorizați. În acest caz, deoarece avem doar trei funcții, următoarele rânduri sunt același lucru.
$ This-> Auth-> permite ( '*'); $ this-> Auth-> permite (array ('register', 'login', 'logout'));
Pentru Logare()
, componenta Auth va gestiona toate mecanismele de conectare pentru noi. Va trebui doar să furnizăm funcția cu o matrice cu două chei: numele de utilizator și parola. Aceste taste pot fi modificate, dar în mod implicit, ambele nume de utilizator
și parola
câmpurile vor fi potrivite cu baza de date și vor fi returnate în cazul în care utilizatorul a fost autentificat.
În cele din urmă, funcția de conectare a controlorului va încerca să se potrivească cu o combinație de nume de utilizator / parolă în baza de date.
Rețineți că acest cod este foarte simplu. Va trebui să validezi caracterele de utilizator, dacă există numele de utilizator, lungimea minimă a parolei și așa mai departe.
Vizualizarea înregistrării (vizualizari / utilizatorii / register.ctp
)
Înregistrați-vă contul
Vizualizarea de conectare (vizualizari / utilizatorii / login.ctp
)
Conecteaza-te la contul tau
Deschis / Utilizatorii / registru
în browserul dvs. Web și înregistrați un nou cont. sugerez admin
ca nume de utilizator și 123
ca parolă și dacă sesiunea expiră doar du-te la / Utilizatori / autentificare
și introduceți combinația corectă de nume de utilizator / parolă pe care tocmai ați creat-o.
Nici măcar nu lucrăm cu ACL, dar putem nega deja postarea, editarea și ștergerea postărilor. Deschideți controlerul Posts și adăugați componenta Auth.
var $ components = array ("Auth");
Acum du-te la / posturi
în browserul dvs. web. Dacă v-ați conectat, ar trebui să vedeți mesajele, dar dacă nu sunteți, veți fi redirecționat (ă) / Utilizatori / autentificare
. Prin includerea pur și simplu a componentei Auth, toate acțiunile sunt implicite, refuzate pentru oaspeți. Trebuie să respingem trei acțiuni pentru utilizatorii neautorizați: crearea, editarea și ștergerea. În alte condiții, va trebui să permitem indexarea și vizualizarea.
funcția înainteFilter () $ this-> Auth-> userModel = 'Utilizator'; $ this-> Auth-> permite (array ('index', 'view'));
Du-te la editarea sau la crearea unei postări; dacă nu sunteți conectat (ă), ar trebui să fiți redirecționat (ă) / Utilizatori / autentificare
. Totul pare să funcționeze destul de bine, dar cum rămâne cu opiniile? Link-urile de editare și ștergere sunt afișate tuturor. Ar trebui să facem o condiție.
Dar, înainte de a intra în asta, să vedem cum funcționează funcția Auth (). Copiați și lipiți aceste linii în funcția de indexare.
$ user = $ acest-> Auth-> utilizator (); pr (utilizator $);
Deschide-ți / posturi
în browserul dvs. și, dacă sunteți conectat (ă), relatii cu publicul()
va arunca ceva de genul asta.
Array ([Utilizator] => Array ([id] => 1 [nume utilizator] => admin))
utilizator()
funcția returnează un matrice la fel cum ar face modelul. Dacă am avea mai mult de trei câmpuri (parola nu este inclusă), acestea vor fi afișate în matrice. Dacă nu sunteți conectat, matricea va fi goală, astfel încât să știți că un utilizator este autentificat dacă este autentificat utilizator()
matricea nu este goală.
Acum, Auth este o componentă, destinată a fi utilizată într-un controler. Trebuie să știm dintr-o vizualizare dacă un utilizator este conectat, de preferință printr-un ajutor. Cum putem folosi o componentă în interiorul unui ajutor? CakePHP este atât de minunat și flexibil încât este posibil.
class AccessHelper extends Helper var $helpers = array("Session"); function isLoggedin() App::import('Component', 'Auth'); $auth = new AuthComponent(); $auth->Sesiune = $ this-> Session; $ user = $ auth-> utilizator (); retur! ($ user); ?>
Salvați acest fragment în vizualizari / ajutoare
la fel de access.php
. Acum, să vedem codul, rând cu linie. În primul rând, suntem înființați o $ ajutoare
var. Ajutorii pot include și alți ajutoare, la fel componente $
poate sa. Componenta sesiunii este necesară pentru componenta Auth, dar nu avem acces la această componentă în cadrul unui ajutor. Din fericire avem un ajutor pentru sesiune, care ne va ajuta.
Apoi, vom crea o funcție și o utilizare App :: import
care ne va permite să importim un element în care în mod normal nu am avea acces. Următoarea linie creează componenta Auth în a $ auth
variabilă, iar acum un pic cam murdar; deoarece componenta Auth citește sesiunea pentru a ști dacă suntem logați sau nu, aceasta necesită componenta Sesiune, dar pe măsură ce o importăm dintr-un loc în care nu ar trebui să îi aparținem, va trebui să îi oferim un nou obiect Session. În cele din urmă, noi folosim utilizator()
și setarea la utilizator $
și întoarcerea adevărată dacă variabila nu este goală, altfel falsă.
Să ne întoarcem la postul de controlor și să continuăm să adăugăm ajutorul.
var $ helpers = array ("Acces");
Ajutorul de acces este acum accesibil din vizualizare. Deschis index.ctp
în vizualizari / posturi
și înlocuiți această linie.
"> edita | echo $html->link ('delete', '/posts/delete/'.$post['Post']['id'], NULL, "Ești sigur?"); ?>
Cu acesta.
if($access->isLoggedin ()):?>"> edita | echo $html->link ('delete', '/posts/delete/'.$post['Post']['id'], NULL, "Ești sigur?"); ?> endif; ?>
Reveniți la browserul dvs. Web, reîncărcați pagina index și dacă sunteți conectat (ă), veți vedea link-urile de editare și ștergere pentru fiecare postare. În caz contrar, nu veți vedea nimic.
În timp ce acest lucru ar fi suficient dacă aveți o aplicație cu unul sau doi utilizatori, nu este suficient dacă aveți înregistrări deschise.
Deschideți controlerul utilizatorilor și adăugați componenta ACL.
var $ components = array ('Auth', 'Acl');
Apoi, să creăm o funcție pentru a instala nodurile ACO și ARO.
install () if ($ this-> Acl-> Aro-> findByAlias ("Admin")) $ this-> redirect ('/'); $ aro = new aro (); $ Aro-> crea (); $ aro-> save (array ('model' => 'Utilizator', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Super')); $ Aro-> crea (); $ aro-> save (array ('model' => 'Utilizator', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Admin')); $ Aro-> crea (); $ aro-> save (array ('model' => 'Utilizator', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Utilizator')); $ Aro-> crea (); $ aro-> save (array ('model' => 'Utilizator', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Suspendat')); $ aco = nou Aco (); $ Aco-> crea (); $ aco-> save (array ('model' => 'Utilizator', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Utilizator')); $ Aco-> crea (); $ ac-> salvați (array ('model' => 'Post', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Post')); $ this-> Acl-> permite ('Super', 'Post', '*'); $ this-> Acl-> permite ('Super', 'Utilizator', '*'); $ this-> Acl-> permite ('Admin', 'Post', '*'); $ this-> Acl-> permite ('Utilizator', 'Post', array ('crea'));
Prin importul componentei ACL, putem accesa modelul ACO și ARO. Începem prin crearea unui ARO, care este solicitantul obiectelor; cu alte cuvinte, cine va accesa obiectele. Acesta ar fi utilizatorul (de aici șirul Utilizator în model). Creați roluri diferite; Super, Admin, Utilizator și Suspendat.
Apoi, facem același lucru cu ACO, deoarece avem doar două obiecte pentru a gestiona (Posts and Users), vom crea două, câte unul pentru fiecare.
Acum, o notă rapidă. Matricea pe care o salvăm atât pentru ACO, cât și pentru ARO are patru câmpuri: numele modelului, cheia externă (care este util dacă dorești să dai acces la un ARO, ca un post pe care la creat), id părinte (care va fi folosit mai târziu și este relația de bază a nodurilor părinte-copil, vom crea utilizatori sub aceste roluri) și aliasul (care este o formă rapidă pentru a găsi obiectele).
În cele din urmă, folosim ACL-urile permite()
funcţie. Primul parametru este aliasul ACO; în al doilea rând, aliasul ARO și, în al treilea rând, permisiunile acordate ARO. Un Superuser are acces deplin la modelele Post și User, un Admin are acces deplin la modelul Post și un utilizator poate doar să creeze postări.
La începutul funcției, am declarat condiționată verificarea rolului de administrator în ACO. Nu doriți să instalați același lucru de mai multe ori, nu-i așa? S-ar deranja destul de rău baza de date.
Deschis / utilizatorii / instala
în browserul dvs. web și, din moment ce nu avem o vizualizare, CakePHP va arunca o eroare, dar trebuie doar să verificați comanda MySQL. Toate relațiile au fost create cu succes, este timpul să lucrăm cu nodurile copilului.
Să curățăm utilizatori
masa. Deschideți phpMyAdmin, selectați baza de date, utilizatori
tabel și faceți clic pe gol. Vom reveni la Inregistreaza-te()
funcția de pe Controlorul utilizatorilor. Chiar sub această linie:
$ This-> Auth-> autentificare ($ date);
Inserați acest cod:
// Setați rolurile utilizatorului $ aro = new Aro (); $ parent = $ aro-> findByAlias ($ this-> User-> find ('count')> 1? 'User': 'Super'); $ Aro-> crea (); $ aro-> save (array ('model' => 'Utilizator', 'foreign_key' => $ this-> User-> id, 'parent_id' alias '=>' Utilizator :: '. $ this-> User-> id));
În prima linie vom crea un nou obiect ARO. Atunci vom obține nodul părinte în care va fi creat utilizatorul. Dacă există o înregistrare în baza de date, o vom seta pe User ARO, altfel, primul utilizator ar trebui să fie Super.
Apoi vom salva un nou ARO; modelul este cel pe care lucrăm în prezent, Utilizator
, cheie externă
este id-ul ultimelor înregistrări pe care tocmai l-am creat. PARENT_ID
este nodul cu care am început; o să-i transmitem id-ul și, în final, aliasul. Este o idee bună să o numiți Numele modelului
, apoi un separator ::
, și apoi identificatorul. Va fi mult mai ușor să o găsiți.
Acum am terminat. Creați patru utilizatori: superuser, adminuser, normaluser și suspenduser. Îți sugerez același nume de utilizator și o parolă pentru scopuri de testare. Nu uitați că, până în acest moment, doar superuserul are rolul de Super; restul vor fi Utilizatori!
Deoarece ACL este o componentă, este accesibilă numai în cadrul controlerului. Mai târziu, va fi și în vedere; dar mai intai lucrurile intai. Includeți componenta ACL în controlerul Posts, așa cum am făcut-o în controlerul utilizatorilor. Acum puteți începe să verificați permisiunile. Să mergem la funcția de editare și să facem un test rapid. În prima linie a metodei menționate, adăugați aceasta.
$ user = $ acest-> Auth-> utilizator (); dacă (! $ this-> Acl-> check ('Utilizator ::'. $ user ['User'] ['id'], 'Post', 'update')) die ('nu sunteți autorizat');
ACL Verifica()
funcția are nevoie de trei parametri: ARO, ACO și acțiunea. Du-te și editați o postare și, dacă nu sunteți logat ca super-utilizator, scriptul va muri. Mergi la / Utilizatori / autentificare
și accesați-l ca utilizator Super și reveniți la editare. Ar trebui să puteți edita postarea. Verificați sub dumpul MySQL pentru a vedea magia. Patru interogări de bază de date: care nu pot fi scalabile.
Avem două probleme. Primul, două linii pentru verificarea permisiunilor. În al doilea rând, ACL-urile nu sunt stocate în cache. Și nu uitați că toți utilizatorii înregistrați pot vedea linkul de editare, chiar dacă doar utilizatorul Super îl poate folosi.
Să creați o nouă componentă. Să o numim Acces.
user = $ this-> Auth-> utilizator (); ?>
Salvează-l controlere / componente
la fel de access.php
. Clasa e utilizator $
var va fi inițiat ca elementul încărcat și lansare()
este o funcție de apel invers, deci este acum setată în clasă. Acum să ne creăm Verifica()
funcţie.
($ user = "user") && $ a-> Acl-> check ('User ::'. $ this-> user ['User' ] ['id'], $ aco, $ actiune)) return true; altfel return false;
Al nostru Verifica()
metoda va necesita doar doi parametri: ACO și acțiunea (care este opțională). ARO va fi utilizatorul curent pentru fiecare sesiune. Parametrul de acțiune va fi implicit la *
, care este acces complet pentru ARO. Funcția începe prin a verifica dacă $ This-> utilizator
nu este gol (ceea ce ne spune cu adevărat dacă un utilizator este conectat) și apoi mergem la ACL. Am acoperit deja acest lucru.
Acum putem include componenta Access din controlerul Posts și verificăm permisiunile cu o singură linie.
dacă (! $ this-> Access-> check ('Post', 'update')) mor ("nu sunteți autorizat");
Același rezultat este obținut cu un cod mai mic, dar mesajul de eroare este urât. Ar trebui să înlocuiți mai bine a muri()
cu agentul de eroare CakePHP:
$ This-> cakeError ( 'error404');
Ar putea fi urât, dar funcționează. Va trebui să creăm un ajutor care să încarce o componentă cu o metodă personalizată pentru a fi utilizată în ajutor.
Adăugați această funcție în componenta Access (controllere / componente / access.php
).
funcția checkHelper ($ aro, $ aco, $ action = "*") App :: import ('Component', 'Acl'); $ acl = nou AclComponent (); întoarcere $ acl-> check ($ aro, $ aco, $ action);
Acum, permiteți rescrierea ajutorului de acces (vizualizari / ajutoare / access.php
).
Acces = nou AccesComponent (); App :: import ('Component', 'Auth'); $ this-> Auth = nou AuthComponent (); $ this-> Auth-> Session = $ this-> Session; $ this-> user = $ this-> Auth-> utilizator (); verifică funcția ($ aco, $ action = '*') if (gol ($ this-> user)) return false; returneaza $ this-> Access-> checkHelper ('Utilizator ::'. $ this-> user ['Utilizator'] ['id'], $ aco, $ action); funcția isLoggedin () return! empty ($ this-> user); ?>
beforeRender ()
metoda este un apel invers, similar cu cel al componentei lansare()
. Încărcăm două componente și din moment ce acestea vor fi folosite în majoritatea funcțiilor, este o idee bună să începeți toate simultan, decât să le porniți manual de fiecare dată când se numește metoda.
Acum pe tine index.ctp
a vedea in vizualizari / posturi
puteți înlocui această linie.
"> edita | echo $html->link ('delete', '/posts/delete/'.$post['Post']['id'], NULL, "Ești sigur?"); ?>
Cu acesta.
if($access->verificați ("Post")):?>"> edita | echo $html->link ('delete', '/posts/delete/'.$post['Post']['id'], NULL, "Ești sigur?"); ?> endif; ?>
Doar nu uitați să verificați permisiunile, atât în vizualizări, cât și în controlere!
Puteți accesa datele utilizatorului pentru un utilizator înregistrat cu utilizator()
în componenta Auth. Apoi puteți accesa matricea și puteți obține informațiile dorite. Dar trebuie să existe o cale mai bună. Să adăugăm următoarea funcție în componenta Access.
funcția getmy ($ ce) return! gol ($ this-> user) && isset ($ this-> user ['Utilizator'] [$ ce]]? $ this-> user ['Utilizator'] [$ what]: false;
Acest lucru este destul de util când trebuie să salvați o postare cu un numele de utilizator
relaţie.
$ this-> data ['Post'] ['user_id'] = $ this-> Acces-> getmy ('id');
Și, în opinia noastră, putem face ceva similar cu ajutorul.
funcția getmy ($ ce) return! gol ($ this-> user) && isset ($ this-> user ['Utilizator'] [$ ce]]? $ this-> user ['Utilizator'] [$ what]: false;
În fișierul dvs. de șablon puteți face ceva ca mai jos pentru a saluta un utilizator prin numele său de utilizator.
Bine ati venit =$access->isLoggedIn ()? $ access-> getmy ('username'): 'Guest'; ?>
Să presupunem că trebuie să schimbăm rolurile pentru utilizatorul # 4: el trebuie să fie un utilizator super. Deci, ID-ul utilizatorului este de 4 și ID-ul lui Aro este 1.
$ user_id = 4; $ user_new_group = 1;
Acum trebuie să găsim Aro-ul utilizatorului pentru a modifica Aro-ul său părinte.
$ aro_user = $ this-> Acl-> Aro-> găsiți ('first', array ('conditions' => array ('Aro.parent_id! =' = NULL, 'Aro.model' => 'Aro.foreign_key' => $ user_id)));
În cele din urmă, dacă $ aro_user
variabila nu este goală, să actualizăm Aro.parent_id
camp.
dacă ! empty ($ aro_user)) $ data ['id'] = $ aro_user ['Aro'] ['id']; $ date ['parent_id'] = $ user_new_group; $ This-> Acl-> Aro-> Salvare (date $);
Rețineți că trebuie să validează atât ID-ul utilizatorului, cât și id-ul lui aro. Dacă una dintre acestea nu există, va crea o mizerie în tabelele dvs. ACL.
Deși componenta pe care tocmai am creat-o sunt simple și utile, baza de date este interogată cu fiecare verificare. Nu este încă pregătită pentru producție. În primul rând, baza de date ar trebui să fie interogată cât mai puțin posibil. Pentru a optimiza acest lucru, ar trebui să profităm de CachePHP's Cache.
Utilizarea Cache-ului va reduce încărcarea destul de puțin, dar dacă avem zece postări afișate în index și pentru fiecare verificăm dacă utilizatorul are permisiuni de actualizare pentru Post Aco, cadrul va fi citirea și parsarea unui fișier pentru a returna același rezultat ... de zece ori pentru fiecare încărcare de pagină.
Acesta este al doilea punct: o variabilă în interiorul clasei și unele condiționări vor face munca mai ușoară, astfel încât o cerere repetată să fie verificată împotriva memoriei cache doar o singură dată.
Ambele schimbări se reflectă în access_cache.php
, în interiorul controlere / componente
director. Deci, asigurați-vă că descărcați sursa!
Lista de control al accesului este o caracteristică de bază pe care majoritatea aplicațiilor o au. CakePHP are o implementare plăcută, dar nu are atât documente bune, cât și exemple. Sper că, cu acest tutorial, aceste două aspecte vor fi acum acoperite. Vă mulțumim pentru lectură!