Skip to content

Příklady použití databázového rozhraní

Tato sekce ukazuje běžné scénáře použití třídy DB v rámci systému.


Inicializace databáze

Automatická inicializace (lazy loading)

Třída DB se inicializuje automaticky při prvním použití jakékoliv metody (DB::query(), DB::results(), atd.). Není nutné ručně volat require na app/database.php - to se děje transparentně na pozadí.

Příklad:

// Automatická inicializace při prvním dotazu
$users = DB::results("SELECT * FROM users WHERE active = ?", [1]);
// DB se připojí automaticky, není potřeba require ani DB::init()

Interní inicializace v app/database.php

Inicializace a nastavení DB probíhá automaticky v rámci CMS v souboru app/database.php. Není třeba (ani vhodné) volat DB::init() ani DB::setting() ručně v běžných částech kódu.

Ukázka z app/database.php:

$dbc = parse_url(env('DATABASE_URL'));
DB::init(
    $dbc['user'],
    rawurldecode($dbc['pass']),
    trim($dbc['path'], '/'),
    $dbc['host'],
    'utf8',
    $dbc['port'] ?? 3306
);

$setting = [
    'cacheUse'     => CONFIG['dbUseCache'],
    'cacheSeconds' => CONFIG['dbCacheSeconds'],
    'errorLogUse'  => true,
    'errorLogDir'  => DIR . '/var/log',
    'debugAll'     => false,
];

DB::setting($setting);

Dále se nastavuje SQL režim a kódování:

DB::query("SET sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'");
DB::query("SET NAMES utf8mb4");

Použití v aplikaci

Třídu DB lze používat kdekoli globálně bez manuální inicializace - připojení se vytvoří automaticky při prvním dotazu. To platí pro modely, kontrolery i CLI skripty.

Příklady běžných dotazů následují níže.


Druhá databáze (DB2)

Pro vzácné případy, kdy je potřeba připojení k druhé databázi, použij třídu DB2. Na rozdíl od DB je nutné DB2 explicitně inicializovat před použitím.

Inicializace DB2

use Petrovo\Database\DB2;
use function Petrovo\Dotenv\env;

// Inicializace druhého připojení
$dbc2 = parse_url(env('DATABASE2_URL'));
$port2 = (int)!empty($dbc2['port']) ? $dbc2['port'] : 3306;
DB2::init($dbc2['user'], $dbc2['pass'], trim($dbc2['path'], '/'), $dbc2['host'], 'utf8', $port2);
unset($dbc2);

Použití DB2

Po inicializaci používej DB2:: stejně jako DB:::

