Linux şi aplicaţii Web în 24 de ore  
introducere în Linux şi iniţiere în dezvoltarea de aplicaţii Web

Linux şi aplicaţii Web în 24 de ore

Aplicaţie Web tipică angajând PHP şi MySQL

«  Aplicaţie Web simplă: situaţia şcolară   ::   Contents   ::   Întrebări conjuncturale  »

Aplicaţie Web tipică angajând PHP şi MySQL

În secţiunea precedentă am realizat aplicaţia “SitSco” folosind numai resurse specifice browserului (HTML, JS, CSS), ceea ce ne-a obligat să implicăm operaţii manuale Copy&Paste (din/în <textarea>) în locul operaţiilor obişnuite cu fişiere de pe disc (sau cu baze de date).

Reconsiderăm acum tema aplicaţie Web pentru situaţia şcolară, implicând de această dată o bază de date. Alegem din start, denumirea sitsco şi prevedem ca această bază de date să conţină două tabele: elev, pentru a păstra numelor şi prenumelor elevilor şi medii pentru mediile elevilor, într-un an şcolar sau în altul.

Scopul nostru este de a releva mecanismele tipice implicate pentru a folosi baze de date (şi scripturi PHP) constituite pe server, în aplicaţii Web. Este suficient atunci, să rezumăm structura sitsco la cele două tabele anunţate; dacă am vrea mai mult decât o simplă exemplificare - ar fi de prevăzut şi alte tabele de date: pentru disciplinele şcolare la care se înregistrează mediile elevilor, poate pentru clasele de care aparţin elevii, etc.

sitsco.elev va înregistra valori de forma (“5”, “Ionescu”, “Petrică”) - unde “5” identifică în mod unic înregistrarea respectivă, în cadrul tabelului (similar cu rolul coloanei uzuale “Nr.Crt”); sitsco.medii va înregistra valori precum (“1234”, “5”, “8.50 9 10 9.50”, “2010”) reprezentând respectiv: identificatorul unic al înregistrării (analog cu “nr-crt”); apoi, o referinţă la un elev (în acest exemplu - la “Ionescu Petrică”, care în tabelul ‘elev’ are ID=5); apoi, mediile acelui elev (şi anume, în aceeaşi formă în care introduceam mediile în <textarea> - dat fiind că n-am mai considerat şi un tabel “discipline” pe care să-l legăm de medii); apoi, anul şcolar la care se referă mediile respective.

Avem astfel câteva avantaje, faţă de tratarea anterioară: odată ce am înregistrat elevul, va fi suficient să introducem doar mediile sale, pentru un an şcolar sau altul; la orice moment vom putea obţine situaţia şcolară înregistrată pentru un an sau altul, pentru un elev sau pentru un grup de elevi.

Fiindcă tabelele respective trebuie să poată fi accesate (pentru înscriere sau extragere de date) de către oricare utilizator al aplicaţiei Web - rezultă în primul rând că baza de date respectivă ar trebui să fie constituită într-un singur loc, anume chiar pe serverul care deserveşte aplicaţia (şi nicidecum, pe Desktop-ul fiecărui utilizator). Mai rezultă un aspect (dar aici, îl vom ignora): n-ar trebui permisă editarea datelor decât unei anumite categorii de utilizatori.

Mecanismul pe care avem să-l evidenţiem seamănă în fond, cu ceea ce ştim din experienţa obişnuită de programare: funcţia main() (din programe C++) prelucrează corespunzător datele şi apoi delegă către “driverul” fstream() operaţiile de scriere în fişier a rezultatelor.

Analog, Apache transferă scripturile către interpretorul PHP (sau după caz, Perl etc.) existent pe server; PHP execută scriptul respectiv şi dacă este cazul, delegă mai departe operaţiile cu baze de date către un anumit “driver” de baze de date; obţinând datele de la acest driver de baze de date, scriptul PHP respectiv le formulează eventual ca HTML şi le returnează către web-server, care apoi le îmbină în pagina Web de răspuns final.

În cazul aplicaţiei Web (cel puţin, cea iniţiată aici): un anumit script PHP va trebui să acceseze (folosind un anumit “driver”) baza de date respectivă şi să extragă datele din tabel, determinând şi media generală pentru fiecare elev; în final, scriptul respectiv va returna rezultatul (formatându-l într-un mod sau altul - ca tabel HTML, ca listă, etc.) spre a fi încorporat în pagina Web finală (trimisă de web-server browserului de la care a primit cererea pentru aplicaţia respectivă). În plus, scriptul PHP respectiv trebuie să şi asigure înscrierea în tabelele MySQL a datelor pentru un nou elev, introduse de utilizator într-un anumit formular existent în pagina Web.

