How to exit from a specific function in PHP? - php

I have multiple nested methods inside a PHP class. What I want to do is, based on certain circumstances, I want to exit from NOT JUST the current method, but 2 above it, then the leftover code should continue running. Now the issue with die(), exit() is that they end the full script and I don't want that. I simply want to go a few methods up and continue the script.
Of course, there's the old school method of returning a value in each method and check if it's false for example. But that way I'll have to write tons of additional code if I have like 50 nested methods. Here's what I have right now - it's a very basic usage here, I'm using it in a lot more complicated scenarios (using PHP 7.2.4):
class Sites
{
public function __construct()
{
$this->fn1();
}
public function fn1()
{
$fn2 = $this->fn2();
echo 'I want this to be displayed no matter what!';
}
public function fn2()
{
$fn3 = $this->fn3();
if ($fn3)
{
return true;
}
}
public function fn3()
{
$fn4 = $this->fn4();
if ($fn4)
{
return true;
}
}
public function fn4()
{
$random = rand(1, 100);
if ($random > 50)
{
return true;
}
else
{
// I want to exit/break the scirpt to continue running after
// the $fn2 = $this->fn2() call in the $this->fn1() function.
exit();
echo "This shouldn't be displayed.";
}
}
}
Just as mentioned in the code comments, I want to break the script - if the random number is below 50 and go back to fn1() but continue executing the echo function there.
Is this possible somehow? Please let me know if you need more information and I'll provide.

You can use Exceptions to do this, not particularly elegant, but this should do what your after, replace these methods...
public function fn1()
{
try {
$fn2 = $this->fn2();
}
catch ( Exception $e ) {
}
echo 'I want this to be displayed no matter what!';
}
public function fn4()
{
$random = rand(1, 100);
if ($random > 50)
{
return true;
}
else
{
// I want to exit/break the scirpt to continue running after
// the $fn2 = $this->fn2() call in the $this->fn1() function.
//exit();
throw new Exception();
echo "This shouldn't be displayed.";
}
}

How about regular function call with a flag?
class Sites
{
protected $flag = false;
public function __construct()
{
$this->fn1();
}
public function fn1()
{
if ($this->flag) {
$this->flag = true;
} else {
echo 'I want this to be displayed no matter what!';
$fn2 = $this->fn2();
}
}
public function fn2()
{
$fn3 = $this->fn3();
if ($fn3)
{
return true;
}
}
public function fn3()
{
$fn4 = $this->fn4();
if ($fn4)
{
return true;
}
}
public function fn4()
{
$random = rand(1, 100);
if ($random > 50)
{
return true;
}
else
{
// I want to exit/break the scirpt to continue running after
// the $fn2 = $this->fn2() call in the $this->fn1() function.
//exit();
$this->flag = true;
$this->fn1();
exit();
echo "This shouldn't be displayed.";
}
}
}
$sites = new Sites;
I hope this helps!

Related

How to check returned value to which function it belogns

