Phalcon\Mvc\Model - rolling back failed transaction - php

The code:
class myModel extends Phalcon\Mvc\Model
{
public function beforeSave()
{
$this->getDi()->getShared('db')->begin();
}
...
public function afterSave()
{
$this->getDi()->getShared('db')->commit();
}
}
My question is - what happens if along the way, between beforeSave() and afterSave() there's an Exception thrown - how can I cleanly roll back the transaction? Where should I stick $this->getDi()->getShared('db')->rollback(); in to?
Thanks!

I'd recommend overloading the save() method entirely.
Here's my example with transactions. Note that you won't need transactions if you're not going to implement additional logic here (like removing related models)
/**
* Delete old relations before saving new ones
*/
public function save($data=null, $whiteList=null)
{
$db = $this->getDI()->get('db');
try
{
// Start transaction
$db->begin();
// ... Do some additional work. Remove related models etc...
// Update model
if (!parent::save($data, $whiteList))
throw new \Exception('Cannot save the model');
$db->commit();
}
catch (\Exception $e)
{
$db->rollback();
$this->appendMessage(new \Phalcon\Mvc\Model\Message($e->getMessage(), '', 'error', $this));
return false;
}
return true;
}

Related

Laravel execute function inside relation

I have some troubles after implementing polymorphic relations into my Laravel database.
Namely, I'm trying to execute two queries, one to save some counts and the other to update the polymorphic table. But update never happens, and I have no errors. Actually, update function works, but it's not executing function before that.
So, here is what my function looks like:
public function setThreadLastInteraction(Thread $thread)
{
return $this->ancestorsAndSelf->map(function ($c) use ($thread) {
try {
$c->statistics(function ($query) use ($thread) {
try {
$query->interaction()->save($thread);
} catch (\Exception $e) {
Log::error($e, __NAMESPACE__, __FUNCTION__);
}
})->update([
'threads_count' => DB::raw('threads_count + 1'),
]);
} catch (\Exception $e) {
Log::error($e, __NAMESPACE__, __FUNCTION__);
}
})->toArray();
}
First, I have to update every category related to the other one, after that I'm doing some function for each of them.
As you see, I'm accessing the Statistics model. Only thing that is not working is inside the statistics function:
try {
$query->interaction()->save($thread);
} catch (\Exception $e) {
Log::error($e, __NAMESPACE__, __FUNCTION__);
}
I'm obviously calling relation as it should be, cause It's updating 'threads_count'.
But here is how it looks like:
public function statistics(): HasOne
{
return $this->hasOne(Statistics::class);
}
And finally, inside Statistics class, relation that should be updated but it's not.
public function interaction(): MorphTo
{
return $this->morphTo();
}
I tried to remove everything inside statistics function and do it like (still inside map function):
$c->statistics()->interaction()->save($thread);
$c->statistics()->update([
'threads_count' => DB::raw('threads_count + 1'),
]);
That way, I'm accessing directly to the model. But it says that interaction relationship is not existing.
What could be the issue? Why Interaction function is not accessible within relation?

Symfony: Best place to catch exception (CQRS/DDD)

I've a personal application. I use design pattern CQRS/DDD for a API.
Schema:
User --> Controller (dispatch command) --> Command handler --> some services...
In my Rest API controller
$this->dispatch($cmd);
If a throw a exception in services or specification classes for example, ok, I've a listener to catch exception and create JSON response error.
But if I want to develop an interface module with TWIG, I think I will not use my listener because I don't want a JSON response.
Should I used try/catch in my controller of my new interface module ?
SomeController extends AbstractController
{
public function getObject($id)
{
try {
$this->dispatch($cmd);
catch(SomeException $ex) {
$this->render(....)
}
}
}
Where is the best place to catch exception for TWIG ?
Thanks.
Edit:
#Cid
if (some conditions && $form->handleRequest($request)->isValid()) --> My handler don't return bool or values.
Imagine this code. Imagine I want share a service between an API and web view app.
class ApiController
{
public function register()
{
$this->dispatch($cmd);
}
}
class WebController
{
public function register()
{
$this->dispatch($cmd);
}
}
class SomeHandler implements CommandHandlerInterface
{
/** #required */
public RegisterService $service;
public function __invoke(SomeCommand $command)
{
$this->service->register($command->getEmail())
}
}
class RegisterService
{
public function register(string $email)
{
// Exception here
}
}
So, I think the best place to handle Exception is EventSubscriber, see here: https://symfony.com/doc/current/reference/events.html#kernel-exception
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
$response = new Response();
// setup the Response object based on the caught exception
$event->setResponse($response);
// you can alternatively set a new Exception
// $exception = new \Exception('Some special exception');
// $event->setThrowable($exception);
}

