Transacciones en modelos

Resumen
Cuando un proceso ejecuta múltiples operaciones de bases de datos, es importante realizar todas esas operaciones como una sola unidad de trabajo. De esta forma, si una de las operaciones falla, no terminaremos con datos corruptos o registros huérfanos. Las transacciones de base de datos ofrecen esta funcionalidad, y aseguran que todas las operaciones de base de datos han sido ejecutadas correctamente antes de almacenar los datos en la base de datos.
Las transacciones en Phalcon le permite confirmar todas las operaciones si se ejecutaron correctamente o deshacer todas las operaciones si algo ha ido mal.
Manual
Si una aplicación sólo usa una conexión y las transacciones no son muy complejas, una transacción se puede crear iniciando una transacción en la conexión y si todo está bien confirmar la transacción o deshacerla:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Db\Adapter\Pdo\Mysql;
/**
* @property Mysql $db
*/
class InvoicesController extends Controller
{
public function saveAction()
{
$this->db->begin();
try {
$customer = Customers::findFirst(
[
'conditions' => 'cst_id = :cst_id:',
'bind' => [
'cst_id' => 10,
]
]
);
$customer->cst_has_unpaid = true;
$result = $customer->save();
if (false === $result) {
throw new \Exception('Error saving file');
}
$invoice = new Invoices();
$invoice->inv_cst_id = $customer->cst_id;
$invoice->inv_number = 'INV-00001';
$invoice->inv_name = 'Invoice for Goods';
$invoice->inv_created_at = date('Y-m-d');
$result = $invoice->save();
if (false === $result) {
throw new \Exception('Error saving file');
}
$this->db->commit();
} catch (\Exception $ex) {
$this->db->rollback();
echo $ex->getMessage();
}
}
}
Implícito
Se pueden usar las relaciones existentes para almacenar registros en sus instancias relacionadas. Una operación como esta implícitamente crea una transacción para asegurar que los datos son correctamente almacenados:
<?php
use MyApp\Models\Invoices;
use MyApp\Models\Customers;
$invoice = new Invoices();
$invoice->inv_cst_id = $customer->cst_id;
$invoice->inv_number = 'INV-00001';
$invoice->inv_name = 'Invoice for Goods';
$invoice->inv_created_at = date('Y-m-d');
$customer = new Customers();
$customer->cst_name = 'John Wick';
$customer->cst_has_unpaid = true;
$customer->invoices = $invoice;
$customer->save();
Aislado
Las transacciones aisladas se ejecutan en una nueva conexión asegurando que todo el SQL generado, las comprobaciones de claves ajenas virtuales y reglas de negocio están aisladas de la conexión principal. Este tipo de transacción requiere un gestor de transacciones que globalmente gestione cada transacción creada, asegurándose de que son correctamente deshechas o confirmadas antes de terminar la petición:
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager->get();
try {
$customer = Customers::findFirst(
[
'conditions' => 'cst_id = :cst_id:',
'bind' => [
'cst_id' => 10,
]
]
);
$customer->cst_has_unpaid = true;
$result = $customer->save();
if (false === $result) {
throw new \Exception('Error saving file');
}
$invoice = new Invoices();
$invoice->inv_cst_id = $customer->cst_id;
$invoice->inv_number = 'INV-00001';
$invoice->inv_name = 'Invoice for Goods';
$invoice->inv_created_at = date('Y-m-d');
$result = $invoice->save();
if (false === $result) {
throw new \Exception('Error saving file');
}
$transaction->commit();
} catch (TxFailed $ex) {
$transaction->rollback();
echo $ex->getMessage();
}
Las transacciones se pueden usar para eliminar un número de registros, asegurándose que todo se borre correctamente:
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager->get();
try {
$invoices = Invoices::find(
[
'conditions' => 'inv_cst_id = :cst_id:',
'bind' => [
'cst_id' => 10,
]
]
);
foreach ($invoices as $invoice) {
$invoice->setTransaction($transaction);
if (false === $invoice->delete()) {
$messages = $invoice->getMessages();
foreach ($messages as $message) {
$transaction->rollback(
$message->getMessage()
);
}
}
}
$transaction->commit();
} catch (TxFailed $ex) {
echo $ex->getMessage();
}
Excepciones
Any exceptions thrown in the Logger component will be of type Phalcon\Mvc\Model\Transaction\Exception or Phalcon\Mvc\Model\Transaction\Failed. Puede usar estas excepciones para capturar selectivamente sólo las excepciones lanzadas desde este componente.
Additionally, you can throw an exception if the rollback was not successful, by using the throwRollbackException(true)
method.
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager
->get()
->throwRollbackException(true)
;
try {
$invoices = Invoices::find(
[
'conditions' => 'inv_cst_id = :cst_id:',
'bind' => [
'cst_id' => 10,
]
]
);
foreach ($invoices as $invoice) {
$invoice->setTransaction($transaction);
if (false === $invoice->delete()) {
$messages = $invoice->getMessages();
foreach ($messages as $message) {
$transaction->rollback(
$message->getMessage()
);
}
}
}
$transaction->commit();
} catch (TxFailed $ex) {
echo $ex->getMessage();
}
Inyección de Dependencias
Las transacciones se reutilizan sin importar de donde se recupera el objeto transacción. Se genera una nueva transacción sólo cuando se realiza un commit()
o rollback()
. Puede usar el contenedor de servicio para crear un gestor de transacciones global para toda la aplicación:
<?php
use Phalcon\Mvc\Model\Transaction\Manager;
$container->setShared(
'transactions',
function () {
return new Manager();
}
);
Entonces accede a él desde un controlador o vista:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Transaction\Manager;
/**
* @property Manager $transactions
*/
class ProductsController extends Controller
{
public function saveAction()
{
$manager = $this->di->getTransactions();
$manager = $this->transactions;
$transaction = $manager->get();
// ...
}
}
NOTE: While a transaction is active, the transaction manager will always return the same transaction across the application.