Mediul LAMP

Un Web-server (de exemplu, Apache) este o aplicaţie rezidentă pe un calculator-server, care acceptă cereri de pagini Web din partea utilizatorilor Internetului şi le rezolvă, returnând fiecăruia ce a solicitat (sau un mesaj de eroare, dacă cererea nu poate fi satisfăcută).

De obicei, Apache este combinat pe calculatorul-server cu un “server de baze de date” (de exemplu MySQL) şi cu anumite interpretoare (limbaje de scriptare precum PHP, Perl, sau Python) - combinaţie care constituie platforma obişnuită pentru dezvoltarea aplicaţiilor Web şi care este numită LAMP (pentru Linux, Apache, MySQL şi Perl/PHP/Python).

Ubuntu (ca şi alte distribuţii de Linux) foloseşte APT (Advanced Packaging Tool) pentru a downloada (de la anumite surse prestabilite) şi a instala (pe propriul calculator) pachete software - punând la dispoziţie atât o interfaţă grafică prin meniul Applications->Ubuntu Software Center, cât şi o gamă de programe APT pentru linia de comandă. Putem folosi programul dpkg pentru a verifica dacă pe serverul nostru avem instalate pachetele implicate de LAMP şi apt-get pentru a instala ceea ce ne trebuie.

“Baza de date” cuprinzând informaţii privitoare la pachetele instalate este menţinută în fişierul /var/lib/dpkg/status; dacă vrem să vedem informaţiile referitoare de exemplu la “apache”, putem folosi: cat /var/lib/dpkg/status | grep apache (‘cat’ listează conţinutul fişierului, apoi ‘|’ transmite acest conţinut comenzii ‘grep’ care îl filtrează, producând numai ceea ce conţine termenul precizat ‘apache’).

Dar şi mai bine, folosim dpkg -l care listează pachetele înregistrate în baza de date menţionată, într-un format convenabil (şi în plus, “ştie” şi unde este baza de date - ceea ce este important, fiindcă aceasta poate să fi fost mutată şi în alt loc decât cel implicit precizat mai sus):

      dpkg -l | grep apache
ii  apache2        2.2.16-1ubuntu3.1 Apache HTTP Server metapackage
ii  apache2-utils         utility programs for webservers
ii  apache2.2-bin         Apache HTTP Server common binary files
ii  apache2.2-common      Apache HTTP Server common files
ii  libapache2-mod-perl2  Integration of perl with the Apache2 web server
ii  libapache2-mod-php5   server-side, HTML-embedded scripting language
                              (Apache 2 module)

Marcajul ‘ii’ indică faptul că pachetele respective sunt instalate. Putem relua, pentru “mysql” (| grep mysql) şi respectiv pentru “php5”.

Dacă este cazul, putem instala ceea ce ne-ar trebui folosind sudo apt-get install apache2, etc.; pachetele necesare - pe lângă ‘apache2’ - ar fi acestea: php5, libapache2-mod-php5, php5-cli, php5-mysql şi respectiv mysql-server şi mysql-client. Desigur, se poate folosi şi interfaţa grafică Software Ubuntu Center.

Când instalăm mysql-server, ni se cere să stabilim o parolă pentru userul MySQL-root (care nu este totuna cu parola de ‘root’ de pe sistemul Linux respectiv); această parolă va fi necesară ulterior, pentru “concesionarea” anumitor drepturi (de a crea baze de date, de a insera în tabelele unei anumite baze de date, etc.) către anumiţi useri (numai MySQL-root poate face delegările de astfel de privilegii).

Se întâmplă să uităm însă, “parola de MySQL-root” (şi este recomandabilă, setarea unei astfel de parole). În această situaţie, cea mai simplă soluţie este de a folosi:

sudo dpkg-reconfigure mysql-server-5.1

ceea ce va stopa serverul şi va cere noua parolă (apoi trebuie repornit serverul: sudo /etc/init.d/mysql restart).

“Serverul mysql” este ceea ce se cheamă un daemon, adică o aplicaţie care rulează “în background” şi care aşteaptă (la un anumit port prestabilit) şi rezolvă cereri de date din partea unei alte aplicaţii (program PHP, Perl, etc.).

