using closure in custom session function - php

I am trying to write a custom SESSION class for PHP to move sessions into the database and away from the file system.
An error is thrown only in the _read() method when the closure $connect2DB is being called. In all other methods calling $connect2DB does not cause any errors.
Also if I instantiate the class as an object myself all methods including the infamous _read() works as expected. Can you find the error?
I have following code:
class sessionDBHandler {
public function __construct() {
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
}
public function _open() {
global $sessCon;
global $HOST;
global $USER;
global $PW;
global $DATABASE;
$sessCon=new mysqli($HOST,$USER,$PW,$DATABASE); //mysqli connection
}
public function _close() {
global $sessCon;
$sessCon->close();
}
public function _read($id) {
global $sessCon;
global $connect2DB;
global $SESSIONTAB;
$qRes=$connect2DB($sessCon,'select `data` from `'.$SESSIONTAB.'` where `ID`=?',array("s",$id)); //<-- error message here
if(!empty($qRes["res"][0]["data"])) return $qRes["res"][0]["data"];
else return '';
}
public function _write($id,$data) {
global $sessCon;
global $connect2DB;
global $SESSIONTAB;
$qRes=$connect2DB($sessCon,'select `ID` from `'.$SESSIONTAB.'` where `ID`=?',array("s",$id));
$param=array(); //buliding SQL query parameters
$param[0]="sis";
$param[]=$id;
$param[]=time();
$param[]=$data;
if(!empty($qRes["res"][0]["ID"])) {
$param[0].="s";
$param[]=$id;
}
$qRes=$connect2DB($sessCon,(!empty($qRes["res"][0]["ID"]) ? 'update' : 'insert').' `'.$SESSIONTAB.'` set `ID`=?, `access`=?, `data`=?'.(!empty($qRes["res"][0]["ID"]) ? ' where `ID`=?' : ''),$param);
if($qRes["info"]["affectedRows"]>0) return true;
else return '';
}
public function _destroy($id) {
global $sessCon;
global $connect2DB;
global $SESSIONTAB;
$qRes=$connect2DB($sessCon,'delete from `'.$SESSIONTAB.'` where `ID`=?',array("s",$id));
if($qRes["info"]["affectedRows"]>0) return true;
else return '';
}
public function _gc($max) {
global $sessCon;
global $connect2DB;
global $SESSIONTAB;
$qRes=$connect2DB($sessCon,'delete from `'.$SESSIONTAB.'` where `access`<?',array("i",(time()-$max)));
if($qRes["info"]["affectedRows"]>0) return true;
else return '';
}
}
and I tell PHP where it findes those functions (before calling session_start() here:
new sessionDBHandler();
My problem seems to be the closure I am using as my MySQL abstraction layer. The error I get is:
Fatal error: Function name must be a string in /Users/user/Sites/script.php on line 401
which suggests to me that it somehow cannot find the namespace of $connect2DB.
The thing is though if I call those functions without __constructor and its session_set_save_handler directive, it will work just fine:
$test=new sessionDBHandler();
$test->_open();
echo $test->_read("d6e08112ff6fc8a7eb6e76832327bf81");
$test->_close();
the above works without an error.
Now I am using closures here because I have to keep the footprint of this script very low and unset $connect2DB at the bottom of the script among other reasons so I would love to keep the current abstraction layer.
Any ideas what I can do here?
thanks everyone!
perhaps the $connect2DB is not initialized when sessions is being called?
connnect2DB
/**
* Simple MySQLi abstraction layer
*
* #param resource $mysqli The MySQLi connection link
* #param string $query The MySQL query for prepaired statement
* #param array $v The parameters to replace ? in $query. First element must be the type
* #param integer $o Option for more debug infos [0]=no infos(default) [1]=adding debug infos
*
* #return array [for select]=associative array of table result [for everything else]=associative array with affectedRows,info and insertID
*
* #author Dominik Wilkowski
*/
$connect2DB=function($mysqli,$query,$v=array(),$o=0) {
if($mysqli->connect_errno) {
return array('info'=>array('error'=>'Connect failed: '.$mysqli->connect_error)); //error handling here
exit();
}
if($v && (substr_count($query,"?")!=strlen($v[0]) || strlen($v[0])!=((count($v)-1)>=0 ? (count($v)-1) : 0))) {
return array('info'=>array('error'=>'Placeholders are unequal! placeholders:'.substr_count($query,"?").', replacements:'.strlen($v[0]).', param:'.(count($v)-1).' ('.$v[0].')')); //error handling here...
exit();
}
if($res=$mysqli->prepare($query)) {
//dynamically bind all $v
if($v) {
$values=array($v[0]);
for($i=1; $i<count($v); $i++) {
${'bind'.$i}=$v[$i];
$values[]=&${'bind'.$i};
}
call_user_func_array(array($res,'bind_param'),$values);
}
$res->execute();
//bind all table rows to result
if(strtolower(substr($query,0,6))=="select") {
$field=$fields=$tempRow=array();
$meta=$res->result_metadata();
while($field=$meta->fetch_field()) {
$fieldName=$field->name;
$fields[]=&$tempRow[$fieldName];
}
$meta->free_result();
call_user_func_array(array($res,"bind_result"),$fields);
//return associative array
$results=array();
$i=0;
while($res->fetch()) {
$results["res"][$i]=array();
foreach($tempRow as $k=>$v2) $results["res"][$i][$k] = $v2;
$i++;
}
$res->free_result();
}
else { //return infos about the query
if($mysqli->warning_count) {
if($err=$mysqli->query("SHOW WARNINGS")) {
$row=$err->fetch_row();
$results["info"]["error"].=$row[0].' ('.$row[1].'): '.$row[2];
$err->close();
}
}
$results["info"]["affectedRows"]=$mysqli->affected_rows;
$results["info"]["info"]=$mysqli->info;
$results["info"]["insertID"]=$mysqli->insert_id;
}
$res->close();
}
if($o===1) { //adding debug infos
$q=$query;
for($i=1;$i<count($v);$i++) $q=preg_replace("/\?/",(substr($v[0],($i-1),1)=="s" ? '"' : '').$v[$i].(substr($v[0],($i-1),1)=="s" ? '"' : ''),$q,1);
$results["info"]["query"]=$q;
$results["info"]["param"]=json_encode($v);
}
if(strtolower(substr($query,0,6))=="update" || strtolower(substr($query,0,6))=="delete") { //optimize at update and delete
preg_match_all('/((update|delete) `(.*)` )/i',$query,$tables);
foreach($tables[3] as $t) $mysqli->query('OPTIMIZE TABLE '.$t);
}
return $results;
};
Update #2
Our earlier test suggested that namespace indeed does not seem to be initialized at the time when _read() runs so I wrote a small test case for this to double check.
<?php
$LOGIN='login credentials';
//Closure
$nob=function() {
return 'closure running!<br>';
};
//Session handler
class sessionDBHandler {
public function __construct() {
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
}
public function _open() {
global $sessCon;
global $LOGIN;
echo '_open running with: '.$LOGIN.'<br>';
$sessCon='connection established!<br>';
}
public function _close() {
global $sessCon;
echo $sessCon='connection closed!<br>';
}
public function _read($id) {
global $sessCon;
global $nob;
echo $nob();
echo 'requested id is: '.$id.'<br>';
echo 'sessCon is: '.$sessCon;
}
public function _write($id,$data) {
global $sessCon;
echo 'requested id is: '.$id.' and data: '.$data.'<br>';
echo 'sessCon is: '.$sessCon;
}
public function _destroy($id) {
global $sessCon;
echo 'requested id is: '.$id.'<br>';
echo 'sessCon is: '.$sessCon;
}
public function _gc($max) {
global $sessCon;
echo 'requested max is: '.$max.'<br>';
echo 'sessCon is: '.$sessCon;
}
}
new sessionDBHandler();
session_start();
?>
Interestingly enough this will run without errors and output this:
_open running with: login credentials
closure running!
requested id is: 746b70a979d9ef8c6686e659707b4b38
sessCon is: connection established!
requested id is: 746b70a979d9ef8c6686e659707b4b38 and data:
sessCon is: connection established!
connection closed!
Now that suggests that there seems to be an error in my particular implementation. Whoever wants to have a stab at this is very welcome to join :)
Update #3
I want to unset my closure after I run my application and had therefore unset($connect2DB); on the very bottom of the script. This however seemed to have caused a "premature" unset somehow. Removing this will remove the error.
However
Is there a way to safely unset my closure after the script has run so it doesn't flow into the main script?