How does DBAL read data that ORM inserts but has not yet “flush”?

For historical reasons, my pattern of running databases using Symfony is mixed. That is, the query uses DBAL and the insert uses ORM. Now you need to write a lot of data to the database. The flush in ORM can help me achieve business at the lowest cost.
All flush operations have been removed from the project. Put it in the __destruct of the controller.
However, doing so will cause DBAL to not find the latest changed data. Of course, these data ORMs can be obtained normally.
This is a very difficult problem. I hope to get guidance.
class BaseController extends Controller
{
public function __destruct()
{
$this->getDoctrine()->getManager()->flush();
}
public function indexAction()
{
$model = new CompanyModel();
$model->install(['company_name' => '1234']);
$model->update(['company_name' => 'abcd'], $model->lastInsertId);
}
}
class CompanyModel extends BaseController
{
public function validate($data, $id = false)
{
$this->entityManager = $this->getDoctrine()->getManager();
if(empty($id)){
$this->company_class = new Company();
}else{
if(!$this->is_exist($id)){
return false;
}
$this->company_class = $this->entityManager->getRepository(Company::class)->find($id);
}
if(array_key_exists('company_name', $data)){
$this->company_class->setCompanyName($data['company_name']);
}
if(self::$error->validate($this->company_class)){
return false;
}
return true;
}
public function insert($data)
{
if(!$this->validate($data)){
return false;
}
$this->company_class->setCreateAt(new \DateTime());
$this->entityManager->persist($this->company_class);
//$this->entityManager->flush();
$this->lastInsertId = $this->company_class->getId();
return true;
}
public function update($data, $id)
{
if(empty($id)){
self::$error->setError('param id is not null');
return false;
}
if(!$this->validate($data, $id)){
return false;
}
$this->company_class->setUpdateAt(new \DateTime());
//$this->entityManager->flush();
return true;
}
public function is_exist($id)
{
return $this->get('database_connection')->fetchColumn('...');
}
}
The final result of executing indexAction company_name is 1234; $ model-> update() was not executed successfully. The reason is that the $this-> is_exist() method that took the DBAL query did not find the ORM insert but did not flush the message.
Unchanging conditions,run
$this->entityManager->getRepository(Company::class)->find($id);
Is successful。
The problem is not the entity manager or dbal, as far as I can tell, but the usage of an anti-pattern, which I would call ... entanglement. What you should strive for is separation of concerns. Essentially: Your "CompanyModel" is an insufficient and bad wrapper for the EntityManager and/or EntityRepository.
No object should know about the entity manager. It should only be concerned with holding the data.
The entity manager should be concerned with persistence and ensuring integrity.
The controller is meant to orchestrate one "action", that can be adding one company, editing one company, batch-importing/updatig many companies.
Services can be implemented, when actions become to business-logic-heavy or when functionality is repeated.
(Note: the following code samples could be made way more elegant with using all the features that symfony provide, like ParamConverters, the Form component, the Validation component, I usually wouldn't write code this way, but I assume everything else would go way over your head - no offence.)
handling actions in the controller
controller actions (or service actions, really) are when you look at your problem from the task perspective. Like "I want to update that object with this data"). That's when you fetch/create that object, then give it the data.
use Doctrine\ORM\EntityManagerInterface;
class BaseController extends Controller {
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function addAction() {
$company = new Company(['name' => '1234']); // initial setting in constructor
$this->em->persist($company);
// since you have the object, you can do any changes to it.
// just change the object
$company->update(['name' => 'abcd']); // <-- don't need id
// updates will be flushed as well!
$this->em->flush();
}
public function editAction($id, $newData) {
$company = $this->em->find(Company::class, $id);
if(!$company) {
throw $this->createNotFoundException();
}
$company->update($newData);
$this->em->flush();
}
// $companiesData should be an array of arrays, each containing
// a company with an id for update, or without an id for creation
public function batchAction(array $companiesData) {
foreach($companies as $companyData) {
if($companyData['id']) {
// has id -> update existing company
$company = $this->em->find(Company::class, $companyData['id']);
//// optional:
// if(!$company) { // id was given, but company does not exist
// continue; // skip
// // OR
// $company = new Company($companyData); // create
// // OR
// throw new \Exception('company not found: '.$companyData['id']);
// }
$company->update($companyData);
} else {
// no id -> create new company
$company = new Company($companyData);
$this->em->persist($company);
}
}
$this->em->flush(); // one flush.
}
}
the base controller should handle creating objects, and persisting it, so very basic business logic. some would argue, that some of those operations should be done in an adapted Repository for that class, or should be encapsulated in a Service. And they would be right, generally.
the entity handles it's internal state
Now, the Company class handles its own properties and tries to stay consistent. You just have to make some assumptions here. First of all: the object itself shouldn't care if it exists in the database or not. it's not its purpose! it should handle itself. Separation of concerns! The functions inside the Company entity should concern simple business logic, that concerns its INNER state. It doesn't need the database, and it should not have any reference to the database, it only cares about it's fields.
class Company {
/**
* all the database fields as public $fieldname;
*/
// ...
/**
* constructor for the inital state. You should never want
* an inconsistent state!
*/
public function __construct(array $data=[]) {
$this->validate($data); // set values
if(empty($this->createAt)) {
$this->createAt = new \DateTime();
}
}
/**
* update the data
*/
public function update(array $data) {
$this->validate($data); // set new values
$this->updateAt = new \DateTime();
}
public function validate(array $data) {
// this is simplified, but you can also validate
// here and throw exceptions and stuff
foreach($array as $key => $value) {
$this->$key = $value;
}
}
}
some notes
Now, there should be NO use case, where you get an object to persist and at the same time an update - with an id - that refers to the new object ... unless that object was given the id beforehand! HOWEVER. If you persist an object, that has an ID and you call $this->em->find(Company::class, $id) you would get that object back.
if you have many relations, there are always good ways to solve this problem without destroying separation of concerns! you should never inject an entity manager into an entity. the entity should not manage its own persistence! nor should it manage the persistence of linked objects. handling persistence is the purpose of the entity manager or entity repository. you should never need a wrapper around an object just to handle that object. be careful not to mix responsibilities of services, entities (objects) and controllers. In my example code, I have merged services and controllers, because in simple cases, it's good enough.

