Jump to content

Critique script


NotionCommotion

Recommended Posts

I've used some libraries before and there always seems to be some magic under the hood.  My intent of this project was not to build a better framework than others out there (it isn't!), but to understand each line of code (I mostly do!).

 

I hope I am not asking too much, but would like a high level critique of my attempt to put together various concepts.  I am by no means expecting a line by line review, but feedback that I am basically "doing it right".  If there is also any constructive criticism you would like to give regarding any of the details, I will surely accept that as well.

 

A few specific topics I would like to here your opinions on are:

  1. My use of interfaces.
  2. Namespace and autoloading.
  3. Dependency injection and the use of pimple.  I may need to get rid of pimple and implement this scope myself to ensure I truly understand dependency injection.
  4. Whether I am applying the controller concept correctly.  I still question whether a controller should be passed to a router or the reverse or neither.
  5. My use of the request and response classes.  I might be confused between a response and a view.
  6. My approach to routes.
  7. How I deal with error handling.  I think this portion surely could be improved.
  8. My approach to authentication.
  9. Whether there are any common programming concepts which should be used in an application such as this one but are not being used.

 

Thank you in advance for any comments you are willing to give.

<?php
//namespace Michael\SimpleRouter;
use Michael\SimpleRouter;
use Michael\SimpleRouter\Request;
use Michael\SimpleRouter\Response;
use Michael\EmbeddedWebserver\Controllers;


session_start();
error_reporting(E_ALL);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);


spl_autoload_register(function ($class) {
    $prefix = 'Michael\\';
    $base_dir = __DIR__ . '/../src/';
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }
    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});


// TBD whether default routes should be added by extending Pimple or in router (which is defined after user defined routes are set so much check them)
// Should I even bother using pimple?
require_once('../src/SimpleRouter/Pimple.php');
$c = new \Pimple();
//$c = new Container();


//Originally, I was planning on passing the view to pimple, but why bother?
//$c['view'] = function ($c) {return new SimpleRouter\View(__DIR__.'/../src/EmbeddedWebserver/templates');};
$c['template']=__DIR__.'/../src/EmbeddedWebserver/templates';


//Authority not sent to constructor thus use default 0 required authority
$router = new SimpleRouter\Router($c);


$auth=(new Controllers\Logon)->getAuthorityLevel();
$router->setAuthUser($auth);


//Routes are regex.  # Deliminator to be applied by router and # are not allowed in regex pattern.
$router->get('^/logon$', function(Request $request, Response $response){
    return($response->html('logon.html',[]));
});
$router->post('^/logon$', function(Request $request, Response $response){
    $controller=new Controllers\Logon();
    if($controller->validCredentials()) {
        $controller->setAuthorityLevel(1);
        return $response
        ->html('page.html',(new Controllers\Page)->getData($request))
        ->redirect('/page1');
    }
    else {
        return($response->html('logon.html',[]));
    }
});


$router->setAuthRequired(1,'/logon');    //subqential routes require authority value of 1


$router->delete('^/logon$', function(Request $request, Response $response){
    (new Controllers\Logon)->setAuthorityLevel(0);
    return($response->json(['success']));
});


$router->get('^/$|^/page1$', function(Request $request, Response $response){    //Both / and /page1
    return($response->html('page.html',array_merge((new Controllers\Page)->getData($request),['page'=>'Page 1'])));
});
$router->get('^/page2$', function(Request $request, Response $response){
    return($response->html('page.html',array_merge((new Controllers\Page)->getData($request),['page'=>'Page 2'])));
});
$router->get('^/page3$', function(Request $request, Response $response){
    return($response->html('page.html',array_merge((new Controllers\Page)->getData($request),['page'=>'Page 3'])));
});


$router->all('^/test$', function(Request $request, Response $response){
    return($response->json((new Controllers\Page)->getData($request)));
});
$router->all('^/test/(\w+)$', function(Request $request, Response $response){
    return($response->json((new Controllers\Page)->getData($request)));
});
$router->all('^/test/(\w+)/(\d+)$', function(Request $request, Response $response){
    return($response->json((new Controllers\Page)->getData($request)));
});


echo $router->run(); //Headers will be set in View if appropriate.
<?php
namespace Michael\SimpleRouter;
interface RouterInterface
{
    // Should _constructor be included? public function __construct($c, array $auth=[]);


