Reverting object property changes in php - php

I have a bunch of Doctrine php objects and I'm calling things like
$myObj = $query->execute()->getFirst();
$myObj->setName('a new name');
$myObj->setAge('40');
$myObj->save();
etc...
etc...
And at some point, if encounter an error, I'd like to revert all those property changes since I've already called the save() function and that propagates the new properties to the database.
I'd like to come up with a way to store the old value of the object as well as which method to call to revert it. Something like:
$undoCollection = array();
$undoObject = array();
$undoObject['revertFunction'] = $myObj->setName;
$undoObject['revertValue'] = 18;
array_push($undoCollection,$undoObject)
So that later on, if something goes wrong, I can loop through the $undoCollection:
foreach($undoCollection as $undoObj)
{
$undoObj['revertFunction']($undoObj['revertValue']);
}
Is this something that's possible in PHP? Or is there something else that can provide that functionality?

You should use transactions. E.g.:
$myObj = $query->fetchOne();
if ($myObj)
{
$conn = $myObj->getTable()->getConnection();
try
{
$conn->beginTransaction();
$myObj->setName('Asd')->save();
// do other stuff...
// if an exception is thrown before calling commit
// nothing in this try block will be saved
$conn->commit();
}
catch(Exception $e)
{
$conn->rollback();
}
}

Related

Does Laravel 5.3019 database Transaction method work?

I've use Larvel 5.0 with Database transaction all the method in my previous web application it work as well and we are really love it because this application help me much more than our estimated
so we have create another webs application by using this newest version of this framework and used the same Database structure but finaly it would not work for me and another one to.
I have as more peoples and post on some toturial website for asking any belp but not yet get any solution so I record this video for sure about this case.
Issue: My issue I've disabled (//commit()) method all data still can insert into Database.
final function Add()
{
if ($this->request->isMethod('post')) {
//No you will see this method use with Try Catch and testing again
//DB::beginTransaction(); // Ihave testing with outside of try and inside again
Try{
DB::beginTransaction();
$cats = new Cat();
$catD = new CategoryDescriptions();
$cats->parent_id = $this->request->input('category_id');
$cats->status = ($this->request->input('status')) ? $this->request->input('status') : 0;
if (($res['result'] = $cats->save())== true) {
$catD->category_id = $cats->id;
$catD->language_id = 1;
$catD->name = $this->request->input('en_name');
if (($res['result'] = $catD->save()) === true) {
$catD2 = new CategoryDescriptions();
$catD2->category_id = $cats->id;
$catD2->language_id = 2;
$catD2->name = $this->request->input('kh_name');
$res['result'] = $catD2->save();
}
}
if(!empty($res)) {
//DB::commit();
}
return [$res,($res['result'] = $catD->save())];
}catch(\Exception $e){ // I have already try to use Exception $e without backslash
DB::rollback();
}
}
$cat = Cat::with(['CategoryDescriptions', 'children'])->where('status', 1)->get();
return view('admin.categories.add', ['cat' => $cat]);
}
You can check on my video to see that .
Check on my video
I don't know why your code did not work. But you can try with this code I think it's will work. Laravel transaction documentation
try{
DB::transaction(function)use(/*your variables*/){
// your code
});
}catch(\PDOException $exception){
//debug
}
If any exception occurs it will automatically rollback. If you want manual rollback then inside transaction you can throw a manual exception based on your logic.

Laravel DB::transaction exception PDO There is no active transaction

i have the following code:
$new_models = DB::transaction(function () use ($supplier, $address, $addressDetail) {
$new_supplier = $this->setNewSupplier($supplier);
$new_address = $this->setNewAddress($address);
$new_addressDetail = $this->setNewAddressDetail($addressDetail,$new_address->id);
$this->syncSupplierAddress($new_supplier->id,$new_address->id);
$this->updateControlAp($new_supplier->supplier_id);
return [$new_supplier, $new_address, $new_addressDetail];
});
The set methods are basically creating the models object with save() at the end;
Now this works perfectly fine if 2nd...nth fails BUT NOT if the first one fails.
If $this->setNewSupplier($supplier);
fails than i get
"PDOException in Connection.php line 541:
There is no active transaction"
Am i doing something wrong here ? Also if i comment $this->rollBack(); from the catch in vendor Connection.php it actually gives me the SQL error. Important part here is that this isn't working only if first save() fails
PS. I am using PostgreSQL not MySQL but i don't think its related
There are different ways to make transactions in Laravel, another one could be this:
...
$new_models = [];
try {
DB::beginTransaction();
$new_supplier = $this->setNewSupplier($supplier);
$new_address = $this->setNewAddress($address);
$new_addressDetail = $this->setNewAddressDetail($addressDetail,$new_address->id);
$this->syncSupplierAddress($new_supplier->id,$new_address->id);
$this->updateControlAp($new_supplier->supplier_id);
$new_models = [$new_supplier, $new_address, $new_addressDetail];
DB::commit();
} catch(\Exception $e) {
DB::rollback();
// Handle Error
}
...

