So I'm trying to implement an Aspect-Oriented Design into my architecture using debug_backtrace and PHP reflection. The design works but I decided to see just how badly it impacts performance so I wrote up the following profiling test. The interesting thing is that when the Advisable and NonAdvisable methods do nothing, the impact is about 5 times that for using an advisable method versus using a non-advisable method, but when I increase the complexity of each method (here by increasing the number of iterations to 30 or more), advisable methods perform begin to perform better and continue to increase as the complexity increases.
Base class:
abstract class Advisable {
private static $reflections = array();
protected static $executions = 25;
protected static function advise()
{
$backtrace = debug_backtrace();
$method_trace = $backtrace[1];
$object = $method_trace['object'];
$function = $method_trace['function'];
$args = $method_trace['args'];
$class = get_called_class();
// We'll introduce this later
$before = array();
$around = array();
$after = array();
$method_info = array(
'args' => $args,
'object' => $object,
'class' => $class,
'method' => $function,
'around_queue' => $around
);
array_unshift($args, $method_info);
foreach ($before as $advice)
{
call_user_func_array($advice, $args);
}
$result = self::get_advice($method_info);
foreach ($after as $advice)
{
call_user_func_array($advice, $args);
}
return $result;
}
public static function get_advice($calling_info)
{
if ($calling_info['around_queue'])
{
$around = array_shift($calling_info['around_queue']);
if ($around)
{
// a method exists in the queue
return call_user_func_array($around, array_merge(array($calling_info), $calling_info['args']));
}
}
$object = $calling_info['object'];
$method = $calling_info['method'];
$class = $calling_info['class'];
if ($object)
{
return null; // THIS IS THE OFFENDING LINE
// this is a class method
if (isset(self::$reflections[$class][$method]))
{
$parent = self::$reflections[$class][$method];
}
else
{
$parent = new ReflectionMethod('_'.$class, $method);
if (!isset(self::$reflections[$class]))
{
self::$reflections[$class] = array();
}
self::$reflections[$class][$method] = $parent;
}
return $parent->invokeArgs($object, $calling_info['args']);
}
// this is a static method
return call_user_func_array(get_parent_class($class).'::'.$method, $calling_info['args']);
}
}
An implemented class:
abstract class _A extends Advisable
{
public function Advisable()
{
$doing_stuff = '';
for ($i = 0; $i < self::$executions; $i++)
{
$doing_stuff .= '.';
}
return $doing_stuff;
}
public function NonAdvisable()
{
$doing_stuff = '';
for ($i = 0; $i < self::$executions; $i++)
{
$doing_stuff .= '.';
}
return $doing_stuff;
}
}
class A extends _A
{
public function Advisable()
{
return self::advise();
}
}
And profile the methods:
$a = new A();
$start_time = microtime(true);
$executions = 1000000;
for ($i = 0; $i < $executions; $i++)
{
$a->Advisable();
}
$advisable_execution_time = microtime(true) - $start_time;
$start_time = microtime(true);
for ($i = 0; $i < $executions; $i++)
{
$a->NonAdvisable();
}
$non_advisable_execution_time = microtime(true) - $start_time;
echo 'Ratio: '.$advisable_execution_time/$non_advisable_execution_time.'<br />';
echo 'Advisable: '.$advisable_execution_time.'<br />';
echo 'Non-Advisable: '.$non_advisable_execution_time.'<br />';
echo 'Overhead: '.($advisable_execution_time - $non_advisable_execution_time);
If I run this test with the complexity at 100 (A::executions = 100), I get the following:
Ratio: 0.289029437803
Advisable: 7.08797502518
Non-Advisable: 24.5233671665
Overhead: -17.4353921413
Any ideas?
You're skipping all of the iterations when you call A's Advisable method... you're overwriting it with one single call to the inherited advise() method. So, when you add iterations, you're only adding them to the NonAdvisable() call.
method overhead should apply to PHP as well as to Java i guess - "the actual method that is called is determined at run time" => the overhead for shadowed Advisible method is bigger
but it would be O(1) instead of Non-Advisable's O(n)
Sorry for the bother, I just found the line return null; in the get_advice method before the parent method gets called. I hate to answer my own question but it's not really worth someone else searching for it.
Related
I have a class Tpl to mount template with this function (template.php)
function Set($var, $value){
$this->$var = $value;
}
A php file that call the function, example (form.php):
$t->Set("lbAddress","Address");
And a html file with the template with tags (template.html)
<tr><td>[lbAdress]</td></tr>
To print the html I have this function (template.php) - the notice points to this function
function Show_Temp($ident = ""){
// create array
$arr = file($this->file);
if( $ident == "" ){
$c = 0;
$len = count($arr);
while( $c < $len ){
$temp = str_replace("[", "$" . "this->", $arr[$c]);
$temp = str_replace("]", "", $temp);
$temp = addslashes($temp);
eval("\$x = \"$temp\";");
echo $x;
$c++;
}
} else {
$c = 0;
$len = count($arr);
$tag = "*=> " . $ident;
while( $c < $len ){
if( trim($arr[$c]) == $tag ){
$c++;
while( (substr(#$arr[$c], 0 ,3) != "*=>" ) && ($c < $len) ){
$temp = str_replace("[", "$" . "this->", $arr[$c]);
$temp = str_replace("]", "", $temp);
$temp = addslashes($temp);
eval("\$x= \"$temp\";"); //this is the line 200
echo $x;
$c++;
}
$c = $len;
}
$c++;
}
}
}
If the template .html have a line [lbName] and I don't have the line $t->Set("lbName","Name"); at the php code, I receive the error PHP Notice: Undefined property: Tpl::$lbName in ../template.php(200) : eval()'d code on line 1. The solution that I found is add lines like $t->Set("lbName","");, but if I have 50 tags in HTML that I don't use in PHP, I have to add all 50 $t->Set("tag_name","");. The error occurred after migrate to the PHP 5.
Can someone help me? Thanks
Perhaps a better way still would be not to rely on dynamic evaluation through eval (it's generally best to avoid eval where possible), but to replace [lbName] with the value stored in the object directly as and when needed. If you can replace [lbName] with $this->lbName, surely you can also replace it with the value of lBName that you've looked up on-the-fly?
To answer your original question, however:
If I understand correctly, you're setting the values like this:
$t->Set('foo', 'bar');
And – effectively – getting them like this:
$t->foo;
If so, you could implement a __get method to intercept the property references and provide your own logic for retrieving the value; e.g.:
public function __get($key)
{
// You can adapt this logic to suit your needs.
if (isset($this->$key))
{
return $this->$key;
}
else
{
return null;
}
}
In this case, you'd probably be better off using an associative array as the backing store, and then using __get and __set to access it; e.g.:
class Template
{
private $values = array();
public function __get($key)
{
if (array_key_exists[$key, $this->values])
{
return $this->values[$key];
}
else
{
return null;
}
}
public function __set($key, $value)
{
$this->values[$key] = $value;
}
}
I was wondering about what the best practices are when it comes to call a class method using the above functions to fill up a method call dynamically using an array!
What advantages and disadvantages do I have? I mean, it seems the RefelectionMethod + invokeArgs option is up to 40% faster than the call_user_funcion in certain conditions… but do I miss something?
thanks.
As requested i will add a little benchmark of my scenario where i needed refereces to be passed... i know its a very specific case and that this is not stricly related to the question above!
class foo { public function bar(&$a, &$b, &$c) { /* */ } }
$t1 = microtime(true);
$arr = array(1,2,3);
$foo = new foo;
$rfl = new ReflectionMethod('foo', 'bar');
for ($i=0; $i < 10000; ++$i)
{
$rfl->invokeArgs($foo, $arr);
}
$t2 = microtime(true);
echo sprintf("\nElapsed reflectionmethod : %f", $t2 - $t1);
$t1 = microtime(true);
$arr = array(1,2,3);
$foo = new foo;
for ($i=0; $i < 10000; ++$i)
{
foreach( $arr as $k => $v ) $ref[$k] = &$arr[$k];
call_user_func_array( array($foo, 'bar'), $arr);
}
$t2 = microtime(true);
echo sprintf("\nElapsed calluserfuncarray : %f", $t2 - $t1);
the result
Elapsed reflectionmethod : 0.025099
Elapsed calluserfuncarray : 0.051189
I really would just like to know when its better to use one versus the other, and why! its not strictly related to speed though!
Not sure where you got your test results but RefelectionMethod + invokeArgs option is up to 40% faster than the call_user_funcion seems far fetched it can only be possible with single instance multiple invoke
Simple Benchmark
set_time_limit(0);
echo "<pre>";
class Foo {
public function bar($arg1, $arg2) {
}
}
$globalRefection = new ReflectionMethod('Foo', 'bar');
$globalFoo = new Foo();
// Using call_user_func_array
function m1($args) {
$foo = new Foo();
call_user_func_array(array($foo,"bar"), $args);
}
// Using ReflectionMethod:invoke
function m2($args) {
$foo = new ReflectionMethod('Foo', 'bar');
$foo->invoke(new Foo(), $args[0], $args[1]);
}
// Using ReflectionMethod:invokeArgs
function m3($args) {
$foo = new ReflectionMethod('Foo', 'bar');
$foo->invokeArgs(new Foo(), $args);
}
// Using Global Reflection
function m4($args) {
global $globalRefection;
$globalRefection->invokeArgs(new Foo(), $args);
}
// Using Global Reflection + Glbal foo
function m5($args) {
global $globalRefection, $globalFoo;
$globalRefection->invokeArgs($globalFoo, $args);
}
$result = array('m1' => 0,'m2' => 0,'m3' => 0,'m4' => 0,'m5' => 0);
$args = array("arg1","arg2");
for($i = 0; $i < 10000; ++ $i) {
foreach ( array_keys($result) as $key ) {
$alpha = microtime(true);
$key($args);
$result[$key] += microtime(true) - $alpha;
}
}
echo '<pre>';
echo "Single Run\n";
print_r($result);
echo '</pre>';
Output
Single Run
Array
(
[m1] => 0.018314599990845 <----- call_user_func_array
[m2] => 0.024132013320923 <----- ReflectionMethod:invoke
[m3] => 0.021934270858765 <----- ReflectionMethod:invokeArgs
[m4] => 0.012894868850708 <----- Global Relection
[m5] => 0.01132345199585 <----- Global Reflection + Global Foo
)
See Live Action
I am attempting to take simple number ranges (in the example of this project, I am working with different increments of money for each user. FAKE money, by the way...) and group them into classes that can be displayed publicly instead of the absolute number.
Here is a rough sample of code for the long way to write this function as an example:
<?
$money=500001;
if($money > 0 AND $money < 5000) {
$class = 'Poor';
} elseif ($money >= 5000 AND $money < 25000) {
$class = 'Lower Class';
} elseif ($money >= 25000 AND $money < 100000) {
$class = 'Middle Class';
} elseif ($money >= 100000) {
$class = 'Upper Class';
}
echo $class;
exit();
?>
Now, I know this function will work, but it seems like an absolutely awful way in going about writing it, since if more $class' are added, it becomes far too long.
Now my question is: Is there a shorter way to write this? Possibly using range() and/or an array of values?
I would go for something like this:
function getClass($sal){
$classes = array(5000=>"poor", 25000=>"Lower Class", 100000=>"Middle Class");
foreach ($classes as $key => $value) {
if($sal < $key){
return $value;
}
}
return "Upper Class";
}
$salary = 90000;
$sal_class = getClass($salary);
echo "salary class = $sal_class\n";
Output:
sal = Middle Class
A bit similar to above answer, but a better one. I will suggest using OO approach.
Class Foo {
protected $class = array(
array("poor", 0, 5000),
array("medium", 5000, 25000)
);
function get_class($amount) {
foreach ($this -> class as $key => $value) {
if($amount > $value[1] && $amount < $value[2]) {
return $value[0];
}
}
return null;
}
function add_class(array $arr) {
$this -> class[] = $arr;
}
}
Usage:
$obj = new Foo();
$obj -> get_class(6000); //Outputs: medium
$obj -> add_class(array("rich", 25000, 50000)); //Add a new class
$obj -> get_class(35000); //Outputs: rich
I am studying OOP and this is my first study project.
I created a Math class and also created an add method. But when I am trying to create a subtract method I don't know where I am getting a problem.
Please kindly help and give me information where I can get more detailed information on OOP.
<?php
class Math
{
/**
*
* #return int
*/
function add()
{
$args = func_num_args();
$sum = 0;
$i = 0;
for ( $i; $i < $args; $i++ )
{
is_int(func_get_arg($i)) ? $sum += func_get_arg($i) : die('use only integers, please');
}
return $sum;
}
function subtract()
{
$args = func_num_args();
$sub = 0;
$i = 0;
while($i < $args)
{
$sub = func_get_arg($i);
if (is_int(func_get_arg($i)))
{
is_int($sub - func_get_arg($i));
}
}
$i++;
return $sub;
}
}
I am calling this class in my index.php like this:
<?php
include("Math.php");
$c = new Math();
$result = $c->subtract(100,10,20,45);
echo $result;
?>
There are a few small problems here:
Your loop won't ever terminate because the incrementing of $i is outside of your while loop.
The setting of $sub the first time should happen before the while loop. I assume your subtraction function is meant to subtract the latter arguments from the first argument. Right now, $sub is reset every pass through the loop.
$sub's value is never updated by the subtraction operation in your loop. You need to assign a new value to $sub based on the subtraction. You can use the -= shorthand for this just like you used the += shorthand in your add() method.
A working solution would look like this:
$sub = func_get_arg( $i ); // At this point $i == 0
while ( $i < $args ) { // Loop while $i is less than the number of args
$i++; // Increment $i
$operand = func_get_arg( $i ); // Name the argument for clarity
if ( is_int( $operand )) { // Make sure the $operand is an integer
$sub -= $operand; // Update $sub by subtracting $operand from it
} else {
// Do some error handling here...
}
}
I would recommend for you to watch this video The Clean Code Talks -- Inheritance, Polymorphism, & Testing.
This might help you to understand OOP better, and one of examples in the talk is very similar to one you are trying to make.
The functional line is_int($sub - func_get_arg($i)); is incorrect. I think you intend to use this as a ternary operator and add additional logic. Here is my rewrite:
public function subtract() {
$args = func_get_args();
$sub = array_shift($args);
foreach ($args as $arg) {
is_int($sub - $arg) and $sub -= $arg
or die('use only integers please');
}
return $sub;
}
You could also do that using array_reduce() and bcsub() (or other subtraction function):
$sub = array_reduce(array_slice(func_get_args(), 1), 'bcsub', func_get_arg(0));
I'm developing a function to parse 2 xml files. It compares them node by node and then if the nodes are different, the function should return one of them. But it isn't returning anything.
$xml = simplexml_load_file("file1.xml");
$xml2 = simplexml_load_file("file2.xml");
$result = parseNode($xml, $xml2);
print_r($result);
echo $result;
function parseNode($node1, $node2) {
for ($i = 0; $i < count($node1->children()); $i++) {
$child1 = $node1->children();
$child2 = $node2->children();
if ($child1[$i]->getName() != $child2[$i]->getName()) {
return $child1[$i];
} else {
parseNode($child1[$i], $child2[$i]);
}
}
}
return parseNode($child1[$i], $child2[$i]);
Well, you can do it with a simple conditional statement...
$xml = simplexml_load_file("file1.xml");
$xml2 = simplexml_load_file("file2.xml");
$result = parseNode($xml, $xml2);
print_r($result);
echo $result;
function parseNode($node1, $node2) {
$child1 = $node1->children();
$child2 = $node2->children();
$numChildren = count($child1);
for ($i = 0; $i < $numChildren; $i++) {
if ($child1[$i]->getName() != $child2[$i]->getName()) {
return $child1[$i];
} else {
$test = parseNode($child1[$i], $child2[$i]);
if ($test) return $test;
}
}
return false;
}
You could also loop over the XML structures using recursive iterators to simplify your parseNodes() function.
$xml = simplexml_load_string("<root><foo/><bar><baz/></bar></root>", "SimpleXMLIterator");
$xml2 = simplexml_load_string("<root><foo/><bar><baz/></bar><bat/></root>", "SimpleXMLIterator");
$result = parseNode($xml, $xml2);
echo $result;
function parseNode($a, $b) {
$mit = new MultipleIterator(MultipleIterator::MIT_NEED_ANY|MultipleIterator::MIT_KEYS_NUMERIC);
$mit->attachIterator(new RecursiveIteratorIterator($a, RecursiveIteratorIterator::SELF_FIRST));
$mit->attachIterator(new RecursiveIteratorIterator($b, RecursiveIteratorIterator::SELF_FIRST));
foreach ($mit as $node) {
// One has more nodes than another!
if ( ! isset($node[0], $node[1])) {
return 'Curse your sudden but inevitable betrayal!';
}
// Nodes have different names
if ($node[0]->getName() !== $node[1]->getName()) {
return $node[0];
}
}
// No differences in names and order
return FALSE;
}
Setting up the MultipleIterator is pretty verbose (mostly due to the über-long class names) but the logic is darned simple.
There's no return in the recursive call. Hence, no result.
EDIT Do not up-vote this. ircmaxell is right. I have removed the exception part of my answer.