    //Add callback for given HTTP method (all adds for each).  $auth overrides the required authority for the given route
    public function get($pattern, $callback, array $auth=[]);
    public function post($pattern, $callback, array $auth=[]);
    public function put($pattern, $callback, array $auth=[]);
    public function delete($pattern, $callback, array $auth=[]);
    public function all($pattern, $callback, array $auth=[]);


    //Updates the default authority
    public function setAuthRequired($authRequiredLevel,$unauthUrl,$unauthMethod=NULL);
    //Gets authority values
    public function getAuthRequired();


    //Sets users authority
    public function setAuthUser($auth);
    //Gets users authority
    public function getAuthUser();


    //Execute the route, set any headers, and return the results
    public function run();


}
<?php
namespace Michael\SimpleRouter;
interface RequestInterface
{
    public function getMethod();
    public function getUri();
    //Returns an unassociated array in the same order as the pattern
    public function getParams();
    //Return $_GET, $_POST, or PUT/DELETE data
    public function getData();
    //public function isAjax(); //Currently private
}
<?php
namespace Michael\SimpleRouter;
interface ResponseInterface
{
    //Should this class really have view functionallity or should view be injected?
    //All methods except for render() and error() return $this to allow chaining


    // Redirect browser
    public function redirect($location);
    // Create HTML or JSON
    public function html($template, $arr, $code=200);
    public function json($data, $code=200);
    // Display error in HTML or JSON as appropriate
    public function error($msg,$code);
    // Return created HTML or JSON
    public function render();
}
<?php
namespace Michael\SimpleRouter;
class Router implements RouterInterface {


    private $routes     =   ['GET'=>[],'POST'=>[],'PUT'=>[],'DELETE'=>[]];
    private $uri;
    private $method;
    private $lastEndpoint=[false,false];    //Error if repeating recursive endpoint
    private $authValue=0;           //The users authority
    private $authRequiredLevel=0;   //0 is unrestricted, and incrementing higher values require higher authority
    private $unauthUrl='/';         //URL to utilize if unathorized access level
    private $unauthMethod='GET';
    public $container  =   [];


    public function __construct($c, array $auth=[])
    {
        $this->container=$c;


        //remove query (duplicated in $_GET), index.php (just case), and then trailing backslash
        $this->uri = preg_replace('/index.php$/', '', explode('?', strtolower($_SERVER['REQUEST_URI']))[0]);
        if(strlen($this->uri)>1)  $this->uri=rtrim($this->uri,'/'); //uri will be: /foo/bar


        $this->method=$_SERVER['REQUEST_METHOD'];
        syslog(LOG_INFO,"Router Constructor: $this->uri $this->method");


        //$auth defines the default authority requirements and default fallback page
        if(isset($auth['authRequiredLevel'])) $this->authRequiredLevel=$auth['authRequiredLevel'];
        if(isset($auth['unauthUrl'])) $this->unauthUrl=$auth['unauthUrl'];
        if(isset($auth['unauthMethod'])) $this->unauthMethod=strtoupper($auth['unauthMethod']);
        if(isset($auth['authValue'])) $this->authValue=$auth['authValue'];   //Users authority level


        set_exception_handler(function($exception) use ($c){
            //Nothing to return to, so echo results?
            exit($c['phpErrorHandler'](new Request(), new Response($this->container['template']), $exception));
        });
        set_error_handler(function($errno, $errmsg, $filename, $linenum) {
            if (!(error_reporting() & $errno)) {
                return;
            }
            throw new \ErrorException($errmsg, 0, $errno, $filename, $linenum);
        });


        //Add default services.  TBD whether it would be more appopriate to extend Pimple to add these.  Is this the correct way to do this?
        $this->addToContainer('missingErrorHandler',function($c){
            return function ($request, $response) {
                return $response->error($request->getUri().' with method '.$request->getMethod().' does not exist on this server.',404);
            };
        });
        $this->addToContainer('phpErrorHandler',function($c){
            return function ($request, $response, $e) {
                return $response->error($e->getFile()." (".$e->getline().'): '.$e->getMessage(),400);
            };
        });
    }