Un program PHP (sau Perl, etc.) operează cu variabile (care sunt “obiecte de memorie”) şi nu cu date în stare brută, precum cele aflate în bazele de date; de aceea a fost necesară o intermediere: PHP (şi analog, Perl etc.) foloseşte un “driver” PDO (PHP Data Object interface) - de exemplu “PDO_MYSQL”, pentru MySQL - care până la urmă se ocupă de conversiile necesare între obiecte PHP şi datele brute în formatul specific bazei de date (punând la dispoziţie şi funcţii pentru conectare la o bază de date, pentru executarea cererilor SQL, etc.).

Există drivere PDO (extensii PHP) pentru diverse tipuri de baze de date (MySQL, SQLite, Oracle, PostgreSQL, etc.) şi pentru a putea opera cu alt tip de bază de date nu este necesară modificarea codului scriptului PHP (exceptând faptul că trebuie indicat noul tip de bază de date cu care să lucreze scriptul PHP respectiv).

Scripturi PHP

PHP poate fi utilizat şi de pe linia de comandă, dacă a fost instalat pachetul php-cli (CLI: Command Line Interface). Un script PHP destinat lansării de pe linia de comandă - şi la fel pentru scripturi Perl, Python, etc. - se constituie după regulile prezentate anterior (vezi Exemple de scripturi Bash).

Pentru un mic exemplu, să creem un fişier “cli-info.php”; presupunem că directorul de lucru este ~/Proiecte:

touch doc/cli-info.php

Deschidem acest fişier în gEdit şi înscriem:

#! /usr/bin/php
<?php
phpinfo();

Spre deosebire de alte limbaje de nivel înalt, PHP a fost creat cu scopul de a putea mixa HTML-ul obişnuit cu instrucţiuni executabile; aceasta a necesitat considerarea unui tag similar elementului <script> din HTML şi s-a ales drept “tag PHP”, combinaţia de caractere <?php. Orice secvenţă de instrucţiuni PHP trebuie să înceapă cu acest tag, pentru a fi recunoscută ca atare de PHP (este prevăzut şi “tagul de încheiere” ?> dar cel puţin în scripturi care folosesc numai PHP - nu şi HTML - nu este necesară folosirea acestuia).

Putem constata că în loc de tagul <?php, se poate folosi totdeauna construcţia (mai “lungă”):

<script language="php">
//... secvenţă de instrucţiuni PHP
<script>

Salvăm “cli-info.php” şi îl marcăm ca fişier executabil: sudo chmod +x doc/cli-info.php; apoi îl putem lansa prin ./doc/cli-info.php. Ca urmare a executării funcţiei phpinfo(), obţinem pe ecran o listă lungă cu informaţii privitoare la configurările şi setările existente pentru PHP-CLI.

Să ne amintim acum că ne-am creat un “virtual host” (vezi Găzduirea aplicaţiei (virtual host)), având ca DocumentRoot ~/Proiecte/web-public; să creem aici fişierul info.php cu următorul conţinut:

<html>
  <head>
        <title>HTML and PHP Test</title>
  </head>
  <body>
     <script language="php">
        echo '<h1>PHP - variabile predefinite</h1>';
        phpinfo(INFO_VARIABLES);
     </script>
  </body>
</html>

Accesând din browser: http://proiecte.home/info.php ar trebui să obţinem o pagină Web pe care apare conţinutul elementului <h1> înscris de instrucţiunea PHP echo, urmat de un tabel care prezintă variabilele indicate prin phpinfo(INFO_VARIABLES).

Dacă nu obţinem aceasta, înseamnă că modulul mod_php5 nu este instalat (sau, nu este activat) - ceea ce se rezolvă (cel mai simplu) prin Ubuntu Software Center (trebuie căutat şi instalat pachetul libapache2-mod-php5).

Dacă vizualizăm sursa paginii redate (folosind meniul browserului View/Page Source, sau combinaţia de două taste CTRL + u) - constatăm că phpinfo() a completat fişierul redat mai sus, adăugând în <head> o secţiune <style> (definind stiluri de prezentare pentru tabelul de informaţii cerut) şi introducând în <body> un element <table> cu două coloane (numele predefinit şi respectiv, valoarea curentă a variabilei). Un rând din acest tabel arată astfel:

_SERVER["DOCUMENT\_ROOT"] /home/vb/Proiecte/web-public

_SERVER este un tablou asociativ predefinit global în PHP, menit să păstreze asocieri de forma cheie => valoare (de exemplu, “DocumentRoot” => “/home/vb/Proiecte/web-public”).

