Method interception in PHP 5. *
I am implementing a logging system for PHP and I am a bit stuck.
All configuration is defined in an XML file that declares each method to register. XML is well parsed and converted to multidimensional 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 have this config 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>
What I would like to ONLY AT RUNTIME (once the XML parser has done its job):
#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 problem here is to convert A and B to their "dynamic" versions after the XML parser has finished.
The ideal would be to achieve this without changing the A and B code at all (I mean, in the files) - or at least find a way to revert to the original versions after the program finishes.
To be clear, I want to find the most appropriate way to intercept method calls in PHP.
What are your ideas about this?
PS: and, of course, there should be NO SEQUENCE on the client code (it doesn't matter if interception is enabled or not).
a source to share
This solution uses eval again, but I'll post it for your consideration anyway, because I find it a really nice dynamic inheritance way.
Here you should use an intermediate class that extends the default class that can be modified (in which case the other class also extends the default).
I'm not sure if it's in your setup that doesn't allow this kind of work - if you clarify this, I could offer 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 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();*
a source to share