    //Add to container with default option not to override (used by route constructor to add default services).
    private function addToContainer($name, $callback, $override=false) {
        if($override || empty($this->container[$name])) {
            $this->container[$name] = $callback;
        }
    }


    /* Add route pattern, callback, and auth for each method.
    Auth is an array specifing the required user authority to access the route and the url and http method to respond with if not authorized
    */
    public function get($pattern, $callback, array $auth=[]) {
        $this->setRoute('GET', $pattern, $callback, $auth);
    }
    public function post($pattern, $callback, array $auth=[]) {
        $this->setRoute('POST', $pattern, $callback, $auth);
    }
    public function put($pattern, $callback, array $auth=[]) {
        $this->setRoute('PUT', $pattern, $callback, $auth);
    }
    public function delete($pattern, $callback, array $auth=[]) {
        $this->setRoute('DELETE', $pattern, $callback, $auth);
    }
    public function all($pattern, $callback, array $auth=[]) {
        $this->setRoute(['GET','POST','PUT','DELETE'], $pattern, $callback, $auth);
    }
    private function setRoute($methods, $pattern, $callback, $auth) {
        if(strpos($pattern, '#')!==FALSE) {
            //trigger_error('"#" are not allowed in route pattern regex.');
            throw new \ErrorException('"#" are not allowed in route pattern regex.');
        }
        $authRequiredLevel=isset($auth['authRequiredLevel'])?$auth['authRequiredLevel']:$this->authRequiredLevel;
        $unauthUrl=isset($auth['unauthUrl'])?$auth['unauthUrl']:$this->unauthUrl;
        $unauthMethod=isset($auth['unauthMethod'])?$auth['unauthMethod']:$this->unauthMethod;
        foreach((array)$methods as $method) {
            $this->routes[$method][$pattern] = ['callback'=>$callback,'authRequiredLevel'=>$authRequiredLevel,'unauthUrl'=>$unauthUrl,'unauthMethod'=>$unauthMethod];
        }
    }


    /* USER ACCESS
    All routes have a given authority integer which is required to access.
    setAuthRequired() or in the constructor will set the required authority (default of 0) as well as the url and http method if user isn't authorized.
    After being set, it will be applied to all routes created after being setting.
    Users also have a given integer authority level which must be equal or greater than the route authority
    */
    public function setAuthRequired($authRequiredLevel,$unauthUrl,$unauthMethod=NULL) {
        if($authRequiredLevel)$this->authRequiredLevel=$authRequiredLevel;
        if($unauthUrl)$this->unauthUrl=$unauthUrl;
        if($unauthMethod)$this->unauthMethod=strtoupper($unauthMethod);
    }
    public function getAuthRequired() {
        return ['authRequiredLevel'=>$this->authRequiredLevel,'unauthUrl'=>$this->unauthUrl,'unauthMethod'=>$this->unauthMethod];
    }
    public function setAuthUser($auth) {
        $this->authValue=$auth;
    }
    public function getAuthUser() {
        return $this->authValue;
    }


    public function run() {
        if($this->lastEndpoint[0] && $this->lastEndpoint[0]===$this->uri && $this->lastEndpoint[1]===$this->method) {
            throw \ErrorException("Recursive call to same endpoint");
        }
        foreach ($this->routes[$this->method] as $pattern => $settings) {
            $pattern="#$pattern#";
            if (preg_match($pattern, $this->uri, $params) === 1) {
                syslog(LOG_INFO,"Router Run Match: $params[0] $this->method");
                if($this->authValue<$settings['authRequiredLevel']) {
                    $this->lastEndpoint=[$this->uri,$this->method];
                    $this->uri=$settings['unauthUrl'];
                    $this->method=$settings['unauthMethod'];
                    syslog(LOG_INFO,"Unauthorized.  Instead do: $this->method $this->uri");
                    return $this->run();
                }
                return call_user_func($settings['callback'], new Request($params), new Response($this->container['template']))->render();
            }
        }
        return $this->container['missingErrorHandler'](new Request(), new Response($this->container['template']));
    }


}
<?php
namespace Michael\SimpleRouter;
class Request implements RequestInterface {


    private $params  =   [];
    private $data=false;
    private $uri, $method;


