Call exit function from child class in PHP - php

I have a PHP class where I have to invoke PHP header function to show a webpage. As pointed out from this post, header should be followed by exit callback. Since this situation is very common in my classes, I've defined a method in parent class:
class ParentClass
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
I would like to invoke this method from children classes:
class ChildClass extends ParentClass
{
public function someFunc()
{
$this->header($some_url);
}
}
PHP documentation says that exit terminates the current script. My question is: does the exit function terminate child script even if it is contained in parent class?
EDIT
In my specific situation, I am using a MVC design pattern and ChildClass is a controller. Inside it, sometimes I need to show a view, sometimes I need to redirect to another url. Let me explain it with a practical example.
Suppose to have a website with a login section. When login page is displayed to users (login data not submitted), login controller should show login view. This view contains a form with an action like action="login.html". When data is submitted, login controller is invoked and checks login data: if login is successful, user is redirected to his admin section.
class UsersController extends BaseController
{
public function login()
{
try
{
if(isset($_POST['submit']))
{
// check login data, throw exception in case of error
// if success, redirect to admin section
$this->header('admin.html');
}
else
{
// show login view
}
}
catch(Exception $e)
{
// show login view (with error)
}
}
}
class BaseController
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
Since this situation is quite common in my controllers, I've preferred to define a header method in BaseController instead of typing everytime
header('Location:someURL.html');
exit;
In my OP, I only wanted to be sure that $this->header('admin.html'); callback would terminate current login method, even if it is defined in BaseController script.
Hope it's a little clearer now.

