3. díl - Tipy pro začátečníky v PHP

PHP Tipy pro začátečníky Tipy pro začátečníky v PHP

Vítejte ve 3. díle série Tipy pro začátečníky v PHP. Tento díl bude zaměřen hlavně na práci s daty z databáze. Především na to, co se dělá zbytečně a co jde udělat lépe s menší zátěží serveru. Příklady vyžadují alespoň základní znalost objektového ovladače PDO a využívají databázi MySQL.

Vyhledání jen potřebných dat

V SQL lze data vyhledat dvěma způsoby - vyjmenovat jednotlivé sloupce nebo napsat hvězdičku (ta vyhledá všechny sloupce z tabulky). Použití hvězdičky je sice velmi jednoduché a není potřeba moc psaní, avšak ve většině případů vyhledá i sloupce, které nakonec vůbec nepotřebujete a nevyužijete je.

Jednoduchý příklad

Máme tabulku s uživateli, která obsahuje sloupce ID, přihlašovací jméno, jméno, příjmení, datum narození a datum registrace. Chceme vyhledat všechny uživatele a zobrazit jejich jméno, příjmení a datum registrace.

Při použití hvězdičky bychom napsali

$pdo->query("
  SELECT * FROM `user`
");

Nicméně tento dotaz nám vyhledá všech 6 sloupců, namísto 3, které chceme. Znamená to, že se bude přenášet zbytečně více dat. U velmi malých databází by to prakticky nemělo na výkon vliv, ale představte si databázi, kde je registrovaných 10 tisíc lidí a stránku s tímto dotazem by si otevřely třeba 2 tisíce uživatelů v jeden okamžik. Znamenalo by to, že by se zbytečně přenášelo o 3 * 10k * 2k = 60M více bloků dat (3 sloupce navíc, 10.000 záznamů a 2000 uživatelů na stránce).

Prakticky bychom nevyhledávali všech 10 tisíc záznamů, ale různě je omezovali klauzulí LIMIT (např. u stránkování). Nicméně pro malý příklad toho, kolik zbytečných dat by se přeneslo, to stačí.

Řešením je vyjmenovat v dotazu jen ty sloupce, které opravdu potřebujeme.

$pdo->query("
  SELECT `nick`, `first_name`, `last_name`
  FROM `user`
");

Zpětné apostrofy

Někdy se nám může stát, že jméno tabulky nebo sloupce bude kolidovat s klíčovým slovem databáze. Například kdybychom měli sloupec pojmenovaný text nebo from, takový dotaz by byl špatně:

$pdo->query("
  SELECT text, from
  FROM user
");

Musíme tedy říci databázi, aby názvy našich sloupců nebrala jako klíčová slova. K tomu slouží zpětné apostrofy.

$pdo->query("
  SELECT `text`, `from`
  FROM `user`
");

Osobně doporučuji psát zpětné apostrofy vždy, i když názvy nekolidují. Minimálně se tím vyhnete risku, že v nové verzi databáze přibude nové klíčové slovo, které by kolidovat mohlo.

Manipulace se záznamy

Databáze obsahují hromadu funkcí, které můžeme využít pro získání přesného formátu záznamů. Například pokud bychom chtěli sečíst hodnotu nějakého sloupce všech záznamů, můžeme to udělat na úrovni PHP.

$sum = 0;
$query = $pdo->query("
  SELECT `amount`
  FROM `table`
");
$rows = $query->fetchAll(PDO::FETCH_OBJ);

foreach ($rows as $row) {
  $sum += $row->amount;
}

echo $sum;  // zobrazení celkového součtu

V tomto případě ale píšeme zbytečně více kódu a zároveň zbytečně z databáze taháme hodnotu sloupce a teprve potom přičítáme. Databáze obvykle obsahuje funkci, která to rovnou spočítá za nás a vrátí nám již výsledek. V MySQL je pro to funkce SUM().

$query = $pdo->query("
  SELECT SUM(`amount`)
  FROM `table`
");
$sum = $query->fetchColumn();

echo $sum;  // zobrazení celkového součtu

Velmi podobný případ nastává i u samotného získávání počtu záznamů. Můžeme to řešit na úrovni PHP, kde jednotlivě budeme v cyklu přičítat. Pokud ještě používáte starý ovladač mysql_*, mohli byste napsat něco takového:

$query = mysql_query("
  SELECT `amount`
  FROM `table`
");
$rowsCount = mysql_num_rows($query);

echo $rowsCount;  // zobrazení počtu záznamů

Opět zbytečně taháme data a řádky počítáme až potom. Databáze ovšem obsahuje funkci COUNT(), která řádky rovnou spočítá.

$query = $pdo->query("
  SELECT COUNT(*)
  FROM `table`
");
$rowsCount = $query->fetchColumn();

echo $rowsCount;  // zobrazení počtu záznamů

Funkci COUNT() můžeme předat také název určitého sloupce. V takovém případě nám to spočítá záznamy, kde hodnota daného sloupce není NULL.

Databáze takových funkcí obsahuje mnoho. Je výhodnější tyto funkce použít již na úrovni databázového dotazu a ne až na úrovni PHP.

Získání ID posledně vloženého záznamu

V určitých případech chceme, abychom po vložení nového řádku do tabulky získali jeho ID. Budeme mít tabulku se dvěma sloupci - ID a jméno uživatele. Lze to udělat zbytečně složitým způsobem:

// ze seznamu sloupců vynecháme ID, protože je nastaven jako AUTO_INCREMENT
// a databáze do něj hodnotu vloží automaticky
$pdo->query("
  INSERT INTO `user` (`nick`)
  VALUES ('jméno uživatele')
");

$query = $pdo->query("
  SELECT MAX(`id`)
  FROM `user`
");
$lastId = $query->fetchColumn();

Tento kód nám prvně vloží nový záznam do tabulky a následně pomocí funkce MAX() vybere to nejvyšší číslo.

$query = $pdo->query("
  SELECT `id`
  FROM `user`
  ORDER BY `id` DESC
  LIMIT 1
");

$lastId = $query->fetchColumn();

Jinak zapsané, ale udělá to to samé. Jednoduše vybere hodnotu sloupce ID, seřadí od největšího a vybere jen to první (pomocí LIMIT).

Tyto dotazy jsou ale úplně zbytečné. Pro získání posledně vloženého ID má PDO metodu lastInsertId().

$pdo->query("
  INSERT INTO `user` (`nick`)
  VALUES ('jméno uživatele')
");
$lastId = $pdo->lastInsertId();

Procházení záznamů

Na rozdíl například od starého ovladače mysql_*, umí PDO vracet záznamy tak, jak požadujeme. Pokud chceme jen jednu hodnotu, nemusíme se k ní dostávat přes nějaké pole nebo naopak pro získání všech záznamů nemusíme postupně procházet řádek po řádku, ale PDO nám vrátí rovnou celý seznam.

Metoda fetchAll()

Jak z názvu vyplývá, metoda nám vrátí všechny nalezené záznamy. Uloží je do dvourozměrného pole ve tvaru:

array (
  0 => array(
     // první řádek
  ),

  1 => array(
     // druhý řádek atp.
  )
)

Díky tomu lze pole projít cyklem (obvykle se používá foreach) a pracovat s jednotlivými záznamy.

$query = $pdo->query("
  SELECT `data`
  FROM `table`
");
$data = $query->fetchAll();

foreach ($data as $row) {
  // $row obsahuje sloupce z daného řádku
}

Pokud se nenašla žádná data, výsledkem je prázdné pole.

Metoda fetch()

Metoda fetch() vrací jednorozměrné pole dle daného záznamu. Využije se v případě, že hledáme jeden konkrétní záznam, například informace o uživateli.

$id = (int) $_GET["id"];

$query = $pdo->query("
  SELECT `first_name`, `last_name`
  FROM `user`
  WHERE `id` = {$id}
  LIMIT 1
");
$data = $query->fetch(PDO::FETCH_OBJ);

echo $data->first_name;  // vypsání jména daného uživatele

Pokud se nenajde žádný vyhovující řádek, metoda vrátí FALSE. Lze to velmi snadno použít pro zjištění, zda se řádek našel nebo ne. Využít se to dá například u přihlašování uživatelů.

$query = $pdo->query("
  SELECT `id`
  FROM `user`
  WHERE `nick` = 'nějaké jméno' AND `password` = 'nějaké heslo'
  LIMIT 1
");
$data = $query->fetch(PDO::FETCH_OBJ);
if ($data !== FALSE) {
  $_SESSION["userId"] = $data->id;
  // atd.

} else {
  echo "Je nám líto, uživatel s takovým jménem nebo heslem neexistuje.";
}

Metoda fetchColumn()

Tato metoda nám vrací hodnotu jednoho určitého sloupce v jednom záznamu. Často se používá například při počítání záznamů.

$query = $pdo->query("
  SELECT COUNT(*)
  FROM `user`
");
$usersCount = $query->fetchColumn();  // proměnná obsahuje počet všech uživatelů

Pokud se nenajde žádná hodnota, metoda vrátí FALSE.

Formát výstupních dat

Tato část úzce souvisí s tou předchozí. Kromě jednotlivého uskupení dat můžeme určit v jakém formátu se nám vrátí. Můžeme data například uložit pouze do asociativního pole nebo třeba do objektu.

$id = (int) $_GET["id"];

$query = $pdo->query("
  SELECT `first_name`, `last_name`, `email`
  FROM `user`
  WHERE `id` = {$id}
  LIMIT 1
");

// nejčastěji používané
$data = $query->fetch(PDO::FETCH_BOTH);   // vrátí jak indexované, tak asociativní pole (defaultně)
$data = $query->fetch(PDO::FETCH_NUM);    // vrátí pouze indexované pole
$data = $query->fetch(PDO::FETCH_ASSOC);  // vrátí pouze asociativní pole
$data = $query->fetch(PDO::FETCH_OBJ);    // vrátí objekt třídy stdClass

Uvedl jsem pouze nejpoužívanější styly, existuje jich více (viz php.net).

Unikátní hodnoty

Pokud chceme mít v tabulce pouze unikátní hodnoty, není potřeba se předem dotazovat, zda tam již daná hodnota je. Často je k vidění takový kód:

// budeme předpokládat, že proměnná $name neobsahuje nebezpečné znaky

$query = $pdo->query("
  SELECT 1 FROM `user`
  WHERE `name` = '{$name}'
  LIMIT 1
");
$exists = $query->fetchColumn();

if (!$exists) {
  // zaregistrování nového uživatele
}

Pokud nějaký záznam existuje, vrátí se 1, jinak nám PDO vrátí FALSE.

Můžeme ale využít unikátního klíče přímo v tabulce. Stačí danému sloupci přidat "unikátní klíč (unique key)" a nemůže se nám stát, že bychom vložili stejný záznam. Pokud se o to pokusíme, vrátí databáze chybu. Tu PDO reprezentuje dle nastavení. Nejčastějším nastavením je, že se při chybě vyhodí výjimka.

try {
  $pdo->query("
     INSERT INTO `user` (`nick`)
     VALUES ('nějaké jméno')
  ");
} catch (PDOException $e) {
  echo "Uživatel s tímto jménem již existuje.";
}

Tak a třetí díl je za námi. Děkuji za přečtení a pokud bude zájem, rád se pokusím sepsat další tipy, které by mohly pomoct jak začínajícím, tak určitě i mírně pokročilým vývojářům.


 

  Aktivity (1)

Článek pro vás napsal Martin Konečný (pavelco1998)
Avatar
Autor se o IT moc nezajímá, raději by se věnoval speciálním jednotkám jako jsou SEALs nebo SAS. Když už to ale musí být něco z IT, tak tvorba web. aplikací v PHP.

Jak se ti líbí článek?
Celkem (8 hlasů) :
55555


 


Miniatura
Předchozí článek
Tipy pro začátečníky v PHP
Miniatura
Všechny články v sekci
Tipy pro začátečníky v PHP

 

 

Komentáře

Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Matúš Petrofčík:

Trochu mi nejde do hlavy jedna vec. Píšeš že metoda fetch() vracia jednorozmerne pole, ale pristupuješ k nemu (na konci kodu) ako k objektu? Buď mi niečo ušlo počas učenia alebo tam máš chybku :[

Metoda fetch() vrací jednorozměrné pole dle daného záznamu. Využije se v případě, že hledáme jeden konkrétní záznam, například informace o uživateli.

$id = (int) $_GET["id"];

$query = $pdo->query("
  SELECT `first_name`, `last_name`
  FROM `user`
  WHERE `id` = {$id}
  LIMIT 1
");
$data = $query->fetch();

echo $data->first_name;  // vypsání jména daného uživatele
echo $data["first_name"]; // nemá to byť takto?

Dík za odpoveď.

Len tak ďalej, články o PHP ma bavia :D

Odpovědět 29.1.2015 17:48
obsah kocky = r^2 ... a preto vlak drnká
Avatar
Odpovídá na Matúš Petrofčík
Martin Konečný (pavelco1998):

Jo, jasný, defaultně vrací pole, které obsahuje jak řetězcové, tak číselné indexy. Musel by se jako parametr předat konstantu PDO::FETCH_OBJ.
Díky, upravím to.

 
Odpovědět  +1 29.1.2015 17:56
Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Odpovídá na Martin Konečný (pavelco1998)
Matúš Petrofčík:

No dobre, už som sa bál že niečo robím zle :D Lepšie sa ti pracuje s objektami než s poľom?

PS: chybka je aj v príklade nižšie

$_SESSION["userId"] = $data->id;
Odpovědět 29.1.2015 18:03
obsah kocky = r^2 ... a preto vlak drnká
Avatar
Odpovídá na Matúš Petrofčík
Martin Konečný (pavelco1998):

Snad jsem to upravil všude, kde to bylo chybně, tak jen počkám na aktualizaci :)
Jinak ano, lépe se mi pracuje s objektem, přijde mi pohodlnější přístup k vlastnosti (->klic oproti ["klic"]).

 
Odpovědět 30.1.2015 15:24
Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Odpovídá na Martin Konečný (pavelco1998)
Matúš Petrofčík:

pravda no, aj mne to príde cez ten objekt lepšie, asi to tak budem robiť u ďaľšieho projektu aj ja :)

Odpovědět 30.1.2015 19:24
obsah kocky = r^2 ... a preto vlak drnká
Avatar
IT Man
Redaktor
Avatar
IT Man:

A ty komentáře budou kdy? :D

Odpovědět 11.2.2015 14:37
Když nevíš jak dál, podá ti ruku někdo, od koho by jsi to nečekal. A tu šanci musíš přijmout!
Avatar
Odpovídá na IT Man
Martin Konečný (pavelco1998):

Sorry, byl jsem ted nějakou dobu mimo a v příštích několika dnech nebudu mít vůbec čas :(

 
Odpovědět 13.2.2015 22:32
Avatar
tomas.haubert:

Pěkné shrnutí, doufám že přibudou i další :)

 
Odpovědět 23.4.2015 19:06
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 8 zpráv z 8.