    public function __construct(array $params=null)
    {
        if(is_null($params)) $params=[$_SERVER['REQUEST_URI']];
        $this->uri=$params[0];
        array_shift($params);
        $this->params=array_values($params);
        $this->method=$_SERVER['REQUEST_METHOD'];
    }


    public function getMethod() {
        return $this->method;
    }
    public function getUri() {
        return $this->uri;
    }
    public function getParams() {
        return $this->params;
    }
    public function getData() {
        if ($this->data!==false) return $this->data;
        switch($this->method){
            case 'GET':$this->data=$_GET;break;
            case 'POST':$this->data=$_POST;break;
            case 'PUT':
            case 'DELETE':  //Can a delete method have data?  Is it the same as $_GET or PUT?
                parse_str(file_get_contents("php://input"),$this->data);
                /*
                //Or do it per http://php.net/manual/en/features.file-upload.put-method.php?
                $putdata = fopen("php://input", "r");
                // Read the data 1 KB at a time and write to a stream
                while ($data = fread($putdata, 1024)) {
                fwrite($fp, $data);
                }
                fclose($fp);
                */
                break;
        }
        return $this->data;
    }
}
<?php
namespace Michael\SimpleRouter;
class Response implements ResponseInterface {
    private $folder;
    private $buffer=null;


    public function __construct($folder)
    {
        $this->folder=$folder;
    }


    public function render() {
        return $this->buffer;
    }


    public function html($template, $arr, $code=200) {
        if($code!==200) http_response_code($code);
        $template=file_get_contents("$this->folder/$template");
        $this->buffer=$arr?$this->substitute($template,$arr):$template;
        //or $this->buffer=$this->parse($template,$arr);
        return $this;
    }


    public function json($data, $code=200) {
        header('Content-Type: application/json');
        http_response_code($code);
        $this->buffer=$data?json_encode($data):null;
        return $this;
    }


    public function error($msg,$code)
    {
        return $this->isAjax()
        ?$this->json(['error'=>$msg],404)->render()
        :$this->html('error.html',['errorMessage'=>$msg],404)->render();
    }


    public function redirect($location)
    {
        header("Location: $location");
        return $this;
    }


    private function isAjax()
    {
        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
    }


    private function substitute($template, $values, $deliminator='{{}}',$preface=null) {
        $length=strlen($deliminator)/2;
        $open  = substr($deliminator, 0, $length);
        $close = substr($deliminator, $length);
        foreach ($values as $key => $data) {
            if(is_array($data)){
                $template=$this->substitute($template,$data,$deliminator,$key.'.');
            }
            else {
                $template = str_replace($open.$preface.$key.$close, $data, $template);
            }
        }
        return $template;
    }
    private function parse($template, $values) {
        ob_start();
        require("$this->folder/$template.html");
        $template = ob_get_contents();
        ob_end_clean();
        return $template;
    }


}
<?php
namespace Michael\EmbeddedWebserver\Controllers;
class Logon implements LogonInterface {


    public function validCredentials()
    {
        return isset($_POST['username'],$_POST['password']) && $_POST['username']=='michael' && $_POST['password']=='mypassword';
    }


    public function setAuthorityLevel($auth)
    {
        $_SESSION['authorized']=$auth;
    }


    public function getAuthorityLevel()
    {
        return isset($_SESSION['authorized'])?$_SESSION['authorized']:0;
    }
}
<?php
namespace Michael\EmbeddedWebserver\Controllers;
class Page implements PageInterface {


    public function getData($request)
    {
        return [
            'method'=>$request->getMethod(),
            'uri'=>$request->getUri(),
            'params'=>json_encode($request->getParams()),
            'data'=>json_encode($request->getData()),
        ];
    }
}

 

Link to comment
Share on other sites

- Don't put Pimple in src/SimpleRouter/Pimple.php. That implies the name of the class in there is Michael\SimpleRouter\Pimple. What's more, Pimple is third-party. Either put it in just src/ or better yet use a vendor/ directory (which is typically for Composer but this is fine too).

- I don't know what templates are but unless they are classes in the Michael\EmbeddedWebserver namespace they should be located somewhere else. Keep your classes directory just for classes and the *rare* necessary non-class complementary file (eg, a data file used by a particular class).