PHP împrumută diverse elemente de sintaxă (inclusiv “cuvinte-cheie”), în special din C şi din Perl; astfel - ca şi în Perl - variabilele folosite în textul-sursă de program PHP trebuie “sigilate” folosind prefixul $. De exemplu, $file = $_SERVER['PHP_SELF'] va obţine în variabila $file valoarea corespunzătoare cheii “PHP_SELF” din tabloul global _SERVER.

Să revenim la fişierul nostru info.php; eliminăm (sau comentăm) linia “phpinfo()” şi scriem în loc, următoarea secvenţă tipică pentru listarea conţinutului unui tablou asociativ:

<?php foreach ($_SERVER as $nume => $cheie): ?>
    <?php echo "<p>$nume: $cheie </p>"; ?>
<?php endforeach; ?>

Reîncărcând în browser, regăsim ceea ce obţinusem anterior (când apelasem phpinfo()), dar acum ca o secvenţă de paragrafe (fiindcă n-am folosit în echo decât un simplu <p>).

O înregistrare (un rând) dintr-un tabel de date va putea fi reprezentată PHP printr-un tablou de asocieri nume_coloană => valoare, încât vom putea folosi instrucţiunea “foreach” după modelul de mai sus, pentru a prelucra sau a afişa elementele tabloului respectiv.

Găzduirea bazelor de date pe server

În unele privinţe activitatea de pe Internet corespunde unor tipare ale realităţii economice obişnuite. Un proprietar de terenuri a construit undeva în Bucureşti, o clădire cu toate utilităţile necesare funcţionării unor sedii de firme comerciale şi închiriază spaţiile respective. Analog, un proprietar de “calculator-server” puternic (localizat în Canada, de exemplu) a instalat LAMP pe calculatorul respectiv şi închiriază spaţiu pe hard-disk - împreună cu anumite utilităţi de exploatare şi anumite drepturi - către terţi.

Să presupunem pentru moment, că suntem un astfel de “chiriaş” - avem alocat pe serverul respectiv (din Canada) /home/vladbazon şi avem dreptul de a uploada în acest spaţiu fişiere proprii; uploadarea de fişiere se face de obicei folosind programe utilitare precum scp (pentru a copia fişiere “la distanţă”) şi ssh (pentru logare de la distanţă într-un Terminal care rulează pe server) - configurate pe calculatorul propriu conform indicaţiilor administratorului serverului.

Să presupunem şi că avem deja înregistrat un domeniu (vezi de exemplu, ROTLD), fie de exemplu ccd-vs.ro.

Să presupunem ca vrem să instalăm pe serverul respectiv, aplicaţia sitsco; aceasta va trebui să fie accesată prin http://www.ccd-vs.ro/sitsco/ (analog, pentru alte aplicaţii). Pentru asigurarea funcţionalităţii dorite, trebuie creat un “virtual host” (cu ServerName www.ccd-vs.ro) şi trebuie creată baza de date “sitsco”; aceste operaţii de creare necesită privilegii de “root” şi de obicei sunt rezervate administratorului serverului respectiv - caz în care trebuie să-i trimitem acestuia definiţiile necesare şi să-i cerem să ne creeze “virtual hostul” şi baza de date necesară.

Am văzut anterior, cum se creează un “virtual host”; să vedem şi cum ne va crea administratorul serverului baza de date solicitată, încât să putem apoi, să creem tabelele necesare şi să putem face sau implica în program operaţiile obişnuite de interogare a bazei de date constituite.

Este de subliniat că dreptul de a crea o bază de date MySQL ţine exclusiv de userul ‘root’ creat la instalare, iar pe de alta parte ‘root’ nu poate fi utilizat decât de pe calculatorul pe care s-a instalat MySQL - ca urmare, numai administratorul serverului poate crea baza de date (este totodată adevărat că se pot forţa lucrurile, dar aceasta ar însemna ca mai multe persoane să controleze complet toate bazele de date existente - inclusiv cele aparţinând altora - încât ar putea uşor să se producă anumite conflicte de nedorit).

Administratorul va deschide un Terminal pe serverul său şi va tasta pe linia de comandă mysql - u root -p (se poate adăuga imediat după ‘-p’ şi parola, dar totdeauna este mai bine ca aceasta să fie tastată după ce apare mesajul “Enter password:” şi nu direct pe linia de comandă: comenzile introduse pe linia de comandă sunt memorate în diverse fişiere “history”, sau de monitorizare a sistemului - încât adăugând imediat şi parola s-ar crea inutil pericolul deconspirării); după ce va înscrie parola cerută (cunoscută administratorului, el fiind cel care a instalat pachetul mysql-server), va putea folosi shell-ul mysql ca user MySQL root.