Related

How do you bind a function to another function 'event status' in PHP

To ease up the debuging for my class(es) I want to bind a function to the status of other function events. The current set-up I have, is similair to following the code:
class config {
function __construct($file) {
$this->functions = array(); // The array with function run/succes information
if (!empty($file)) {
$this->checkFile($file);
}
}
public function checkFile($file) {
$this->functionStatus('run',true);
if (file_exists($file)) {
$this->functionStatus('succes',true);
return true;
} else {
$this->functionStatus('succes',true);
return false;
}
}
/* Simplified functionStatus function */
private function functionStatus($name,$value) {
/* - validations removed -*/
// Get parent function name
$callers = debug_backtrace();
$function = $callers[1]['function'];
/* - validations removed -*/
$this->functions[$function][$name] = $value;
}
}
An example of the principle in use:
$config = new config('example.ini');
print var_dump($config->functions);
/* Results in:
array(1) {
["checkFile"]=> array(2) {
["run"]=> bool(true)
["succes"]=> bool(true)
}
}
*/
While this set-up works fine. I would like to improve it by removing the manually placed $this->functionStatus('run',true) function everytime I create a function to keep the code a bit cleaner and prevent assuming a function isn't run because someone forgat defining the functionStatus at top of the function. Same goes for the definitions at the return events.
*Note, the best solution would also support this binding with other classes
Is there any way to accomplish this 'event binding' with PHP ?
You can do this using the __call magic method. Change all your public functios to private methods, and add a prefix to the name, e.g.
private function internal_checkFile($file) {
...
}
Then add the magic method:
public function __call($name, $arguments) {
$this->functionStatus('run', true);
return call_user_func_array(array($this, "internal_$name"), $arguments);
}