- If you're using interfaces then use the interfaces. Right now they're not really doing anything and all your code depends on the concrete implementations.

 

> Should _constructor be included?

Generally the only time you put a constructor in an interface is when you have a factory somewhere that requires a constructor with a certain signature.

 

There are some other things I'll try to remember to comment on after I've gotten some sleep.

Link to comment
Share on other sites

Thanks requinix,

 

  1. 3rd party in vendor/ directory.  Good point.
  2. Templates.  I have various controller classes in \var\www\EmbeddedWebserver\Controllers\ and templates in \var\www\EmbeddedWebserver\templates\.  The templates are HTML files which PHP parses and replaces {{placeholders}} with PHP variables.  Maybe I shouldn't have used "Controllers" but used "Classes"?  I my use of this templates directory wrong?
  3. Interfaces.  Totally agree that they are providing very little value how I am using them.  I will study up on the subject.
  4. Constructors in interfaces. Thanks
 

If after you get some good sleep and remember more, please let me know!

 

Thanks

Link to comment
Share on other sites

Like I said, src/ (or whatever) should be just for PHP classes. Templates should go in a different location. Like templates/.

 

- Don't put error and exception handling in a constructor if you aren't using a singleton. The point is that your code allows for multiple Router classes, and each one will replace the two handlers from the one before. The handlers should be global, and since they're error handlers should be set up as early as possible, without depending on the Router being created. I would restructure it so that the handlers are installed in your setup/bootstrap/whatever script, somehow using the right Router or using a new one or I don't know. You could move to a error/exception management class and have the Router add hooks/listeners/whatever you want to call them so that it's notified of an error/exception, but I'm not sure about that.

- Personally I would allow # but escape it. It probably won't ever be used anyways though.

- PHP 7? Move the less important syslog messages into an assert so that they can all be enabled or disabled at will, and when disabled won't impact runtime.

- Either Router or Request should determine the current request type, not both. Code duplication and separation of concerns.

- Remember that POST requests can still have $_GET data. In fact any request can.

- IMO the presence of a X-Requested-With is enough to imply AJAX, or at least an AJAX-y request, regardless of the value.

- Are you sure Page should JSON-encode the params and data? To a string? Won't it have to be decoded later?

 

> Can a delete method have data?

- The standards don't say anything so yes, but there probably won't be.

- file_get_contents(php://input) is fine.

- Do not parse_str() anything unless you know it's in the appropriate format - look at the incoming Content-Type header ("application/x-www-form-urlencoded"), which may not be present at all (if there's no request body). You could also automatically handle application/json, but otherwise leave it as a string blob.

Link to comment
Share on other sites

Thanks requinix,

 

For the errors, I see your point, but I don't know how best to handle it.  Maybe something like the following?



<?php
//bootstraper..
spl_autoload_register(function ($class) {/*..*/});

$c=new MyContainer(); 
$c['phpErrorHandler'] = function ($c) {
    return function ($request, $response, $error) use ($c) {
        /* custom error handler */
    };
};


<?php
require_once('../Vendor/Pimple.php');
class MyContainer extends Pimple {

    public function __construct(array $values = [])
    {
        //parent::__constructor($values);
        $this['phpErrorHandler'] = function ($c) {
            return function ($request, $response, $error) use ($c) {
                /* standard error handler */
            };
        };
        parent::__constructor($values);
    }
}


Never new about asserts until now.  I mostly will be using PHP 7, but why is that relevent relevent?  Looks like they have two purposes.  To enunciate some test script as well the purpose you described to allow various debugging script to be globally disabled.



assert(syslog(LOG_INFO,"Some logging message which can globally be disabled..."));


Yes, I now remember that POST requests can still have $_GET data.  Need to be reminded every now and then.

 

Can you elaborate a little about not using parse_str() unless the appropriate format is known and automatically handling application/json?

Link to comment
Share on other sites

For the errors, I see your point, but I don't know how best to handle it.  Maybe something like the following?

Normally I handle it by having a method in the router specifically for errors. I think that's more or less what you're doing.

 

I mostly will be using PHP 7, but why is that relevent relevent?

PHP 7 has zero-cost assertions, meaning you can have them enabled in development to get the errors and disabled at no impact to performance in production. In PHP 5 assert() was always a function call even if assert options were set to disable evaluation, meaning a bit of overhead every time.

 