Say I have to similar function :
public function auth(){
return $someResponse;
}
public function collect(){
return $someOtherResponse
}
Question : When one of the response get passed to another class, is there any way to check which function returned the response ?
In a purely object-oriented way, wanting to attach information to a value is akin to wrapping it into a container possessing context information, such as:
class ValueWithContext {
private $value;
private $context;
public function __construct($value, $context) {
$this->value = $value;
$this->context = $context;
}
public value() {
return $this->value;
}
public context() {
return $this->context;
}
}
You can use it like this:
function auth()
{
return new ValueWithContext($someresponse, "auth");
}
function collect()
{
return new ValueWithContext($someotherrpesonse, "collect");
}
This forces you to be explicit about the context attached to the value, which has the benefit of protecting you from accidental renamings of the functions themselves.
As per my comment, using arrays in the return will give you a viable solution to this.
It will allow a way to see what has been done;
function auth()
{
return (array("auth" => $someresponse));
}
function collect()
{
return (array("collect" => $someotherrpesonse));
}
class myClass
{
function doSomething($type)
{
if (function_exists($type))
{
$result = $type();
if (isset($result['auth']))
{
// Auth Used
$auth_result = $result['auth'];
}
else if (isset($result['collect']))
{
// Collect used
$collect_result = $result['collect'];
}
}
}
}
It can also give you a way to fail by having a return array("fail" => "fail reason")
As comments say also, you can just check based on function name;
class myClass
{
function doSomething($type)
{
switch ($type)
{
case "auth" :
{
$result = auth();
break;
}
case "collect" :
{
$result = collect();
break;
}
default :
{
// Some error occurred?
}
}
}
}
Either way works and is perfectly valid!
Letting the two user defined functions auth() & collect() call a common function which makes a call to debug_backtrace() function should do the trick.
function setBackTrace(){
$backTraceData = debug_backtrace();
$traceObject = array_reduce($backTraceData, function ($str, $val2) {
if (trim($str) === "") {
return $val2['function'];
}
return $str . " -> " . $val2['function'];
});
return $traceObject;
}
function getfunctionDo1(){
return setBackTrace();
}
function getfunctionDo2(){
return setBackTrace();
}
class DoSomething {
static function callfunctionTodo($type){
return (($type === 1) ? getfunctionDo1() : getfunctionDo2());
}
}
echo DoSomething::callfunctionTodo(1);
echo "<br/>";
echo DoSomething::callfunctionTodo(2);
/*Output
setBackTrace -> getfunctionDo1 -> callfunctionTodo
setBackTrace -> getfunctionDo2 -> callfunctionTodo
*/
The above function would output the which function returned the response

Serializable objects disappearing from session, but not on localhost