PHP function inside a class function include

I am working with lemonade-php. My code is at https://github.com/sofadesign/limonade.
The issue I am having is when I try to run
class syscore {
public function hello(){
set('post_url', params(0));
include("./templates/{$this->temp}/fullwidth.tpl");
return render('fullwidth');
}
}
which then loads the fullwidth.tpl and runs function fullwidth
fullwidth.tpl
<?php
global $post;
function fullwidth($vars){
extract($vars);
$post = h($post_url);
}
print_r($this->post($post));
?>
it seems to pass the $post_url but I can not pass it again to the print_r($this->post($post));
However when I try to run print_r($this->post($post)) inside the fullwidth function it says it can not find the post() function
I have tried a number of things like below
function fullwidth($vars){
extract($vars);
$post = h($post_url);
print_r(post($post));
}
I tried re connecting to the syscore by
$redi = new syscore();
$redi->connection() <-- this works
$redi->post($post) <-- this does not
Here is my full class syscore
class syscore {
// connect
public function connect($siteDBUserName,$siteDBPass,$siteDBURL,$siteDBPort, $siteDB,$siteTemp){
for ($i=0; $i<1000; $i++) {
$m = new Mongo("mongodb://{$siteDBUserName}:{$siteDBPass}#{$siteDBURL}:{$siteDBPort}", array("persist" => "x", "db"=>$siteDB));
}
// select a database
$this->db = $m->$siteDB;
$this->temp = $siteTemp;
}
public function hello(){
set('post_url', params(0));
include("./templates/{$this->temp}/fullwidth.tpl");
return render('fullwidth');
}
public function menu($data)
{
$this->data = $data;
$collection = $this->db->redi_link;
// find everything in the collection
//print $PASSWORD;
$cursor = $collection->find(array("link_active"=> "1"));
if ($cursor->count() > 0)
{
$fetchmenu = array();
// iterate through the results
while( $cursor->hasNext() ) {
$fetchmenu[] = ($cursor->getNext());
}
return $fetchmenu;
}
else
{
var_dump($this->db->lastError());
}
}
public function post($data)
{
$this->data = $data;
$collection = $this->db->redi_posts;
// find everything in the collection
//print $PASSWORD;
$cursor = $collection->find(array("post_link"=> $data));
if ($cursor->count() > 0)
{
$posts = array();
// iterate through the results
while( $cursor->hasNext() ) {
$posts[] = ($cursor->getNext());
}
return $posts;
}
else
{
var_dump($this->db->lastError());
}
}
}
It looks like you are having some issues understanding the execution path that PHP is taking when trying to render your template. Let's take a more in-depth look, shall we?
// We're going to call "syscore::hello" which will include a template and try to render it
public function hello(){
set('post_url', params(0)); // set locals for template
include("./templates/{$this->temp}/fullwidth.tpl"); // include the template
return render('fullwidth'); // Call fullwidth(array('post_url' => 'http://example.com/path'))
}
The trick to solving this one is to understand how PHP include works. When you call include("./templates/{$this->temp}/fullwidth.tpl") some of your code is executing in the scope of the syscore object, namely:
global $post;
and
print_r($this->post($post));
fullwidth is created in the global scope at this point, but has not yet been called. When render calls fullwidth you're no longer in the syscore scope, which is why you cannot put $this->post($post) inside without triggering an error.
Ok, so how do we solve it? Glad you asked.
We could probably refactor syscore::post to be a static method, but that would then require syscore::db to be static, and always return the SAME mongodb instance (singleton pattern). You definitely do not want to be creating 1000 Mongo instances for each syscore instance.
We could just abuse the framework. A much poorer solution, but it will get the job done.
fullwidth.tpl
<?php
function fullwidth($vars){
$post_url = ''; // put the variables you expect into the symbol table
extract($vars, EXTR_IF_EXISTS); // set EXTR_IF_EXISTS so you control what is added.
$syscore_inst = new syscore;
$post = h($post_url);
print_r($syscore->post($post)); // I think a puppy just died.
}
Look the second way is a complete hack, and writing code like that will probably mean you won't get promoted. But it should work.
But let's say you wanted to get promoted, you would make good, shiny code.
// Note: Capitalized class name
class Syscore {
protected static $_db;
public static function db () {
if (! static::$_db) {
static::$_db = new Mongo(...);
}
return static::$_db;
}
// #FIXME rename to something more useful like "find_posts_with_link"
public static post($url) {
$collection = static::db()->redi_posts;
// find everything in the collection
$cursor = $collection->find(array("post_link"=> $url));
// Changed to a try-catch, since we shouldn't presume an empty find is
// an error.
try {
$posts = array();
// iterate through the results
while( $cursor->hasNext() ) {
$posts[] = ($cursor->getNext());
}
return $posts;
} catch (Exception $e) {
var_dump($this->db->lastError());
}
}
}
Then in your fullwidth function, we don't have to do any of that stupid nonsense of treating an instance method like it were a static method.
function fullwidth($vars){
$post_url = ''; // put the variables you expect into the symbol table
extract($vars, EXTR_IF_EXISTS); // set EXTR_IF_EXISTS so you control what is added.
$post = h($post_url);
print_r(Syscore::post($post)); // static method. \O/ Rainbows and unicorns.
}