But anyways, assertions in programming (not just PHP) are for things like sanity checks and other "this should always be true" conditions. For example,

function sqrt_of_half($number) {
	assert($number >= 0);
	return sqrt($number / 2);
}
Here I, as the developer, decided that calling the function with a negative $number is incorrect usage of the function. The assert() will be active in development in order to catch bugs elsewhere, but disabled in production to increase performance.

(Personally I believe assertions should be enabled in production too, in a non-fatal way, but that's besides the point.)

 

Can you elaborate a little about not using parse_str() unless the appropriate format is known and automatically handling application/json?

parse_str() is specifically for handling strings formatted in a certain way, known as application/x-www-form-urlencoded:

key1=value1&key2=value2+with+spaces&key3=always+give+110%25
If you use it with something else then it won't work properly, so make sure you're only calling it when you should be: when the content has been encoded that way. (There's also multipart/form-encoding which you might recognize from file uploads with PHP.)

 

So you need to look at the Content-Encoding header, if present (it should be), to decide how to handle the input. If it's application/x-www-form-urlencoded then you can parse_str() the body, but if not then you have to decide what else to do.

Like

if (isset($_SERVER["CONTENT_LENGTH"])) {
	$rawbody = file_get_contents("php://input");

	switch (strtolower($_SERVER["CONTENT_ENCODING"] ?? "")) {
		case "application/x-www-form-urlencoded":
			parse_str($rawbody, $body);
			break;

		case "application/json":
		case "text/json":
			$body = @json_decode($rawbody);
			break;

		default:
			$body = $rawbody;
	}
All that said, try to rely on $_POST when at all possible. This is one of the few cases where I would consider backfilling $_POST and/or $_FILES (for non-POST requests).
Link to comment
Share on other sites

- PHP 7? Move the less important syslog messages into an assert so that they can all be enabled or disabled at will, and when disabled won't impact runtime.

 

PHP 7 has zero-cost assertions, meaning you can have them enabled in development to get the errors and disabled at no impact to performance in production. In PHP 5 assert() was always a function call even if assert options were set to disable evaluation, meaning a bit of overhead every time.

 

But anyways, assertions in programming (not just PHP) are for things like sanity checks and other "this should always be true" conditions. For example,

function sqrt_of_half($number) {
	assert($number >= 0);
	return sqrt($number / 2);
}
Here I, as the developer, decided that calling the function with a negative $number is incorrect usage of the function. The assert() will be active in development in order to catch bugs elsewhere, but disabled in production to increase performance.

(Personally I believe assertions should be enabled in production too, in a non-fatal way, but that's besides the point.)

 

 

As I understood, asserts can be used as shown on your second example, but your first post was a means to easily globally enable/disable syslogs.  But no matter what options are used, the second syslog is always skipped.  Am I mistaken?  Were you implying to zend.assertions?

EDIT.  Ah, I think you were!

<?php
openlog('Public API', LOG_CONS | LOG_NDELAY | LOG_PID, LOG_USER | LOG_PERROR);


assert_options(ASSERT_ACTIVE,   true);
assert_options(ASSERT_QUIET_EVAL,  false);
syslog(LOG_INFO,'outside assert');
assert(syslog(LOG_INFO,'within assert'));

php.ini

[Assertion]
; Switch whether to compile assertions at all (to have no overhead at run-time)
; -1: Do not compile at all
;  0: Jump over assertion at run-time
;  1: Execute assertions
; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1)
; Default Value: 1
; Development Value: 1
; Production Value: -1
; http://php.net/zend.assertions
zend.assertions = -1


; Assert(expr); active by default.
; http://php.net/assert.active
;assert.active = On


; Throw an AssertationException on failed assertions
; http://php.net/assert.exception
;assert.exception = On


; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active)
; http://php.net/assert.warning
;assert.warning = On


; Don't bail out by default.
; http://php.net/assert.bail
;assert.bail = Off


; User-function to be called if an assertion fails.
; http://php.net/assert.callback
;assert.callback = 0


; Eval the expression with current error_reporting().  Set to true if you want
; error_reporting(0) around the eval().
; http://php.net/assert.quiet-eval
;assert.quiet_eval = 0
Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.