As already descripted in the comment, exit will terminate everything, i.e. the webpage immediately stops executing, including clean up functions and finally blocks.
So, you should consider using exit very carefully because many things might happen: data doesn't get written to the database when you're not using auto-commit (unless you commit the data before calling exit). Auto-commit is not enabled by default in PHP's MySQL module (as far as I know).
Here is an example:
class B extends A {
public function someFunc() {
# you might wanna use partent instead as
# pointed out by Ding in the comments, but
# maybe someFunc does more that just doing
# the redirect.
$this->header("http://google.com");
}
}
try {
print("Ok...");
$obj = new B();
$obj->someFunc();
print("Nahh..."); # doesn't get called/
} finally {
print("Cleaning up."); # doesn't get called, either.
}
Instead of calling the exit method, you should rather implement a clear MVC design pattern. Here is a very quick example:
<?php
class Response {
# use private here and use appropriate
# getters and setters.
public $status_code = 200;
public $content = "";
public $headers = array();
}
class HomeView extends View {
# called when a get request is made.
public function get() {
$response = new Response();
$response->content = "Hello world."
}
}
class RedirectionView {
public function get() {
$response = new Response();
$response->status_code = 301; # use 302 if moved only temporarily.
$response->headers["Location"] = "http://google.com";
}
}
function processRequest() {
# load appropriate view programatically
$view = new RedirectionView();
$response = $view->get();
http_response_code($response->status_code);
foreach ($response->headers as $headerName => $headerValue) {
header(sprintf("%s: %s", $headerName, $headerValue));
}
print($view->content)
}
?>
Note that this is not really a MVC design pattern (the model is missing and, well, it's not clear what the controller, however, that's what django (a Pyhton framework) uses, too). You might wanna check out PHP MVC frameworks (a quick google search will do the trick) or implement your own (my example might be a good start).
Edit 1:
Using exit is probably ok (but I wouldn't use it because I believe it is bad practice). If you're able to edit the design approach you're using, I'd make your View BaseClass get/post method (or whatever you're the method that returns response object. Note: if you're using prints to show the response to the user, try to add a Response which contains all the data you're request needs to show. This is better than having prints (or echos) everywhere in your code).
Using the response object, your code would then be able to either just set the location header (if it's a redirect) or show the message. So you don't need to "kill" any part of the webpage and it will terminal when the execution ends. But again: you're probably good to go with an exit call in your method (unless you're encountering any issues with your code (database transactions that aren't committed, statistic data that is not updated (because its after the exit statement). Exit will simply terminate your script completely.

Related

Laravel Controller -> API class

I'm struggling with how to do this correctly following best practices. It might be difficult to explain but i'll try my best here.
I have an external API I need to make very many different calls to. So what I did was creating a class in the App folder called Api.php for now. It's using Guzzle for API calls.
In the Controller for the view I create the Api object in the needed functions and call the corresponding function in the API class.
Controller
public function uploadDevice(Request $request)
{
## Validation etc is performed
// Calling the API
$api = new Api();
$api->uploadDevice();
}
Api.php
class Api
{
private $token;
public function __construct(){}
public function checkIfHasToken(){}
public function getTokenFromSession(){}
public function getFreshToken(){}
public function uploadDevice(){}
}
Some questions
The checkIfHasToken() needs to be called before every request. Should it be done in the constructor, first in each function doing API calls or directly from the Controller?
Exceptions : Where should I do the Try/catch etc ? Should it be done in the Api class where it's needed or in the Controller by calling each and every function from the API class and wrapping it in try/catch?
Redirects : I want to redirect back to the Route the request came from with every possible errors or success message included. So if I have a try/catch I want to redirect with the result of the catch included. Where to put this logic? Redirecting from the nested function does not seem to work. So then I'm back to calling each and every function in the Api class from the Controller one by one and handle the exceptions/errors/validations separately in the Controller?
Maybe I'm thinking too much about this or making it more complicated than it needs to be. Not sure anymore.
// Controller
public function __construct(ApiService $apiService)
{
$this->api = $apiService;
}
public function uploadDevice(Request $request)
{
// Ensure that the user has a token in a custom HTTP request or in a middleware somewhere
try {
$this->api->uploadDevice();
}
catch (Exception $exception){
return redirect()->back();
//You can include errors from $exception here.
}
}
// Service
class ApiService
{
public function uploadDevice()
{
return 'I did a thing';
}
}
Explaination
Laravel has many ways to do the same thing, it is all about what you need and how you want your application to scale.
Checking if a token is present or valid should be done in a middleware.
A try catch can be anywhere depending on how much you need to see in the exception, normally just in a controller is ok, but you can
do this in many ways. I personally like to make an event listener
for any http error.
Return redirect back from the controller will be fine to always redirect to the place that invoked the controller
The checkIfHasToken() needs to be called before every request. Should
it be done in the constructor, first in each function doing API calls
or directly from the Controller?
If it needs to be called for every request, I suggest making it middleware as it's made for this purpose.
Exceptions : Where should I do the Try/catch etc ? Should it be done
in the Api class where it's needed or in the Controller by calling
each and every function from the API class and wrapping it in
try/catch?
This depends, if you want to be able to control the output when an exception occurs then you probably want it in your controller. If you can program something to do when the exception occurs (return unsuccessful for instance), do it in a lower level (api).
Redirects : I want to redirect back to the Route the request came from
with every possible errors or success message included. So if I have a
try/catch I want to redirect with the result of the catch included.
Where to put this logic? Redirecting from the nested function does not
seem to work. So then I'm back to calling each and every function in
the Api class from the Controller one by one and handle the
exceptions/errors/validations separately in the Controller?
You can go back by returning redirect()->back() as the response, the best way to show errors would to include them somewhere. I suggest using session()->flash() for this. These calls can be made from the try/catch.

The way of defining class methods without the need of keeping the order of calling them

Hi I have a class which includes two methods.
One of which is to initialize session , another one is for redirect the web page
I ve written the class such that it can be called repeatedly.
$obj->setSession(key,value)->redirect(url);
In this mode , the session is firstly initialized and then it redirects to the next page.
But if it's written like this
$obj->redirect(url)->setSession(key,value);
It just redirects to the defined location qnd the session is not initialized anymore ..
It s cuz when the resirect method is called, the page changes promptly and it causes the second method not to be called ..
Is there any way to be able to call methods repeatedly without the need of considering their order ?
When I usually face that issue, I add a method in the $obj object called render() or done() or something to that effect that checks all the flags I might have defined previously. One of those flags might be a header flag, which is what a redirect method usually does header(Location: $yourDestUrl).
So you end up with something like:
$obj->redirect(url)->setSession(key,value)->render();
When you call a method such as redirect or setSession, put those actions in a "stack" as a property of you class.
Then, when all your methods are called, call a method called exec (for example) that will execute all the actions in the "stack".
Here is a base class based on this idea using magic methods:
class Stack {
private $_stack = array();
public function __call($method, $args) {
// Adding method to stack
$this->_stack[$method] = $args;
return $this;
}
public function __isset($name) {
// Checks if method is in stack
return isset($this->_stack[$name];
}
public function exec() {
// setSession is executed first
if (isset($this->setSession))
call_user_func_array('setSessionMethod', $this->_stack['setSession']);
// redirect is executed second
if (isset($this->redirect))
call_user_func_array('redirectMethod', $this->_stack['redirect']);
}
}
To use this class, you would do:
$stack = new Stack;
$stack->redirect('arg')
->setSession('arg1', 'arg2')
->exec();

How to return false from a PHP script from inside a class/function?

How can I make the main PHP script return false from inside a class or a function?
Why: this is because of the built-in webserver:
If a PHP file is given on the command line when the web server is started it is treated as a "router" script. The script is run at the start of each HTTP request. If this script returns FALSE, then the requested resource is returned as-is.
from the documentation about the PHP Built-in webserver
In other words, you return false in your router script so that the built-in webserver can serve static files. Example from the documentation:
if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
return false; // serve the requested resource as-is.
} else {
echo "<p>Welcome</p>";
}
The thing is that I'm trying to add that behavior to a web framework: I don't want to write that into index.php. I'd rather encapsulate that logic into a class (middleware) that will halt the script's execution if php_sapi_name() == 'cli-server' and a static asset is asked.
However, I don't know how I can make the whole PHP script return false from a class or a function, since obviously return false will return from the current method/function/file and not from the main file.
Is there a way to achieve the same behavior with exit() for example? I realize I don't even know what return false in the main file actually means (is that a specific exit code?).
You should have the router invoke the class method, and then, if the method returns false, you return false from your router file.
Of course it can turn into a headache. There are basically only two methods to achieve what you want to achieve.
There is a faster way though, you can abuse exceptions and create a specialized exception for the case:
StaticFileException.php
<?php
class StaticFileException extends Exception {}
router.php
<?php
try {
$c = new Controller();
return $c->handleRequest();
} catch (StaticFileException $e) {
return false;
}
Once you have this kind of code in place, just throw new StaticFileException and you're done.
If the method in your class handles static assets by using exit then the solution can be as simple as replacing exit with return false and having the caller of that method simply return the method in the global scope as well.
So if your class looks something like this...
class Router
{
public function handleRequest($uri)
{
if (is_file($this->docRoot . $uri->path)) {
exit; // static file found
} else {
// handle as normal route
}
}
}
Just replace exit there with return false ...
return false; // static file found
Then if your index.php works something like this...
$router = new Router($docRoot);
$router->handleRequest($_SERVER['REQUEST_URI']);
Simply add a return infront of the handleRequest method like so....
return $router->handleRequest($_SERVER['REQUEST_URI']);
This should have minimal side-effects on your framework design and as you can see requires very little code and refactoring because returning from the script's global scope only has a single side-effect in PHP (in that it returns the value to calling script i.e. if you used include/require as an expression in an assignment). In your case if index.php is the calling script then you have nothing to worry about here just by adding return infront of that method.
Of course, once you return the rest of the script will not continue so make sure it is the last statement in your index.php. You can even just assign the return value to a temporary value and return later if you needed for logic....
$router = new Router($docRoot);
if ($router->handleRequest($_SERVER['REQUEST_URI'])) {
/* if you need to do anything else here ... */
} else {
return false; // otherwise you can return false for static here
}
In general I would say that calling exit from inside of a function/method is almost never desirable. It makes your class harder to test and debug and really has no upside from the alternatives like throwing an exception, or just returning from the method, and letting the caller handle the failure scenarios gracefully.
Isn't possible to play with register_shutdown_function or with auto_append_file setting to deal with that? Not really nice but maybe that can do the job.

testing error_log with PHPUnit

I have this function I want to test looking like this:
class Logger {
function error($msg){
if (is_string($msg)){
error_log($msg);
die($msg);
} elseif (is_object($msg)){
error_log($msg.' '.$msg->getTraceAsString());
die('exception');
} else {
var_dump($msg);
die('error');
}
}
I want to test this function without logging the $msg. Is there a way to determine if error_log works without logging? I tried using setExpectedException but I wasn't able to catch the error and it kept logging.
The obvious answer is a simple alias/proxy-function that itself called error_log in the Logger class (which can be easily mocked, and checked to see what is set to it),
To actually test the native error_log function however (without a proxy in the original class), can be done with namespaces. The test would end up defined to be the same namespace as the original code, and then after the test class, add a function - in this case error_log() - but that function is also defined in the namespace - and so would be run in preference to the root-namespace-equivalent from the native functions.
Unfortunately, you can't do the same overriding with die (or its alias, exit). They are 'language constructs', and cannot be overridden like error_log can.
<?php
namespace abc;
use abc\Logger;
class ThreeTest extends \PHPUnit_Framework_TestCase
{
public function setUp() { $this->l = new Logger(); }
// test code to exercise 'abc\Logger'
}
// Now define a function, still inside the namespace '\abc'.
public function error_log($msg)
{
// this will be called from abc\Logger::error
// instead of the native error_log() function
echo "ERR: $msg, ";
}
you can use a function-mocking framework like php-mock (there are others as well) to mock the call to error_log (and check whether it is called with your expected parameters).
Unfortunately you will not be able to use that for the die-construct as that is not a normal function but anlanguage construct.
I'd replace the die() with a 'throw new \Exception()' (or any other appropriate exception) as you can then
test for the thrown exception and
can decide in your programming whether execution shall be stopped on calling the logger or whether you want to go on by wrapping the call into a try/catch
But I'd also ask myself whether the execution has to stop when calling a logger
Capturing error_log() output in a variable
If you want to redirect the error_log() output in a way that lets you inspect it with PHPUnit assertions, the following code works for me:
$errorLogTmpfile = tmpfile();
$errorLogLocationBackup = ini_set('error_log', stream_get_meta_data($errorLogTmpfile)['uri']);
error_log("Test for this message");
ini_set('error_log', $errorLogLocationBackup);
$result = stream_get_contents($errorLogTmpfile);
// Result: [11-May-2022 22:27:08 UTC] Test for this message
As you can see, it uses a temporary file to collect the output, then grabs the content into a variable and resets the error_log config.
Re-usable methods
Personally, I've organized this into a pair of methods that I inject into the PHPUnit object with a trait so I can re-use them.
Of course the code below won't work out of the box, but it serves to demonstrate how you can make this system re-usable:
trait WithWPTestCaseGeneralTools {
var $gvErrorLogLocationBackup = "";
var $gvErrorLogTmpfile = "";
public function gvErrorLogStartListening() {
$this->gvErrorLogTmpfile = tmpfile();
$streamUri = stream_get_meta_data($this->gvErrorLogTmpfile)['uri'];
$this->gvErrorLogLocationBackup = ini_set('error_log', $streamUri);
}
public function gvErrorLogGetContents() {
ini_set('error_log', $this->gvErrorLogLocationBackup);
return stream_get_contents($this->gvErrorLogTmpfile);
}
}
You could of course achieve the same things with a couple of functions that use globals, I'll leave that to you if it's what you need!

Where to do the work, inside the __construct() or outside?

I've written a user class based on other supposedly high quality, secure classes I found online (although mixing some of them since, from what I've learned, none was actually that secure). The thing is, inside my script that initializes the different objects, I've got a too long snippet for the $User object. Other objects require as little as $Browser = new Browser(); or $_ = new Translate ($DB, $User->get('Language'));. So, it feels kind of ugly to have all this code suddenly here:
/* USER. Handles user data and login/logout/register. */
$User=new User($DB, Configuration::get('SiteKey'));
if (isset($_POST['logout']))
$User->logout();
else if (isset($_POST['login']) && !$User->login($_POST['email'], $_POST['password'])) // If user tries to login
$Error->set ('Banner', 'Username or password incorrect. Please try again.');
else if (isset($_SESSION['email']) && isset($_SESSION['session']))
$User->loginSession ($_SESSION['email'], $_SESSION['session']);
else if (isset($_POST['register']))
$User->add ($_POST);
/* Language */
if (!empty ($_POST['lang']) && in_array($_POST['lang'],Configuration::get('SupportedLanguages')))
{
$User->set('language', $_POST['lang']);
$_SESSION['language'] = $_POST['lang'];
}
if (!$User->get('language'))
$User->set('language', Configuration::get('DefaultLanguage'));
I'm not sure where all this code is supposed to be, should I put this inside the User's class __constructor() or this is it supposed to be outside as it is now?
It's better to pass the $_POST than using it directly inside, which might affect the answer (a __constructor() with too many variables passed isn't also really good).
I don't think the code within the class is so relevant as to append it here, but if you want to see it I just made it publicly available in my github.
I'm not sure where all this code is supposed to be, should I put this
inside the User's class __constructor() or this is it supposed to be
outside as it is now?
I wouldn't put it in the constructor. Whatever script or class you have this code in looks to perform a number of responsibilities. You could consider following the single responsibility principle and separate out this code into other classes such as an authentication class, a session class and a user factory that can build the user object. A brief and by no means carefully architectured example:
class User
{
}
class Session
{
}
class UserFactory
{
public function create($vars, ...)
{
//build user...
return $user;
}
}
class Authenticate
{
public function __construct($userFactory)
{
//...
}
public function Login($username, $passwd)
{
//do stuff...
return $userFactory->create($vars, ...);
}
public function AuthenticateSession($session)
{
//...
return $userFactory->create($vars, ...);
}
}
a __constructor() with too many variables passed isn't also really
good
Well you can always group variables into a collection object and pass that in the constructor. For example language and time settings could be grouped into a culture object.

Categories