PHP sessions and class members

Ok, messing about with classes in PHP and can't get it to work the way I'm used to as a C++/Java-guy. In the "_init" function, if I run a query at the "// query works here" line", everythong works, but in the "getUserID" function, all that happens is said warning...
"getUserID" gets called from login.php (they are in the same dir):
login.php
<?php
include_once 'sitehandler.php';
include_once 'dbhandler.php';
session_start();
#TODO: Safer input handling
$t_userName = $_POST["name"];
$t_userId = $_SESSION['handler']['db']->getUserID($t_userName);
if ($t_userId != -1) {
$_SESSION['user']['name'] = $t_userName;
$_SESSION['user']['id'] = $t_userId;
}
//error_log("user: " . $_SESSION['user']['name'] . ", id: ". $_SESSION['user']['id']);
header("Location: " . $_SERVER["HTTP_REFERER"]);
?>
dbhandler.php
<?php
include_once 'handler.php';
class DBHandler extends HandlerAbstract {
private $m_handle;
function __construct() {
parent::__construct();
}
public function test() {
#TODO: isdir liquibase
#TODO: isfile liquibase-195/liquibase + .bat + execrights
$this->m_isTested = true;
}
public function _init() {
if (!$this->isTested()) $this->test();
if (!file_exists('files/data.db')) {
#TODO: How to to if host is Windows based?
exec('./files/liquibase-1.9.5/liquibase --driver=org.sqlite.JDBC --changeLogFile=files/data_db.xml --url=jdbc:sqlite:files/data.db update');
#TODO: quit if not success
}
#TODO: Set with default data
try {
$this->m_handle = new SQLite3('files/data.db');
} catch (Exception $e) {
die("<hr />" . $e->getMessage() . "<hr />");
}
// query works here
$this->m_isSetup = true;
}
public function teardown() {
}
public function getUserID($name) {
// PHP Warning: SQLite3::prepare(): The SQLite3 object has not been correctly initialised in
$t_statement = $this->m_handle->prepare("SELECT id FROM users WHERE name = :name");
$t_statement->bindValue(":name", $name, SQLITE3_TEXT);
$t_result = $t_statement->execute();
//var_dump($this->m_handle);
return ($t_result)? (int)$t_result['id']: -1;
}
}
When you place something in the session and the script ends, PHP goes through each object and calls the __sleep() magic function. Once it resumes the session, __wakeup() is called.
Resources are not stored in sessions. So every time you want to have a resource available to you, you must initialize it on every script run. This can easily be done, in your example, by implementing the __wakeup() magic method, which should call $this->_init();.
Your code could then become:
<?php
include_once 'handler.php';
class DBHandler extends HandlerAbstract { private $m_handle;
function __construct() {
parent::__construct();
}
public function test() {
#TODO: isdir liquibase
#TODO: isfile liquibase-195/liquibase + .bat + execrights
$this->m_isTested = true;
}
public function _init() {
if (!$this->isTested()) $this->test();
if (!file_exists('files/data.db')) {
#TODO: How to to if host is Windows based?
exec('./files/liquibase-1.9.5/liquibase --driver=org.sqlite.JDBC --changeLogFile=files/data_db.xml --url=jdbc:sqlite:files/data.db update');
#TODO: quit if not success
}
#TODO: Set with default data
try {
$this->m_handle = new SQLite3('files/data.db');
} catch (Exception $e) {
die("<hr />" . $e->getMessage() . "<hr />");
}
// query works here
$this->m_isSetup = true;
}
public function teardown() {
}
public function getUserID($name) {
// PHP Warning: SQLite3::prepare(): The SQLite3 object has not been correctly initialised in
$t_statement = $this->m_handle->prepare("SELECT id FROM users WHERE name = :name");
$t_statement->bindValue(":name", $name, SQLITE3_TEXT);
$t_result = $t_statement->execute();
//var_dump($this->m_handle);
return ($t_result)? (int)$t_result['id']: -1;
}
/**
* Start up the SQLite resource once
* the session is started.
*
*/
public function __wakeup() {
$this->init();
}
}