Catching Exceptions in OO PHP

I have a class Person.
I want to add error handling into my script, so that say, the user enters an incorrect email address the script will tell them. Usually not a problem at all, but now I am using OO classes I am in unfamiliar territory.
So. I guess I want to know how to handle multiple exceptions. Or do I need to try each line of code one at a time and catch each line? This seems slightly excessive. Ideally I'd like to do the following:
try {
$people[$new]->set_fullname($_POST['name']);
$people[$new]->set_active(true);
$people[$new]->set_add1(rEsc($_POST['add1']));
$people[$new]->set_add2(rEsc($_POST['add2']));
$people[$new]->set_add3(rEsc($_POST['add3']));
$people[$new]->set_add4(rEsc($_POST['add4']));
$people[$new]->set_postcode(rEsc($_POST['postcode']));
$people[$new]->set_phone(rEsc($_POST['phone']));
$people[$new]->set_email(rEsc($_POST['email']));
} catch {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
But in my error handling, How can I catch multiple errors? I'd like to push all the error messages into an array and display them each nicely in the webpage. As far as I can see on php.net it seems that I can only catch one error message at a time.
Do I really have to try {} catch {} each line of code?
Imho this shouldn't throw exceptions in the first place. Simply loop through the fields and add the possible errors to some $errors array.
Users screwing up fields is not an exceptional case. I don't even think the user object should be able to validate an emailaddress. That seems to be like a responsibility of the Form.
Also am I wondering what that rEsc function is you are using. Not only are you using a global function which makes it virtually impossible to swap it out for some other function in the future (tight coupling), but also the name is chosen badly. Also do I fail to see why you would want to escape stuff in that place (I guess that is what the thing does). Only escape / sanitize data when you are using it. And I'm wondering for what you are escaping your data, because if it is for database input there are far better ways.
try {
$people[$new]->set_fullname($_POST['name']);
$people[$new]->set_active(true);
$people[$new]->set_add1(rEsc($_POST['add1']));
$people[$new]->set_add2(rEsc($_POST['add2']));
$people[$new]->set_add3(rEsc($_POST['add3']));
$people[$new]->set_add4(rEsc($_POST['add4']));
$people[$new]->set_postcode(rEsc($_POST['postcode']));
$people[$new]->set_phone(rEsc($_POST['phone']));
$people[$new]->set_email(rEsc($_POST['email']));
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} catch (EmailFormatException $em) {
echo 'Caught exception: '. $e->getMessage();
}
Just continue it like that
Here's how I would design this:
Create a validate() method on the Person class that verifies every property and returns an array of strings that explain the errors to the user. If there are no errors, have the method return null.
Do not use exceptions at all. They are slow; they complicate code maintenance (and you're seeing the symptoms in the approach you've taken so far)
Remove the custom methods for setting properties of the Person object. PHP is not Java. Set the properties directly.
Putting this all together:
class Person {
public $name;
public $address1;
public $address2;
public function validate() { }
}
And then your code:
$obj = new Person();
$obj->name = "Bob";
$obj->address1 = "1 Elm St.";
$validationResult = $obj->validate();
if ( $validationResult != null) { // there were errors
print_r($validationResult);
}
You can make a foreach statement that sets the data that needs validation with try/catch inside the loop in order to populate an array with the errors, like that:
$errors = [];
foreach (['field1', 'field2', ...] as $field) {
try {
$method = "set_{$field}";
$people[$new]->$method(rEsc($_POST[$field]));
} catch (Exception $e) {
$errors[] = $e->getMessage();
}
}

Handling exceptions for multiple calls to functions of a class

I can't get my head around WHEN to throw and catch exceptions for when I call functions from classes.
Please imagine that my QuizMaker class looks like this:
// Define exceptions for use in class
private class CreateQuizException extends Exception {}
private class InsertQuizException extends Exception {}
class QuizMaker()
{
// Define the items in my quiz object
$quiz_id = null;
$quiz_name = null;
// Function to create a quiz record in the database
function CreateQuizInDatabase()
{
try
{
$create_quiz = // Code to insert quiz
if (!$create_quiz)
{
// There was an error creating record in the database
throw new CreateQuizException("Failed inserting record");
}
else
{
// Return true, the quiz was created successfully
return true;
}
}
catch (CreateQuizException $create_quiz_exception)
{
// There was an error creating the quiz in the database
return false;
}
}
function InsertQuestions()
{
try
{
$insert_quiz = // Code to insert quiz
if (!$insert_quiz)
{
// There was an error creating record in the database
throw new CreateQuizException("Failed inserting quiz in to database");
}
else
{
// Success inserting quiz, return true
return true;
}
}
catch (InsertQuizException $insert_exception)
{
// Error inserting question
return false;
}
}
}
... and using this code, I use the class to create a new quiz in the database
class QuizMakerException extends Exception {}
try
{
// Create a blank new quiz maker object
$quiz_object = new QuizMaker();
// Set the quiz non-question variables
$quiz_object->quiz_name = $_POST['quiz_name'];
$quiz_object->quiz_intro = $_POST['quiz_intro'];
//... and so on ...
// Create the quiz record in the database if it is not already set
$quiz_object->CreateQuizRecord();
// Insert the quiz in to the database
$quiz_object->InsertQuestions();
}
catch (QuizMakerException $quiz_maker_error)
{
// I want to handle any errors from these functions here
}
For this piece of code, I want to call a QuizMakerException if any of the functions don't perform what I want them to (at the moment they return TRUE or FALSE).
What is the correct way to go about catching when any of the functions in this code does not perform what I want them to? At the moment they simply return TRUE or FALSE.
Do I really have to put lots of if/else statements between calling each function, I thought that was the whole point in exceptions, they simply halt the execution of further statements within the try/catch?
Do I throw a QuizMakerException from within the catch of my functions?
What is the right thing to do?
Help!
Well typically in the function which throws the exception, say your InsertQuestions method, you don't want to catch any exceptions, you want to throw them or let ones occurring to "bubble up". Then your "controller" code can make the determination of how to handle the exception.
If your goal here is to halt if CreateQuizRecord fails I would wrap CreateQuizRecord and InsertQuestions each in their own try block.
One advantage of exceptions is they can tell you more than a simple bool pass/fail. Either extending your base exception into things like "Invalid_Parameter" and testing for specific exceptions or -less ideally- inferring from properties of the exception. You can nest your catch blocks to handle exceptions individually.
Do I throw a QuizMakerException from within the catch of my functions?
Yes. Typically your code under // Code to insert quiz would itself return an exception. Say if the Model failed to insert it might be raising a database exception. In which case you can let that database exception bubble up, or do what you sort of doing now and catch it simply to in turn throw another exception (kinda dumbs down your exceptions in a way, doing that though).
Do I really have to put lots of if/else statements between calling each function, I thought that was the whole point in exceptions, they simply halt the execution of further statements within the try/catch?
I look at it like this, each call which throws an exception and is followed by a subsequent call which depends on this one not throwing any exceptions, should be wrapped in a try block. Assuming you want to handle it gracefully, if you simply want it to error out and halt just don't handle the exception. You'll get an error and stack trace. Sometimes that is desirable.
You might want to change the structure a bit. Your class QuizMaker can become:
<?php
// Define exceptions for use in class
public class CreateQuizException extends Exception {}
public class InsertQuizException extends Exception {}
class QuizMaker()
{
// Define the items in my quiz object
$quiz_id = null;
$quiz_name = null;
// Function to create a quiz record in the database
function CreateQuizInDatabase()
{
try
{
$create_quiz = // Code to insert quiz
if (!$create_quiz)
{
// There was an error creating record in the database
throw new CreateQuizException("Failed inserting record");
}
else
{
// Return true, the quiz was created successfully
return true;
}
}
catch (Exception $create_quiz_exception)
{
// There was an error creating the quiz in the database
throw new CreateQuizException(
"Failed inserting record " .
$create_quiz_exception->getMessage()
);
}
}
function InsertQuestions()
{
try
{
$insert_quiz = // Code to insert quiz
if (!$insert_quiz)
{
// There was an error creating record in the database
throw new InsertQuizException("Failed inserting quiz in to database");
}
else
{
// Success inserting quiz, return true
return true;
}
}
catch (Exception $insert_exception)
{
// Error inserting question
throw new InsertQuizException(
"Failed inserting quiz in to database " .
$create_quiz_exception->getMessage()
);
}
}
}
Effectively, if you cannot insert the record correctly, you throw an Insert exception. If anything goes wrong with that block of code, you catch it and throw again an Insert exception. Same goes with the Create function (or any other ones you might have).
In the main block of code you can have:
try
{
// Create a blank new quiz maker object
$quiz_object = new QuizMaker();
// Set the quiz non-question variables
$quiz_object->quiz_name = $_POST['quiz_name'];
$quiz_object->quiz_intro = $_POST['quiz_intro'];
//... and so on ...
// Create the quiz record in the database if it is not already set
$quiz_object->CreateQuizRecord();
// Insert the quiz in to the database
$quiz_object->InsertQuestions();
}
catch (InsertQuizException $insert_exception)
{
// Insert error
}
catch (CreateQuizException $create_quiz_exception)
{
// Create error
}
catch (Exception $quiz_maker_error)
{
// Any other error
}
If you don't want to have the multiple catch block there, just keep the catch(Exception) one and then check in it the type of each exception thrown to specify the actions taken from there.
HTH
The best thing to do is to not have to thhrow exceptions in the first place.
Exceptions are thrown when the program crashes and they are not made to handle wrong output.
If your function returns anything (even its the wrong thing) it doesn't need an exception.
To answer your question, if it's necessaryto use a lot of ifs/elses then you must use them.

PHP Exceptions in Classes

I'm writing a web application (PHP) for my friend and have decided to use my limited OOP training from Java.
My question is what is the best way to note in my class/application that specific critical things failed without actually breaking my page.
My problem is I have an Object "SummerCamper" which takes a camper_id as it's argument to load all of the necessary data into the object from the database. Say someone specifies a camper_id in the query string that does not exist, I pass it to my objects constructor and the load fails. I don't currently see a way for me to just return false from the constructor.
I have read I could possibly do this with Exceptions, throwing an exception if no records are found in the database or if some sort of validation fails on input of the camper_id from the application etc.
However, I have not really found a great way to alert my program that the Object Load has failed. I tried returning false from within the CATCH but the Object still persists in my php page. I do understand I could put a variable $is_valid = false if the load fails and then check the Object using a get method but I think there may be better ways.
What is the best way of achieving the essential termination of an object if a load fails? Should I load data into the object from outside the constructor? Is there some sort of design pattern that I should look into?
Any help would be appreciated.
function __construct($camper_id){
try{
$query = "SELECT * FROM campers WHERE camper_id = $camper_id";
$getResults = mysql_query($query);
$records = mysql_num_rows($getResults);
if ($records != 1) {
throw new Exception('Camper ID not Found.');
}
while($row = mysql_fetch_array($getResults))
{
$this->camper_id = $row['camper_id'];
$this->first_name = $row['first_name'];
$this->last_name = $row['last_name'];
$this->grade = $row['grade'];
$this->camper_age = $row['camper_age'];
$this->camper_gender = $row['gender'];
$this->return_camper = $row['return_camper'];
}
}
catch(Exception $e){
return false;
}
}
A constructor in PHP will always return void. This
public function __construct()
{
return FALSE;
}
will not work. Throwing an Exception in the constructor
public function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
would terminate script execution unless you catch it somewhere
try {
$camper = new SummerCamper(1);
} catch(Exception $e) {
$camper = FALSE;
}
You could move the above code into a static method of SummerCamper to create instances of it instead of using the new keyword (which is common in Java I heard)
class SummerCamper
{
protected function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
public static function create($camperId)
{
$camper = FALSE;
try {
$camper = new self($camperId);
} catch(Exception $e) {
// uncomment if you want PHP to raise a Notice about it
// trigger_error($e->getMessage(), E_USER_NOTICE);
}
return $camper;
}
}
This way you could do
$camper = SummerCamper::create(1);
and get FALSE in $camper when the $camper_id does not exist. Since statics are considered harmful, you might want to use a Factory instead.
Another option would be to decouple the database access from the SummerCamper altogether. Basically, SummerCamper is an Entity that should only be concerned about SummerCamper things. If you give it knowledge how to persist itself, you are effectively creating an ActiveRecord or RowDataGateway. You could go with a DataMapper approach:
class SummerCamperMapper
{
public function findById($id)
{
$camper = FALSE;
$data = $this->dbAdapter->query('SELECT id, name FROM campers where ?', $id);
if($data) {
$camper = new SummerCamper($data);
}
return $camper;
}
}
and your Entity
class SummerCamper
{
protected $id;
public function __construct(array $data)
{
$this->id = data['id'];
// other assignments
}
}
DataMapper is somewhat more complicated but it gives you decoupled code which is more maintainable and flexible in the end. Have a look around SO, there is a number of questions on these topics.
To add to the others' answers, keep in mind that you can throw different types of exceptions from a single method and handle them each differently:
try {
$camper = new SummerCamper($camper_id);
} catch (NoRecordsException $e) {
// handle no records
} catch (InvalidDataException $e) {
// handle invalid data
}
Throwing an exception from the constructor is probably the right approach. You can catch this in an appropriate place, and take the necessary action (e.g. display an error page). Since you didn't show any code, it's not clear where you were catching your exception or why that didn't seem to work.
try {
$camper = new SummerCamper($id);
$camper->display();
} catch (NonexistentCamper $ex) {
handleFailure($ex);
}

Categories