La promptul afişat de shell-ul mysql tocmai lansat, administratorul (‘root’-ul MySQL) are de tastat vreo două-trei comenzi tipice, pentru a înregistra un nou “user” MySQL şi pentru a crea fişierele necesare pentru noua bază de date (încât “userul” creat să poată opera pe baza de date constituită). Mai întâi:

mysql> GRANT usage ON *.* TO ccdvs@localhost
    -> IDENTIFIED BY 'ilovevs';

MySQL îşi constituie de la instalare o bază de date cu numele “mysql”, în care între altele păstrează informaţii despre “userii” şi bazele de date create. Să inspectăm (ca user MySQL ‘root’, cu care am deschis shell-ul mysql) tabelul “user” (în urma executării comenzii de mai sus):

mysql> USE mysql;   # deschide baza de date `mysql`
mysql> SHOW TABLES; # listează numele tabelelor din `mysql`
mysql> SELECT * FROM `user`; # listează înregistrările din tabelul `user`

Putem astfel constata că a fost înregistrat şi userul ‘ccdvs’ (alături de ‘root’ şi eventual de alţi “useri” creaţi anterior fie de către MySQL în momentul instalării, fie de către administratorul sistemului). Să observăm mai bine (comparativ) datele înregistrate pentru ‘root’ şi ‘ccdvs’:

mysql> SELECT * FROM `user` WHERE user='ccdvs'
                            OR (user='root' AND host='localhost');

| Host    | User  | Password    | Select_priv | Insert_priv | ...
localhost | root  | *BDBB285... | Y           | Y           | ...
localhost | ccdvs | *670A6A8... | N           | N           | ...

Vedem că în timp ce ‘root’ are înscrisă valoarea “Y”, noul user ‘ccdvs’ are “N” - în toate câmpurile corespunzătoare “privilegiilor”; aceasta vrea să însemne că în acest moment, ‘ccdvs’ nu poate face nici o operaţie cu tabele ale vreunei baze de date - el doar se poate conecta la mysql printr-o procedură ca următoarea: “intră pe server” (folosind ssh pentru autentificare şi acces la un shell de pe server) şi tastează mysql - u ccdvs -p (indicând parola “ilovevs”).

Acum, că s-a creat posibilitatea ca ‘ccdvs’ să poată folosi shell-ul mysql, rămâne ca administratorul (ca MySQL ‘root’) să creeze bazele de date solicitate de clientul său şi să seteze privilegiile corespunzătoare:

mysql> CREATE DATABASE sitsco;

mysql> GRANT ALL PRIVILEGES ON sitsco.*
       TO ccdvs@"localhost" IDENTIFIED BY 'ilovevs';

mysql> FLUSH PRIVILEGES;

S-a indicat drept host @"localhost", pentru că baza de date şi aplicaţia care o foloseşte sunt ambele pe serverul respectiv (altfel, trebuia indicată adresa serverului care găzduieşte acea aplicaţie). Putem repeta comanda de mai sus “SELECT * FROM `user`”, pentru a constata ce permisiuni are acum ‘ccdvs’ - dar… vom găsi tot ‘N’, ceea ce este normal dacă avem în vedere că s-a precizat “privileges ON `sitsco`” (nu pentru toate bazele de date); cu “SELECT * FROM `db`” vom putea constata privilegiile asupra unei anumite baze de date.

Din acest moment, cel căruia administratorul i-a furnizat informaţiile de acces cuvenite va putea să-şi creeze tabelele necesare (în baza de date ‘sitsco’) şi să opereze cu ele sau asupra lor (dintr-un shell mysql deschis prin “ssh” pe server), iar pe de altă parte - aplicaţiile uploadate pe server vor putea şi acestea să exploateze datele din baza de date ‘sitsco’.

Desigur, în contextul particular de lucru pe care ni l-am constituit aici - “calculatorul-server” este propriul calculator, pe care noi înşine am instalat LAMP; dar în principiu, aceasta nu schimbă cu nimic lucrurile evidenţiate mai sus (dar evident, le simplifică: nu mai este necesar să folosim ssh şi nici scp).

Crearea tabelelor