PHP OO retry logic implementation and passing dynamic method and args

My first question here.
The question is similar to this one: PHP: Retrying a query a set number of times or until success
Try till success in OO way.
Here example what i'm trying to do:
class Creatives {
public function run() {
$auth_token='mypassword';
$id=123123;
$this->retry_till_success ( $this->getCreatives, array($auth_token, $id) );
print $this->creatives;
}
public function getCreatives($auth_token, $id) {
$this->creatives = $this->campagin->get($auth_token, $id);
}
private function retry_till_success($method, $args) {
do {
$try_again = false;
try {
/* how to call the method with */
/* call user method with params pass */
/* do until success */
} catch (SoapFault $fault) {
if($fault->faultstring== 'couldnt connect to host')
$try_again=true;
}
} while ($try_again);
}
}
i read about call_user_func, but don't know if i could use it inside the class,
I need to make 99.9% success rate in my calls, any suggestion to achieve this will be great.
thank you.
Best way would be to extend SoapClient and add the retry in the __call method.
class LocalSoapClient extends SoapClient
{
public function __call($function_name, $arguments)
{
$result = false;
$max_retries = 5;
$retry_count = 0;
while(! $result && $retry_count < $max_retries)
{
try
{
$result = parent::__call($function_name, $arguments);
}
catch(SoapFault $fault)
{
if($fault->faultstring != 'Could not connect to host')
{
throw $fault;
}
}
sleep(1);
$retry_count ++;
}
if($retry_count == $max_retries)
{
throw new SoapFault('Could not connect to host after 5 attempts');
}
return $result;
}
}
then when you instantiate your soap client use new LocalSoapClient() instead of new SoapClient()
call_user_func_array() is great for this:
$result = call_user_func_array( array($this, $method), $args );
The first argument is the callback pseudo-type, and the second is an array of parameters which will be passed to the function/method as individual arguments.
As a side note, you might want to look at throttling your retries (e.g. have a sleep time which doubles every time it fails up to a set limit). If the connection to the host is down there may not be much point in retrying as fast as possible.
private function retry_till_success($method, $args) {
...
$this->$method($args[0], $args[1]);
}
You could also use ReflectionClass/ReflectionMethod and call InvokeArgs() for a variable number of args
http://nz.php.net/oop5.reflection
I'm not quite sure what you mean, but this is how call_user_func(_array)() works:
call_user_func($method, $arg); //call global function named $method like this: $method($arg)
call_user_func(array($this, $method), $arg); call class method on this class like this: $this->$method($arg);
//lets presume args is an array: $args = array(1, 2);
call_user_func_array($method, $args); //calls global function named $method like this: $method($args[0], $args[1]);
call_user_func_array(array($this, $method), $args); //calls class method like this: $this->$method($args[0], $args[1]);
Also see the documentation for call_user_func:
http://nl3.php.net/manual/en/function.call-user-func.php
and for call_user_func_array:
http://nl3.php.net/manual/en/function.call-user-func-array.php
$this->getCreatives won't work because in PHP functions are not base class citizen. You can use call_user_func[_array] or create a factory for tasks and represent every task as an object which implements an interface (ie iRepeatableTask). Thus you can call
try
{
$task->repeatUntilSuccess()
} catch (SoapFault $e) {...}
and the advantage is that those objecs are easy-to-save/restore in DB to perform later (ie with cronjob etc.).
Here is my final solution I'm using in production.
protected function retry_till_success($method, $args) {
/*
* -1 : unrecoverable error
* 0 : recoverable error
* 1 : success
*/
$success = 0;
$tries = 0;
do {
$tries++;
$success = call_user_func_array( array($this, $method), $args );
if ($success===0) {
usleep(self::DELAY_MS);
}
} while ($success===0 && $tries < self::MAX_RETRIES);
if ($tries >= self::MAX_RETRIES)
return false;
return true;
}