// SELECT dotaz na druhou databázi
$product = DB2::row("SELECT p.cena, p.nazev, p.kod
                     FROM gProdukt p
                     WHERE p.kod = ?", [$productCode]);

// INSERT do druhé databáze
$sql = "INSERT INTO log SET action = ?, created = NOW()";
DB2::query($sql, ['product_view']);

Poznámka: DB2 se používá zřídka (např. integrace se starším systémem, external API databáze). Pro běžnou práci používej vždy DB.


Výběry dat SELECT

Práce s daty probíhá výhradně přes statickou třídu DB a využívá připravené SQL dotazy (prepare + execute), které se volají automaticky metodou DB::query().

Pravidla a upozornění

  • Nemíchej náhrady ? a :name v jednom dotazu – způsobuje chyby v PDO. Používej buď jeden, nebo druhý způsob.
  • Parametry se neescapují ručně – escapování provádí MariaDB až na úrovni vykonání dotazu.
  • Nepoužívej vlastní addslashes() nebo ruční escape() – pouze výjimečně můžeš použít DB::escape() pro případy, kdy musíš dynamicky sestavit části dotazu, ale ne hodnoty.
  • Dotaz se připravuje a spouští automaticky pomocí DB::query($sql, $params).
  • Pokud provádíš opakovaný dotaz ve smyčce, je výhodnější připravit si dotaz ručně pomocí DB::prepare() a opakovaně volat DB::execute($stmp, $params).

Výběry dat SELECT

Pro práci s daty používej metody DB::results(), DB::row(), DB::col(), DB::var(). Všechny tyto metody volají interně prepare + execute a vracejí výsledek z databáze podle zvoleného typu výstupu.

Výstupem je vždy datová struktura podle typu požadovaného „row typu“:

  • 'OBJECT' (výchozí) – každý řádek jako objekt
  • 'ARRAY_A' – asociativní pole (['column' => value])
  • 'ARRAY_N' – číselné pole ([0 => value])
  • 'JSON' – JSON string (méně často, např. do API)

Tyto typy platí pro results() a row().


Ukázky použití

// Více řádků – výchozí výstup (OBJECT), bez parametrů
$rows = DB::results("SELECT id, name FROM tags ORDER BY name");
foreach ($rows as $tag) {
    echo $tag->name;
}
// Více řádků – výstup jako pole (ARRAY_A), bez parametrů
$rows = DB::results("SELECT id, name FROM tags ORDER BY name", 'ARRAY_A');
foreach ($rows as $tag) {
    echo $tag['name'];
}
// Jeden řádek – pojmenované parametry
$user = DB::row("SELECT * FROM users WHERE id = :id", ['id' => 42], 'ARRAY_A');
echo $user['email'];
// Jeden sloupec – bez parametrů
$emails = DB::col("SELECT email FROM users ORDER BY id DESC");
// Jedna hodnota – s parametrem
$login = DB::var("SELECT last_login FROM users WHERE id = :id", ['id' => 42]);

Dotazy, které nevrací data

Pro dotazy typu INSERT, UPDATE, DELETE, SET nebo CALL se používá:

DB::query("UPDATE users SET last_login = NOW() WHERE active = 1");

Tyto dotazy nemají návratovou hodnotu, ale ovlivňují $affectedRows, $lastError, $lastQuery atd.


Sestavení SET části dotazu – DB::set()

Metoda DB::set() slouží k automatickému vytvoření části SQL dotazu typu SET column = :column. Výhodou je, že stačí předat asociativní pole, a metoda vygeneruje správný SQL fragment.


⚠️ Upozornění

  • Nepoužívej ? jako placeholdery při použití DB::set() – vygenerované výrazy používají pojmenované parametry (:column), a ty musí být použity i ve zbytku dotazu.
  • Nesmí se míchat ? a :name – jinak dojde k chybě při provádění dotazu.
  • Pokud používáš DB::set(), musíš následně předat pole s klíči odpovídajícími názvům sloupců.

Základní INSERT s DB::set()

$set = [
    'news_id'  => $newsNewId,
    'lang'     => $r->lang,
    'active'   => $r->active,
    'homepage' => $r->homepage,
    'data'     => $r->data,
];

$sql = "INSERT INTO news_lang SET " . DB::set($set);
DB::query($sql, $set);

UPDATE s podmínkou pomocí :id

$set = [];
$set['name']  = html_entity_decode($_post['name']);
$set['alias'] = $_post['alias'];
$set['group'] = array_get_int($_post, 'group', 0);
$set['active'] = array_get_int($_post, 'active', 0);
$set['perex'] = html_entity_decode($_post['perex']);
$set['description'] = $_post['description'];

$sql = "UPDATE cube_type SET " . DB::set($set) . " WHERE id = :id";
DB::query($sql, [...$set, 'id' => $_post['id']]);

Pokročilé použití – ON DUPLICATE KEY UPDATE

$set = [
    'cube_id' => $_POST['cId'],
    'lang'    => $lang,
    'data'    => Json\encode($this->data($lang, $onlyFields)),
];

$sql = "INSERT INTO cube_lang SET " . DB::set($set) .
       " ON DUPLICATE KEY UPDATE " . DB::set($set, ['cube_id', 'lang'], true);

DB::query($sql, $set);

Třetí parametr metody DB::set($fields, $exclude, $useValues) umožňuje použít field = VALUES(field) pro ON DUPLICATE KEY UPDATE, bez nutnosti přepisovat klíče.


Generování SET výrazů – DB::set()

Metoda DB::set() vygeneruje část SQL dotazu typu SET column = :column, nebo alternativní formáty podle hodnot a nastavení.

Signatura

DB::set(array $fields, array $exclude = [], bool $useValues = false): string

Parametry

  • $fields – pole ['column' => value], ze kterého se generují výrazy column = :column
  • $exclude – seznam klíčů, které se mají z $fields vyloučit (např. primární klíče)
  • $useValues – pokud je true, místo :column se použije VALUES(column), např. pro ON DUPLICATE KEY UPDATE

Automatické úpravy hodnot

Metoda se snaží automaticky přizpůsobit výraz podle typu hodnoty:

Hodnota ve vstupu Výstup v SQL
'NOW()' (string) column = NOW()
true (bool) column = 1
false (bool) column = 0
jiné hodnoty column = :column
při useValues = true column = VALUES(column)

Příklad: NOW(), true, false

$set = [
    'published' => true,
    'archived'  => false,
    'created'   => 'NOW()',
    'title'     => 'Nový článek',
];

$sql = "INSERT INTO article SET " . DB::set($set);
DB::query($sql, [$set['title']]);
Upozornění: Zde se musí dát pozor. DB::set() první tři hodnoty doplní automaticky do SQL dotazu dle předchozí tabulky! Lépe použít DB::params(), který přímo vkládané vypustí.

$sql = "INSERT INTO article SET " . DB::set($set);
DB::query($sql, DB::params($set));

Výsledný SQL fragment:

`published` = 1, `archived` = 0, `created` = NOW(), `title` = :title

Příklad: Vyloučení polí (exclude)

$set = [
    'id'    => 42,
    'title' => 'Nový název',
    'slug'  => 'novy-nazev',
];

$sql = "UPDATE article SET " . DB::set($set, ['id']) . " WHERE id = :id";
DB::query($sql, [...$set]);

Příklad: useValues = true pro ON DUPLICATE KEY UPDATE

$set = [
    'user_id' => 7,
    'points'  => 120,
];

$sql = "INSERT INTO leaderboard SET " . DB::set($set) .
       " ON DUPLICATE KEY UPDATE " . DB::set($set, ['user_id'], true);

DB::query($sql, $set);

Výsledný fragment:

`points` = VALUES(`points`)

Práce s JSON daty a podmínkami přes JSON_EXTRACT

V některých případech potřebujeme pracovat s JSON daty přímo v SQL dotazu (např. s polem translations). To platí zejména u polí, která nejsou indexovaná a není třeba na ně aplikovat vyhledávání nebo řazení.

Typické scénáře:

  • test na prázdný překlad ([], [""], null)
  • práce s JSON_EXTRACT(...)
  • nutnost vložit název JSON klíče přímo do SQL — nelze použít ? placeholder

Příklad: kontrola prázdné hodnoty v JSON

$_post = $_post ?? $_POST ?? [];

$_target = DB::escape($_post['target']); // bude použit přímo do SQL
$_section = $_post['section'];

$sql = "SELECT id FROM i18n
        WHERE section = ? AND (
            JSON_LENGTH(JSON_EXTRACT(translations, '$.$_target')) = ''
            OR JSON_LENGTH(JSON_EXTRACT(translations, '$.$_target')) = 0
            OR JSON_EXTRACT(translations, '$.$_target') IS NULL
            OR JSON_EXTRACT(translations, '$.$_target') = '\[\"\"\]'
            OR JSON_EXTRACT(translations, '$.$_target') = 'null')";

$ids = DB::results($sql, [$_section]);

Vysvětlení

  • $_target je klíč v JSON objektu (translations) – musí být vložen do SQL dotazu přímo jako součást stringu.
  • Proto je nutné jej ručně escapovat přes DB::escape() – kvůli bezpečnosti.
  • Parametr section je stále předán běžně přes ?.
  • Dotaz testuje různé formy prázdné hodnoty: NULL, prázdné pole, [""], 0.

Vkládání, úpravy a mazání dat

Pro všechny dotazy, které nevracejí výsledek (INSERT, UPDATE, DELETE), se používá DB::query().

Výsledky těchto operací je možné kontrolovat pomocí:

  • DB::$insertId – ID posledního vloženého záznamu
  • DB::$lastError – text poslední chyby
  • DB::$affectedRows – počet ovlivněných řádků

INSERT s insertId

$sql = "INSERT INTO log_ecomail (email, status, action, created_on)
        VALUES (?, 0, ?, NOW())";

DB::query($sql, [$r->email, $action]);

if ($id = DB::$insertId) {
    // Záznam byl vložen, máme ID
}

INSERT s DB::set() a kontrola chyby

$_post = $_post ?? $_POST ?? [];

$password = array_get_str($_post, 'password', '');

$set['password'] = hash('sha512', DB::escape($password));
$set['username'] = array_get_str($_post, 'username', '');
$set['name']     = array_get_str($_post, 'name', '');
$set['email']    = array_get_str($_post, 'email', '');
$set['group_id'] = array_get_int($_post, 'group_id', 1);
$set['status']   = array_get_str($_post, 'status', 'Blocked');

$sql = "INSERT INTO admin SET id = UUID(), " . DB::set($set);
DB::query($sql, $set);

$output = empty(DB::$lastError);

UPDATE s ověřením změn

$id = (int)($_get['id'] ?? $_GET['id'] ?? 0);
$forLang = $_get['forLang'] ?? $_GET['forLang'] ?? '';

$sql = "UPDATE news_lang SET homepage = NOT homepage WHERE news_id = ? AND lang = ?";
DB::query($sql, [$id, $forLang]);

$output = DB::$affectedRows;

DELETE s návratovou hodnotou

$_id = base64_decode(array_get_str($_GET, 'id', ''));

$sql = "DELETE IGNORE FROM redirection WHERE old = ?";
DB::query($sql, [$_id]);

$output = (bool)DB::$affectedRows;

Práce s transakcemi

Pokud je třeba provést více SQL operací jako celek (např. INSERT + UPDATE + DELETE), použij DB::beginTransaction() spolu s DB::commit() nebo DB::rollback().

Doporučený postup

  1. DB::beginTransaction();
  2. provedení všech dotazů (DB::query(), DB::var(), …)
  3. pokud OK → DB::commit();
  4. při chybě → DB::rollback();

Jednoduchý INSERT s transakcí

DB::beginTransaction();

$sql = "INSERT IGNORE INTO i18n (section, identifier, translations)
        VALUES (?, ?, ?)";

DB::query($sql, [$_section, $_identifier, $translationsJson]);

if ($output = DB::$insertId) {
    DB::commit();
} else {
    DB::rollback();
}

Složený mazací dotaz s transakcí

$_id  = (int)($_get['id'] ?? $_GET['id'] ?? 0);
$_web = (int)($_get['web'] ?? $_GET['web'] ?? 0);

DB::beginTransaction();

// Získání content_id podle ID stránky
$sql = "SELECT content_id FROM page WHERE id = ?";
$contentId = DB::var($sql, [$_id]);

// Smazání z content
$sql = "DELETE FROM content WHERE id = ?";
DB::query($sql, [$contentId]);

// Smazání z page
$sql = "DELETE IGNORE FROM page WHERE id = ?";
DB::query($sql, [$_id]);
$output = (bool)DB::$affectedRows;

// Smazání z tabulky slug
$sql = "DELETE FROM slug WHERE web_id = ? AND type='page' AND join_id = ?";
DB::query($sql, [$_web, $_id]);

if (DB::$lastError) {
    DB::rollback();
} else {
    DB::commit();
}

return outputJson($output);