Am convenit anterior să creem două tabele: `elev` (nume şi prenume) şi `medii` (referinţă la elev, an-şcolar, medii). Este adevărat că am putem intra în shell-ul mysql şi să tastăm pe rând comenzile “create table” necesare - dar tot maniera exemplificată deja anterior (pentru scripturi Bash, PHP şi programe C) este cea mai bună cale: edităm un script SQL conţinând comenzile necesare şi îl pasăm interpretorului mysql.

De regulă, aplicaţia nu se dezvoltă direct pe server, ci pe calculatorul propriu - tranferând apoi versiunea finală a fişierele respective pe server.

Este mai uşor să transferăm pe server un script de creare a tabelelor (urmând să-l pasăm şi acolo, shell-ului mysql de pe serverul gazdă), decât să intrăm pe server şi să tastăm iarăşi în mysql, comenzile respective, una câte una. Deasemenea, dacă ulterior vom avea de făcut modificări de structură a tabelelor, va fi mai uşor să edităm scriptul, decât să tastăm pe linia de comandă comenzile necesare.

Să creem deci fişierul Proiecte/doc/sitsco.sql, cu următorul conţinut:

SET FOREIGN_KEY_CHECKS = 0;
drop table if exists elev;
drop table if exists medii_anual;
SET FOREIGN_KEY_CHECKS = 1;

CREATE TABLE elev (
    id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
    nume VARCHAR(32),
    pren VARCHAR(32)
) ENGINE = INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_romanian_ci;

CREATE TABLE medii_anual (
    id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
    elev_id int,
    medii VARCHAR(255),
    an YEAR(4),
    FOREIGN KEY (elev_id) REFERENCES elev(id) ON DELETE CASCADE
) ENGINE = INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_romanian_ci;

După ce vom transfera acest fişier pe server, vom putea crea tabelele respective (amintindu-ne că pentru conectare la shell-ul mysql de pe server administratorul ne-a indicat numele `ccdvs` şi parola `ilovevs`) tastând pe linia de comandă:

mysql -u ccdvs -pilovevs sitsco  <  Proiecte/doc/sitsco.sql

mysql va interpreta şi va executa asupra bazei de date indicate “sitsco”, comenzile întâlnite în scriptul “sitsco.sql”.

MySQL modelează mai multe modalităţi (storage Engines) pentru memorarea “fizică” a datelor; în scriptul de mai sus am ales InnoDB fiindcă acest mod suportă şi lucrul cu “chei străine” (FK, sau Foreign Keys), spre deosebire de modul implicit “MyISAM”.

Câmpul de date numit `id` este PK (“cheie primară”) şi va fi gestionat în mod automat (datorită declaraţiei “auto_increment”): la fiecare nouă adăugare a unei înregistrări, `id` va fi completat cu o valoare diferită faţă de valorile existente deja; ca urmare, `id` joacă rolul de selector de înregistrare (unică) din tabel.

`elev_id` este o “cheie străină”, FK ale cărei valori trebuie să fie printre valorile `id` din tabelul `elev`; astfel, o înregistrare oarecare din tabelul `medii_anual` pointează prin valoarea existentă în coloana sa `elev_id` către una dintre înregistrările existente în tabelul `elev`. Specificaţia “on delete cascade” asigură ca la ştergerea unei înregistrări din tabelul `elev` să fie şterse automat şi toate înregistrările din `medii_anual` care vizează elevul respectiv.

Specificaţia “DEFAULT CHARSET=utf8 COLLATE=utf8_romanian_ci” vrea să indice MySQL că datele care se vor transmite pentru înregistrare în tabele sunt codificate Unicode, “utf-8” (dat fiind că avem de folosit şi caractere româneşti) şi că eventualele ordonări solicitate prin clauze ca “order by” trebuie să ţină cont de ordinea corectă a literelor româneşti (_ci pentru “case-insensitive”).

Primele linii din scriptul de mai sus reprezintă o măsură de precauţie: dacă după ce am instalat tabelele respective în baza de date, suntem în situaţia de a modifica structura (adăugând tabele sau noi câmpuri, ori modificând definiţii de câmpuri, sau chiar schimbând “engine”) - atunci de regulă va trebui să ştergem vechile tabele şi să operăm cu noul script pentru a institui tabelele noi; de regulă, o înregistrare nu va putea fi ştearsă decât după ştergerea prealabilă a acelor înregistrări din alte tabele care o referă prin FK. Pentru a nu mai depinde de ordinea tabelelor în cadrul scriptului, am forţat variabila FOREIGN_KEY_CHECKS (întâi pe 0, pentru a ignora FK; în final am restaurat-o pe valoarea 1).