I have a shopping cart object that implements serializable, and it works fine on my localhost, but when I upload it to the live server it stops working correctly.
When I add a product to the shopping cart and print_r($_SESSION) I can see the products have been added successfully, but when I refresh the page they disappear. This only happens with objects that implement serializable. These same objects work perfectly on my localhost, which has IDENTICAL code, and it's driving me insane.
Could it be a difference in session handling between php version 5.5 and 5.6?
I am using spl_autoload_register to load my classes if that helps at all (none of them appear in the session as incomplete_class or anything like that).
I also noticed that the session save path is blank on the live version, but is set on my localhost - but the rest of the session functions perfectly - it's only when it contains serializable classes that everything disappears.
Please help before I murder someone...
OK here's the code:
shopping cart class:
class shoppingCart implements Serializable
{
private $products, $error;
function __construct()
{
}
function addProduct($productCode, $quantity = 1)
{
include_once ('include/requests.php');
$productInfo = requestPostData('getProductList');
if (in_array(strtoupper($productCode), $productInfo['results']) == true)
{
if (isset($this->products[strtoupper($productCode)]))
{
$this->products[strtoupper($productCode)] = $this->products[strtoupper($productCode)] + $quantity;
return true;
}
else
{
$this->products[strtoupper($productCode)] = $quantity;
return true;
}
}
else
{
$this->error = 'Product '.$productCode.' could not be found.';
}
}
function editProduct($productCode, $quantity)
{
if (isset($this->products[strtoupper($productCode)]))
{
$this->products[strtoupper($productCode)] = $quantity;
}
}
function removeProduct($productCode)
{
if (isset($this->products[strtoupper($productCode)]))
{
unset($this->products[strtoupper($productCode)]);
}
}
public function getProducts()
{
if (count($this->products) >= 1)
{
return $this->products;
}
else
{
return false;
}
}
public function isInCart($productCode)
{
if (isset($this->products[strtoupper($productCode)]))
{
return true;
}
else
{
return false;
}
}
public function getQuantity($productCode)
{
if (isset($this->products[strtoupper($productCode)]))
{
return $this->products[strtoupper($productCode)];
}
else
{
return 0;
}
}
public function getError()
{
$error = $this->error;
return $error;
}
public function resetError()
{
$this->error = '';
}
public function serialize()
{
$dataArray = array('products' => $this->products, 'error' => $this->error);
return serialize($dataArray);
}
public function unserialize($data)
{
$dataArray = unserialize($data);
$this->products = $dataArray['products'];
$this->error = $dataArray['error'];
}
}
And the code on the page:
if (session_status() !== PHP_SESSION_ACTIVE)
{
session_start();
}
if (!isset($_SESSION['shoppingCart']))
{
$_SESSION['shoppingCart'] = new shoppingCart;
}
if (isset($_POST['cart__add_to_cart']))
{
if (isset($_POST['cart__quantity']))
{
if ($_SESSION['shoppingCart']->addProduct($_POST['cart__add_to_cart'], $_POST['cart__quantity']))
{
header('Location: http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
}
}
else
{
if ($_SESSION['shoppingCart']->addProduct($_POST['cart__add_to_cart']))
{
header('Location: http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
}
}
}
If I print_r($_SESSION) right at the bottom of the page after a product has been added then it appears in the session as it should... But as soon as I refresh it disappears :'(

How to pass custom error messages from Model to Controller Laravel

Ok so I currently have this Controller which basically retrieves a Model and does some calculation.
Original code has tonnes of calculation but this is a trimmed down version for simplicity.
I wanted to move all logic to the Model and have built the code below it so far but can not figure out how to pass the custom messages to the Controller.
I am a beginner in Laravel so trying to achieve this in an easy to understand way so I can maintain it and managed to get the code below working but without custom error messages being passed onto the Controller.
Can you give me an example code of how you are passing custom error messages to controller
This is the original code in controller.
Controller
public function getDetail()
{
$request = Model::where('id','=',8)->first();
if($request)
{
if($request->number >= 5)
{
return Redirect::back()->withMessage('You have 5 or more');
}
if($request->number > 0 && $request->number < 5)
{
return Redirect::back()->withMessage('You have between 1 and 4');
}
if($request->number <= 0)
{
return Redirect::back()->withErrors('You do not have enough points');
}
}
else
{
return Redirect::back()->withErrors('No details found');
}
}
This is the new code I tried to build to move logic to model but could not figure out how to pass the custom error messages along?
Model
Class Profile
{
private $model
function __construct()
{
$this->model = Model::where('id','=',8)->first();
}
public function Notification()
{
if($this->model->number >=5)
{
return true;
}
if($this->model->number > 0 && $this->model->number < 5)
{
return true;
}
if($this->model->number <=0)
{
return false;
}
}
}
Controller
public function getDetail()
{
$request = new Profile;
$result = $request->Notification();
if($result)
{
return Redirect::back()->withMessage(????????);
}
else
{
return Redirect::back()->withErrors(????????);
}
}
Just return the message from the Model function and use it in the controller to return like shown below.
Model function
public function Notification()
{
$returnArray = array();
if($this->model->number >=5)
{
$returnArray['isMessage'] = true;
$returnArray['message'] = "You have 5 or more";
}
if($this->model->number > 0 && $this->model->number < 5)
{
$returnArray['isMessage'] = true;
$returnArray['message'] = "You have between 1 and 4";
}
if($this->model->number <=0)
{
$returnArray['isError'] = true;
$returnArray['error'] = "You do not have enough points";
}
return $returnArray;
}
Controller function
public function getDetail()
{
$request = new Profile;
$result = $request->Notification();
if(isset($result['isMessage']) && $result['isMessage'] == true)
{
return Redirect::back()->withMessage($result['message']);
}
else if (isset($result['isError']) && $result['isError'] == true)
{
return Redirect::back()->withErrors($result['error']);
}
}
Ideally speaking you should not create an object of Model in controller. You can just create the function as a static method inside model and call it from controller.

Running class methods in cascade

I have a system that was designed to do a kind of cascading - get the sequence of methods called upon success of the previous condition.
The example is the below code, which I presume it's not a best practice for doing this, so would be great if I could get some suggestions to refactor this, probably using a design pattern or a different than this system.
<?php
class Model
{
public function isOk()
{
return true;
}
}
class OtherClass
{
public function isOk()
{
return true;
}
}
class AnotherClass
{
public function verifies()
{
return true;
}
}
class Sequence
{
public function fire()
{
$model = new Model();
if($model->isOk()) {
$otherclass = new OtherClass();
if($otherclass->isOk()) {
$anotherclass = new AnotherClass();
if($anotherclass->verifies()) {
echo "We're done with the sequence.";
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
}
$sequence = new Sequence();
echo $sequence->fire();
?>
I would avoid deep nesting of if/else statements to enhance the readability. One way is to use early return:
class Test1
{
public function isOk()
{
echo 'Test1';
return true;
}
}
class Test2
{
public function isOk()
{
echo 'Test2';
return true;
}
}
class Sequence
{
public function fire()
{
$test1 = new Test1();
if (!$test1->isOk()) {
return false;
}
$test2 = new Test2();
if (!$test2->isOk()) {
return false;
}
echo "We're done with the sequence.";
return true;
}
}
If you need it more dynamically you could use call_user_func or call_user_func_array.
class Sequence
{
protected $sequence = array(
array('Test1', 'isOk'),
array('Test2', 'isOk'),
);
public function fire()
{
foreach ($this->sequence as $callback) {
if (!call_user_func(array(new $callback[0], $callback[1]))) {
return false;
}
}
echo "We're done with the sequence.";
return true;
}
}

PHP Bi-Directional map

I'm porting to PHP a piece of Java code that uses a lot of Bi-directional maps (Guava's BiMap). Java-like maps are provided by PHP arrays or SplObjectStorage, but is there a library PHP Bi-Directional map available?
This class should provide for most needs of a bi-directional map :
class BiMap
{
private $KtoV, $VtoK;
public function __constructor()
{
$this->KtoV = []; // for version < 5.4.0, syntax must be: $this->KtoV = array();
$this->VtoK = [];
}
public function getKey($v)
{
if($this->hasValue($v))
{
return $this->VtoK[$v];
}
else
{
return null;
}
}
public function getAllKeys()
{
if($this->KtoV)
{
return array_keys($this->KtoV);
}
else
{
return $this->KtoV;
}
}
public function getValue($k)
{
if($this->hasKey($k))
{
return $this->KtoV[$k];
}
else
{
return null;
}
}
public function getAllValues()
{
if($this->VtoK)
{
return array_keys($this->VtoK);
}
else
{
return $this->VtoK;
}
}
public function hasKey($k)
{
return isset($this->KtoV[$k]);
}
public function hasValue($v)
{
return isset($this->VtoK[$v]);
}
public function put($k, $v)
{
if($this->hasKey($k))
{
$this->removeKey($k);
}
if($this->hasValue($v))
{
$this->removeValue($v);
}
$this->KtoV[$k] = $v;
$this->VtoK[$v] = $k;
}
public function putAll($array)
{
foreach($array as $k => $v)
{
$this->put($k, $v);
}
}
public function removeKey($k)
{
if($this->hasKey($k))
{
unset($this->VtoK[$this->KtoV[$k]]);
$v = $this->KtoV[$k];
unset($this->KtoV[$k]);
return $v;
}
else
{
return null;
}
}
public function removeValue($v)
{
if($this->hasValue($v))
{
unset($this->KtoV[$this->VtoK[$v]]);
$k = $this->VtoK[$v];
unset($this->VtoK[$v]);
return $k;
}
else
{
return null;
}
}
}
However, if you require null checking for key/values and/or object/array checking then handling similar to the following lines of code should be given in the body of a function and called appropriately within the hasKey($k), hasValue($v) and put($k, $v) methods :
if($item === null)
{
throw new Exception('null as BiMap key / value is invalid.');
}
if(is_object($item) || is_array($item))
{
throw new Exception('Object / Array as BiMap key / value is invalid.');
}
I did that once putting the values into 2 arrays. If keySet() and valueSet() are disjunct you can even use one value. Example:
$mapKtoV = array();
$mapVtoK = array();
function putInMap ($key,$value)
{
$mapKtoV[$key] = $value;
$mapVtoK[$value] = $key;
}
Of course you can also put them into a class.
Do you also think that this solution appears dodgy and smells? Yes, true, welcome to the world of PHP, which is usually dominated by bad code design. If you are really looking for a good solution, you should actually port you source from PHP to Java ;)
Hope it helps.

Categories