views:

165

answers:

2

Hi everybody,

I'm implementing a Log system for PHP, and I'm a bit stuck. All the configuration is defined in an XML file, that declares every method to be logged. XML is well parsed and converted into a multidimensionnal array (classname => array of methods). So far, so good.

Let's take a simple example:

#A.php
class A {
    public function foo($bar) {
        echo ' // Hello there !';
    }

public function bar($foo) {
    echo " $ù$ùmezf$z !";
}
}

#B.php
class B {
public function far($boo) {
    echo $boo;  
}
}

Now, let's say I've this configuration file:

<interceptor>
<methods class="__CLASS_DIR__A.php">
        <method name="foo">
    <log-level>INFO</log-level>
    <log-message>Transaction init</log-message>
        </method>
</methods>  
<methods class="__CLASS_DIR__B.php">
        <method name="far">
    <log-level>DEBUG</log-level>
    <log-message>Useless</log-message>
        </method>
</methods>
</interceptor>

The thing I'd like AT RUNTIME ONLY (once the XML parser has done his job) is:

#Logger.php (its definitely NOT a final version) -- generated by the XML parser
class Logger {
public function __call($name,$args) {
    $log_level = args[0];
    $args = array_slice($args,1);
    switch($method_name) {
        case 'foo':
        case 'far':
        //case .....
            //write in log files
            break;

    }
    //THEN, RELAY THE CALL TO THE INITIAL METHOD
 }
}

    #"dynamic" A.php
class A extends Logger {
    public function foo($log_level, $bar) {
    parent::foo($log_level, $bar);
        echo ' // Hello there !';
    }

public function bar($foo) {
    echo " $ù$ùmezf$z !";
}
}

#"dynamic" B.php
class B extends Logger {
public function far($log_level, $boo) {
    parent::far($log_level, $bar);
    echo $boo;  
}
}

The big challenge here is to transform A and B into their "dynamic" versions, once the XML parser has completed its job. The ideal would be to achieve that without modifying the code of A and B at all (I mean, in the files) - or at least find a way to come back to their original versions once the program is finished.

To be clear, I wanna find the most proper way to intercept method calls in PHP. What are your ideas about it ???

Thanks in advance, Rolf

PS: and of course, there should be NO CONSEQUENCE on the client code (no different if interception is enabled or not).

A: 

You could use eval() to actually define the classes, but you should be very careful. The eval() function can be very dangerous.

Something like this:

$parentName = 'Bar';

eval('class Foo extends ' . $parentName . ' { }');

http://php.net/eval

awgy
+1 until somebody posts a way to define a new class without using eval(). Note that the runkit can dynamically change inheritance via [runkit_class_adopt()](http://us3.php.net/manual/en/function.runkit-class-adopt.php).
Adam Backstrom
unfortunately, runkit_class_adopt is part of an external library, which compromises its use once the app's hosted...concerning eval, my classes are already defined before runtime, and I'd like to avoid to add a variable in every class subject that may be in the configuration file...just take a look at the initial post, I explained my situation in a better way...
Rolf
A: 

This solution once again uses eval, but I'll post it for your consideration anyway because I think it's a really nice way of dynamic inheritance.

The method here is to use an intermediary class which extends some default class that can be changed (in this instance to a different class also extending the default).

I'm uncertain as to what it is in your setup that is not allowing for this kind of working -- if you clarify this I could perhaps provide a better recommendation.

<?php

/*
 * One of the following classes will be the superclass of the Child
 */

class Ancestor {
    function speak() {
        echo 'Ancestor <br />';
    }
}

class Mum extends Ancestor {
    function speak() {
        parent::speak();
        echo 'Mum <br />';
    }
}

class Dad extends Ancestor {
    function speak() {
        parent::speak();
        echo 'Dad <br />';
    }
}

/*
 * Decide on which class we wish to make the superclass of our Child
 */

$parentClass = null;

if (isset($_GET['parent'])) {
    $parentClass = $_GET['parent'];
    if (!class_exists($parentClass)) {
        $parentClass = "Ancestor";
    }
}

if (!is_null($parentClass)) {
    eval("class ChildParent extends $parentClass {};");
} else {
    class ChildParent extends Ancestor {};
}

if (class_exists('ChildParent')) {
class Child extends ChildParent
{
    function speak() {
        parent::speak();
        echo 'Child <br />';
    }
}
}

/*
 * Show what's going on
 */

echo '<a href="?">Either</a> | <a href="?parent=Mum">Mum</a> | <a href="?parent=Dad">Dad</a> <br />';

$child = new Child();
$child->speak();*
icio
I updated the initial topic (and its title). You should take a look, to have the whole picture. Thanks to your example, I think I understand how I'm gonna do for the dynamic inheritance (one default superclass, and one generated by the XML parser).
Rolf
You're *not* going to be able to do this without changing your PHP code somewhere. It's either going to be in the class structure or the instantiation of the object itself. The first method as above. The second method as `Neu::MyClass();` instead of `new MyClass();`. So which is it?
icio
Or runkit, of course.
icio