sitsco.php

Construim treptat un script PHP nepretenţios, sitsco.php în care prevedem (şi gestionăm în corelaţie cu baza de date ‘sitsco’ de pe server) un formular pentru înregistrarea unui elev şi a mediilor sale şi un tabel care să redea elevii înscrişi împreună cu mediile generale.

<?php
$self = 'sitsco.php';   // $_SERVER['PHP_SELF'] = '/sitsco.php';
mysql_connect('localhost', 'ccdvs', 'ilovevs');
mysql_select_db('sitsco');

Se conectează serverul MySQL care rulează pe acelaşi calculator ca şi acest script (valoarea primului parametru fiind ‘localhost’) - indicând numele de user ‘ccdvs’ şi parola, conform indicaţiilor primite de la administrator, discutate anterior - şi se deschide baza de date ‘sitsco‘.

Variabila $self păstrează numele scriptului; n-ar fi necesar, dacă am separa lucrurile (un script pentru inserarea datelor introduse în formular, un alt script pentru redarea datelor) - însă aici, vom prevedea toate operaţiile într-un singur script (astfel că, după o inserare de date scriptul va trebui reapelat; este preferabil să definim într-un singur loc, la început, numele scriptului care va trebui apelat din diverse locuri, în loc să-l folosim direct în fiecare loc).

Instituim o variabilă care să păstreze (ca şir de caractere) definiţia formularului; folosim modalitatea de specificare here-document, întâlnită şi în Bash, în Perl, etc. Definind formularul prin “here-doc” şi la începutul scriptului - va fi uşor să modificăm eventual, definiţia respectivă.

$formular = <<< FORMULAR
    <form method="post" action="$self">
        <p>Nume: <input name="nume" /></p>
        <p>Prenume: <input name="pren" /></p>
        <p>An şcolar: <input name="an" /></p>
        <p>Medii: <input name="medii" /></p>
        <p><input type="submit" name="sendelev" value="Înscrie"/></p>
    </form>
FORMULAR;

Formularul va putea fi instanţiat (şi creat în pagina Web) prin echo $formular;. Elementul <form> prevăzut prin definiţia de mai sus va prezenta utilizatorului elemente <input> în care să introducă datele şi un “buton” (un <input> cu type="submit") a cărui “apăsare” va trimite datele înscrise scriptului definit în atributul action.

Browserul va folosi ca metodă de trimitere a datelor metoda HTTP POST. Anume, datele vor fi transmise ca perechi “nume_parametru: valoare”, unde “nume_parametru” este valoarea atributului name din <input>-ul respectiv.

Putem face următorul experiment de testare: adăugăm scriptului constituit până în acest moment o linie de instanţiere a formularului, “echo $formular;” şi încărcăm în browser prin http://proiecte.home/sitsco.php; lansăm şi extensia Firefox Firebug (presupunând că am instalat-o în prealabil) şi reîncărcăm pagina (CTRL+F5). Deschidem tab-ul marcat cu Net din fereastra Firebug şi activăm (click) opţiunea Persist; apoi completăm formularul afişat şi acţionăm (click) butonul “Înscrie”. Obţinem astfel informaţii despre parametrii transmişi:

_images/Screenshot-632.png

