PHP OO retry logic implementation and passing dynamic method and args - php

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;
}

Related

PHP: Is it a bad practice to use file_get_contents() in a class __constructor?

I have a class that makes an economic calendar out of a json string. The only problem is that I don't know if I should use file_get_contents()(to get the data from an api) inside my class __constructor() or I should just pass the json string to the __constructor from my try{...}catch{...} block?
Which practice is better and why?
Here is my class(EconomicCalendar.php) so far:
class EconomicCalendar{
private $_data,
$_calendar = [];
public function __construct($url){
$this->_data = json_decode(file_get_contents($url));
}
private function make_economic_calendar(){
foreach($this->_data->events as $e){
$arr[$e->date][] = [
'title' => $e->title,
'date' => $e->date
];
}
if(is_array($arr) && count($arr) >= 1){
return (object)$arr;
} else{
throw new Exception('EC was not created');
}
}
public function get_calendar(){
$this->_calendar = $this->make_economic_calendar();
return $this->_calendar;
}
}
Here is the code(ec.php) that outputs the calendar:
spl_autoload_register(function($class){
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . $class . '.php';
});
try {
$c = new EconomicCalendar('https://api.example.com/ec?token={MY_TOKEN}');
$economic_calendar = $c->get_e_list();
} catch (Exception $e) {
exit($e->getMessage());
}
Thank you!
Almost always is better to make IO operation as late (or as little) as possible. So I recommend you to use "named constructor" if you want initialize with data
class EconomicCalendar {
...
public function __construct($data){
$this->_data = $data;
}
...
public static function fromUrl($url){
return new self(json_decode(file_get_contents($url)));
}
}
And usage:
$instance = EconomicCalendar::fromUrl('https://api.example.com/ec?token={MY_TOKEN}');
Moving IO and decoding to dedicated function is closer to single responsibility principle (IO at static, logic at class instance).

Run method chain from a string value

I have a question about getting value from running a method from a string. While I am able to handle a single method call from a string I am curious how to call a chain of methods from a string.
For example. $project is an Object.
$method1 = "name";
$project->$method1; // It shows the valid results
$method2 = "get()->first()->name";
$project->get()->first()-name; // It shows the valid results
$project->$method2; // get a null result
Please help to find a way to make the $method2 work. And what happen if I have params inside those methods?
The reason here is I have made an array of customized methods. It can be run line by line, but I am thinking of a way to turn them into a loop, so it's more efficient. Put the methods in to a file then get values by looping to them.
Array = ["getvalue1()", "getvalue2()",...."getValuen()->anotherMethod()->value"]
Thanks,
If you want nested try something like this:
private function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
return $model->$method;
}, $model);
}
This will go through a chain of method calls as your described. If some of the chain (the last piece) is a property I think I once rigged up the following to handle it:
protected function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
try {
return $model->$method;
} catch (Exception $e) {
return $model->$method();
}
}, $model);
}
If you want to add params try replacing $model->method with:
call_user_func_array(
array($project, 'your_method'),
$params
);
Try this approach:
$method1 = 'name';
$project->{$method1}();
Run method from a string value
Use call_user_func() and call_user_func_array().
call_user_func_array() suits good if you are passing parameters
call_user_func_array(
array($project, 'your_method'),
$params
);
Chain function
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
Here is test
akshay#db-3325:/tmp$ cat test.php
<?php
class Testclass
{
private $str;
function __construct()
{
$this->str = new StdClass;
}
function addA()
{
$this->str->a='A';
return $this;
}
function addB()
{
$this->str->b='B';
return $this;
}
function get()
{
return $this->str;
}
}
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
$object = new Testclass();
// Output 1
print_r(chain_fun("addA()->addB()->get()", $object));
// Output 2
echo chain_fun("addA()->addB()->get()->a", $object);
?>
Output
akshay#db-3325:/tmp$ php test.php
stdClass Object
(
[a] => A
[b] => B
)
A

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 factory for returning conditional objects after verification?

I basically need to call one of two constructors from my PHP class dependent on wheter or not verification is needed.
My current code looks like this:
class Event extends Generic {
private $user_id;
function __construct($user_id=null) {
$this->user_id = $user_id;
}
public function verify($user_id=null, $identifier=null) {
if (parent::verifyUser($user_id, $identifier)) {
return new Event($user_id);
}
else {
// what gets retruend here in the event of a failure???
}
}
public function noverify($user_id=null) {
return new Event(user_id);
}
}
Calling the code like so:
$event = new Event();
$obj1 = $event->noverify(5);
$obj2 = $event->verify(5, 100);
I'm really not clear how I should handle the event of a failed constructor if the condition inside fails. Am I heading down the wrong path here?
I would either throw an Exception or return FALSE:
else {
throw new Exception('verification failed');
}
or
else {
return FALSE;
}

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.
}

Categories