tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Best Practice: Spouštění a zpracování uložených procedur

před 10 lety

romansklenar
Člen | 657

Uložené procedury v dibi se dají spouštět například pomocí fluent rozhraní:

dibi::command()->executeProcedure('DELETE_USER %l', array('ID' => 1))->execute();
// sql: EXECUTE PROCEDURE DELETE_USER(1)

Data vrácené procedurou se dají samozřejmě i získat:

$data = dibi::command()->executeProcedure('READ_USERS')->execute()->fetchAll();
// sql: EXECUTE PROCEDURE READ_USERS

Lepší ale je, si podporu pro procedury zapouzdřit do modelu.

$model->execute('DELETE_USER %l', array('ID' => 1));
$model->execute('READ_USERS')->fetchAll();

Uložené procedury mohou i vyhazovat výjimky na úrovní db a to ve dvou případech:

  1. když se procedura rovnou spoustí
EXECUTE PROCEDURE READ_USERS
  1. když se procedura použije za FROM místo tabulky
SELECT * FROM READ_USERS

V prvním případě je případná výjimka z procedury vyhozena hned při provedení dotazu (v dibi driverech funkce query), ale v druhém případě až při získávání dat (v dibi driverech funkce fetch).

Proto dotazy tohoto typu (2. případ) je nutné pro korektní zachycení výjimky v modelu použít ošetřenou funkci.

Spojíme-li takový přístup s fluent rozhraním, dá se s výsledkem procedur ještě v dotazu hezky pracovat:

$model->command()->select('*')->from('READ_USERS')->where("name LIKE 'John'")->orderBy('id')->execute()->safeFetch('all');
// sql: SELECT * FROM READ_USERS WHERE `name` LIKE 'John' ORDER BY `id`

Dost povídání, teď ukázka implementace do jednoduchého modelu.

Ukázka implementace procedur a zpracování chyb v modelu je pro dibi driver Firebird/Interbase, který na případnou výjimku v uložených procedurách reaguje vyhozením DibiProcedureException s metodou getSeverity() vracející název výjimky, tak je je definována v db.

Uvedený postup půjde ale použít i s ostatními drivery v dibi, jen je třeba zajistit vyhazování výjimek při výjimce v proceduře na straně db. Pokud ošetření výjimek nepotřebujete, můžete bloky try – catch v metodě execute() a metody processProcedureException() a safeFetch() vynechat.


/**
 * Simple model class.
 */
class Model extends DibiObject implements IModel
{
    ...


    /**
     * Creates fluent SQL builder.
     * @return DibiFluent
     */
    public function command()
    {
        return $this->connection->command();
    }


    /**
     * Executes procedure.
     * @param  string
     * @param  array
     * @return DibiResult
     */
    public function execute($procedure, array $data = NULL)
    {
        try {
            $query = $this->command();

            if ($data === NULL) {
                $query->executeProcedure($procedure);

            } else {
                $query->executeProcedure("$procedure %l", $data);
            }

            return $query->execute();;

        } catch (DibiProcedureException $e) {
            self::processProcedureException($e);
        }
    }


    /**
     * DibiResult fetch with handling possible exceptions.
     * @param  DibiResult
     * @param  string  possibile:fetch|fetchSingle|fetchAll|fetchPairs
     * @return DibiRow|array|FALSE
     */
    public static function safeFetch(DibiResult $result, $method = NULL, array $cols = NULL)
    {
        try {
            switch ($method) {
                case 'single':
                case 'fetchSingle': $data = $result->fetchSingle(); break;
                case 'all':
                case 'fetchAll': $data = $result->fetchAll(); break;
                case 'pairs':
                case 'fetchPairs': $data = $result->fetchPairs($cols[0], $cols[1]); break;
                default: $data = $result->fetch(); break;
            }
            return $data;

        } catch (DibiProcedureException $e) {
            Model::processProcedureException($e);
        }
    }


    /**
     * Exception handling.
     * @param DibiProcedureException $e
     * @return void
     * @throws DibiProcedureException|InvalidStateException
     */
    public static function processProcedureException(DibiProcedureException $e)
    {
        if ($e->getSeverity() == 'SYSTEM') {
            /* vážná systémová chyba -
             *  zaloguj a ukonči běh aplikace / přenechej činnost ErrorPresenteru
             */
            Debug::processException($e);
            throw $e;

        } elseif ($e->getSeverity() == 'ERROR' || $e->getCode() == 1) {
            /* méně závažná chyba -
             *  ulož do modelu pro pozdější zobrazení (např. validace formuláře) a pokračuj
             */
            $this->exception = $e;

        }
    }

    ...
}

A nakonec zbývá namapovat pro pohodlnější fetchnutí:

DibiResult::extensionMethod('DibiResult::safeFetch', 'Model::safeFetch');

Editoval romansklenar (21. 7. 2009 15:39)

před 9 lety

musa
Člen | 30

Ahoj, jak bych pomocí výše uvedeného zavolal proceduru se dvěma parametry? Jedním IN a jedním OUT, který pak potřebuji přečíst.

před 9 lety

cuga
Člen | 212

Je tenhle Best Practise stale aktualni, resp. je pouzitelny pro MySQL?

Mam ulozenou proceduru, pri volani z Adminera funguje, klasicke volani pres native

$query = $this->db->nativeQuery("call Podrizeni('$id')");
$data = $query;
$query->free();
return $data;

mi vyhodi chybu

PROCEDURE test.Podrizeni can't return a result set in the given context

S ulozenyma procedurama zatim nemam zadne zkusenosti, proto ani nevim, co by tohle mohlo zpusobovat. Mate nekdo tip?