În loc de metoda “post”, puteam folosi method="GET" - caz în care browserul ar fi transmis parametrii adăugându-i efectiv în bara de adresă (după http://proiecte.home/sitsco.php) sub forma:

?nume=Barbu&pren=Ion&an=2011&medii=6.50+7.67+8+10&sendelev=%C3%8Enscrie

constituind ceea ce se numeşte un “QUERY-STRING”.

PHP foloseşte tabloul global _POST pentru a înregistra parametrii transmişi prin metoda “post” (şi analog, tabloul _GET în cazul “query-string”), astfel că în cadrul scriptului aceştia vor putea fi accesaţi prin $_POST['nume_parametru'].

Să considerăm întâi cazul când utilizatorul a completat formularul şi a acţionat butonul “Înscrie”; aceasta înseamnă că în tabloul _POST s-a înregistrat parametrul sendelev=Înscrie (“sendelev” fiind valoarea atributului “name” din input-ul “submit” care trimite datele); următoarea secvenţă asigură înserarea în cele două tabele a datelor respective:

<?php // ... continuare

 if( isset($_POST['sendelev']) ) {
     $nume = trim($_POST['nume']);
     $pren = trim($_POST['pren']);
     $an = trim($_POST['an']);
     $medii = trim($_POST['medii']);

     $sql = "INSERT INTO elev (nume, pren) VALUES('$nume', '$pren')";
     mysql_query($sql);

     $lid = mysql_insert_id(); // PK al ultimei înregistrări inserate
     $sql = "INSERT INTO medii_anual (elev_id, an, medii)" .
                                    "VALUES('$lid', '$an', '$medii')";
     mysql_query($sql);

     echo "<script type='text/javascript'>" .
              "window.location='$self';" . "</script>";
 }

Secvenţa redată este nepretenţioasă (ca şi întregul script), vrând să reliefăm logica de bază a lucrurilor. Am folosit trim() pentru a şterge eventualele spaţii iniţiale şi finale din secvenţele introduse în formular; dar nu am verificat altfel, ceea ce a introdus utilizatorul (într-o aplicaţie reală, o secvenţă ca “<script>alert(‘ha-ha!’);</script>” nu se poate accepta ca “input”, fără o “curăţare” prealabilă - scop în care am dispune măcar de funcţia htmlspecialchars()).

Pe de altă parte, “logica” învederată pentru aplicaţie este cam defectuoasă, dat fiind că s-a prevăzut înregistrarea mediilor odată cu înregistrarea elevului. Pentru a permite ca unui elev înregistrat deja să i se asocieze alte medii (pentru un alt an şcolar), ar fi trebuit ca înainte de “INSERT INTO `elev`” să se testeze dacă elevul respectiv este deja înregistrat (caz în care trebuie omis “INSERT”-ul).

Nu ne ocupăm aici să “corectăm” acest aspect, dar menţionăm că soluţia cea mai bună constă în separarea în două formulare, unul pentru adăugarea unui nou elev şi altul care să permită selectarea unui elev dintre cei înscrişi deja şi înregistrarea mediilor pe anul respectiv pentru elevul selectat.

După înscrierea datelor în tabele, secvenţa de mai sus cere browserului să execute instrucţiunea javaScript window.location = '$self', ceea ce asigură reîncărcarea paginii (“reapelarea” scriptului ‘sitsco.php’).

Secvenţa finală a scriptului nostru redă datele într-un element <table> şi instanţiază formularul; ea va fi executată la orice reîncărcare a paginii (sau “reapelare” a scriptului).

<?php // ... continuare

 else {
     $sql = "SELECT e.nume, e.pren, m.medii, m.an " .
                "FROM elev as e, medii_anual as m " .
                "WHERE e.id = m.elev_id " .
                "ORDER BY CONCAT(e.nume, e.pren)";
     $result = mysql_query($sql);

     $tabel = array();
     $tabel[] = "<table border='1'><tr><th>Nume şi Prenume</th>" .
                                       "<th>An</th><th>Media</th></tr>";
     while($elev = mysql_fetch_assoc($result)) {
         // media generală
         $arr_med = explode(' ', $elev['medii']);
         $mg = array_sum($arr_med) / count($arr_med);

         $tabel[] = "<tr><td>" . $elev['nume'] . " " . $elev['pren'] . "</td><td>" .
                                 $elev['an']. "</td<td>" . $mg. "</td></tr>";
     }
     $tabel[] = "</table>";

     echo implode('', $tabel);
     echo $formular;
 }

Variabila $result păstrează referinţa returnată de mysql_query() după executarea interogării SQL indicate în variabila $sql. Zona referită prin $result este parcursă “înregistrare după înregistrare”, obţinând prin mysql_fetch_assoc() datele elevului curent - apoi acestea sunt ambalate în taguri <td> şi adăugate astfel în tabloul $tabel.

Pentru calculul mediei generale a elevului, am transformat şirul de caractere “medii” într-un tablou PHP, folosind funcţia explode(); dar maniera în care am folosit aici explode() şi apoi array_sum() este cam defectuoasă, fiindcă necesită ca în şirul iniţial “medii” mediile să fie separate în mod obligatoriu prin câte un singur spaţiu.

În final, tabloul PHP $tabel (în care s-au înregistrat pe rând elementele structurii de element HTML <table>) este transformat în şir obişnuit folosind implode() şi este înscris ca atare (element <table>) în pagină (adăugând apoi şi formularul).

Acum se poate proba desigur funcţionarea finală, cu http://proiecte.home/sitsco.php.

«  Aplicaţie Web simplă: situaţia şcolară   ::   Contents   ::   Întrebări conjuncturale  »