How do you stop a PHP class from executing?

I'm looking for something like break for loops.
Here's some example code (using Symfony's lime) where stop() would not let the class continue and I_DONT_WANT_THIS_TO_RUN() would not be executed.
$browser->isStatusCode(200)
->isRequestParameter('module', 'home')
->isRequestParameter('action', 'index')
->click('Register')
->stop()
->I_DONT_WANT_THIS_TO_RUN();
$browser->thenThisRunsOkay();
Calling $this->__deconstruct(); from within stop() doesn't seem to do the trick. Is there a function I can call within stop() that would make that happen?
You could use PHP exceptions:
// This function would of course be declared in the class
function stop() {
throw new Exception('Stopped.');
}
try {
$browser->isStatusCode(200)
->isRequestParameter('module', 'home')
->isRequestParameter('action', 'index')
->click('Register')
->stop()
->I_DONT_WANT_THIS_TO_RUN();
} catch (Exception $e) {
// when stop() throws the exception, control will go on from here.
}
$browser->thenThisRunsOkay();
Just return another class which will return $this for every method called.
Example:
class NoMethods {
public function __call($name, $args)
{
echo __METHOD__ . " called $name with " . count($args) . " arguments.\n";
return $this;
}
}
class Browser {
public function runThis()
{
echo __METHOD__ . "\n";
return $this;
}
public function stop()
{
echo __METHOD__ . "\n";
return new NoMethods();
}
public function dontRunThis()
{
echo __METHOD__ . "\n";
return $this;
}
}
$browser = new Browser();
echo "with stop\n";
$browser->runThis()->stop()->dontRunThis()->dunno('hey');
echo "without stop\n";
$browser->runThis()->dontRunThis();
echo "the end\n";
Will result in:
with stop
Browser::runThis
Browser::stop
NoMethods::__call called dontRunThis with 0 arguments.
NoMethods::__call called dunno with 1 arguments.
without stop
Browser::runThis
Browser::dontRunThis
the end
OIS's answer is really good, though I could see that it might get confusing if the object suddenly changes to something else. That is, you'd expect that at the end of your chain, you'll end up with the same object. To avoid that problem, I'd add a private variable to tell the class whether or not to actually do anything. If the class has been stopped, then every class just returns $this straight away. This gives you the added benefit of being able to restart the execution.
class MyClass {
private $halt;
function __call($func, $args) {
if ($this->halt) {
return $this;
} else {
return $this->$func($args);
}
}
private function isRequestParameter() {
// ...
}
public function stop() {
$this->halt = true;
}
public function start() {
$this->halt = false;
}
}
This could be put into a parent class so you don't have to duplicate this code.

Categories