Laravel Implict Binding got No query results for model

I want suggestion how to handle and which method is best one. Implicit Binding or Normal Binding method.
I'm using Laravel route implicit binding. when I post wrong ID, I got error No query results for model how to handle in controller not Exception Handler. Now I done with exception handler but need better solution to handle this or avoid Implicit binding.
//Web.php
Route::delete('/master/user/department/{department}/delete', ['as' => 'master.user.department.destroy', 'middleware' => 'permission:master.user.department.destroy', 'uses' => 'Master\User\DepartmentController#destroy']);
//DepartmentContrller.php
public function destroy(Department $department)
{
try {
$department->delete();
return redirect(route('master.user.department.index'))->with('success', array(' Department Deleted successfully'));
} catch (Exception $e) {
return back()->with('criticalError', array($e->getMessage()));
}
}
//Handler.php
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException)
{
return redirect()->back()->with('custom_modal', ['Model Not Found Exception', $exception->getMessage()]);
}
The below code is perfectly work, I would like to know which method is best one.
//DepartmentContrller.php
public function destroy($id)
{
try {
$department=Department::find($id);
if($department){
$department->delete();
return redirect(route('master.user.department.index'))->with('success', array(' Department Deleted successfully'));
}
else{
return back()->with('criticalError', array('Department is not found.'));
}
} catch (Exception $e) {
return back()->with('criticalError', array($e->getMessage()));
}
}
Both methods are valid. It is up to you to choose which method is appropriate for your situation.
Implicit model binding will let you get code out the door quicker, but you give up some control.
Explicit (normal) binding will take more code to write, but you have complete control over how the exceptions are caught and handled.
Just an FYI, if you stick with implicit binding, the ModelNotFoundException has a getModel() method that will give you the name of the model that caused the exception. This will let you customize your exception handling a little bit more, but still doesn't give you the same control as handling the exception where it happens.
All of above method be work for you but you can override elqoent method --find() in case of you in your respective model
// Put this in any model and use
// Modelname::find($id);
public static function findOrCreate($id)
{
$obj = static::find($id);
return $obj ?: new static;
}
in depth description

Yii2 set new active record relation on init

I have a one-to-one relationship, where there are extra fields for Thing in a table ThingExtra.
I'm trying to initialise a new ThingExtra to work with when creating a new Thing, and then save them both when Thing is saved:
class Thing extends ActiveRecord
{
public function init(){
if($this->isNewRecord){
$this->extra = new ThingExtra();
}
}
public function getExtra(){
return $this->hasOne(ThingExtra::className(),['thing_id' => 'id']);
}
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
$this->extra->thing_id = $this->id;
$this->extra->save();
}
}
Now when I try to create a Thing:
$thing = new Thing;
I get the following exception:
Exception: Setting read-only property: Thing::extra
Is there anyway round this? Or is my design utterly flawed?
This approach worked pretty well for us in Yii1.1
You cannot assign a relation like this, you could try this instead :
public function init(){
if($this->isNewRecord){
$this->populateRelation('extra', new ThingExtra());
}
}
Read more about ActiveRecord::populateRelation()

Categories