Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I've read everywhere about how great they are, but for some reason I can't seem to figure out how exactly I'm supposed to test something. Could someone perhaps post a piece of example code and how they would test it? If it's not too much trouble :)
There is a 3rd "framework", which is by far easier to learn - even easier than SimpleTest, it's called phpt.
A primer can be found here:
http://qa.php.net/write-test.php
Edit: Just saw your request for sample code.
Let's assume you have the following function in a file called lib.php:
<?php
function foo($bar)
{
return $bar;
}
?>
Really simple and straight forward, the parameter you pass in, is returned. So let's look at a test for this function, we'll call the test file foo.phpt:
--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"
In a nutshell, we provide the parameter $bar with value "Hello World" and we var_dump() the response of the function call to foo().
To run this test, use: pear run-test path/to/foo.phpt
This requires a working install of PEAR on your system, which is pretty common in most circumstances. If you need to install it, I recommend to install the latest version available. In case you need help to set it up, feel free to ask (but provide OS, etc.).
There are two frameworks you can use for unit testing. Simpletest and PHPUnit, which I prefer. Read the tutorials on how to write and run tests on the homepage of PHPUnit. It is quite easy and well described.
You can make unit testing more effective by changing your coding style to accommodate it.
I recommend browsing the Google Testing Blog, in particular the post on Writing Testable Code.
I rolled my own because i didnt have time to learn someone elses way of doing things, this took about 20 minutes to write up, 10 to adapt it for posting here.
Unittesting is very usefull to me.
this is kinda long but it explains itself and there is an example at the bottom.
/**
* Provides Assertions
**/
class Assert
{
public static function AreEqual( $a, $b )
{
if ( $a != $b )
{
throw new Exception( 'Subjects are not equal.' );
}
}
}
/**
* Provides a loggable entity with information on a test and how it executed
**/
class TestResult
{
protected $_testableInstance = null;
protected $_isSuccess = false;
public function getSuccess()
{
return $this->_isSuccess;
}
protected $_output = '';
public function getOutput()
{
return $_output;
}
public function setOutput( $value )
{
$_output = $value;
}
protected $_test = null;
public function getTest()
{
return $this->_test;
}
public function getName()
{
return $this->_test->getName();
}
public function getComment()
{
return $this->ParseComment( $this->_test->getDocComment() );
}
private function ParseComment( $comment )
{
$lines = explode( "\n", $comment );
for( $i = 0; $i < count( $lines ); $i ++ )
{
$lines[$i] = trim( $lines[ $i ] );
}
return implode( "\n", $lines );
}
protected $_exception = null;
public function getException()
{
return $this->_exception;
}
static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
{
$result = new self();
$result->_isSuccess = false;
$result->testableInstance = $object;
$result->_test = $test;
$result->_exception = $exception;
return $result;
}
static public function CreateSuccess( Testable $object, ReflectionMethod $test )
{
$result = new self();
$result->_isSuccess = true;
$result->testableInstance = $object;
$result->_test = $test;
return $result;
}
}
/**
* Provides a base class to derive tests from
**/
abstract class Testable
{
protected $test_log = array();
/**
* Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
**/
protected function Log( TestResult $result )
{
$this->test_log[] = $result;
printf( "Test: %s was a %s %s\n"
,$result->getName()
,$result->getSuccess() ? 'success' : 'failure'
,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
,$result->getComment()
,$result->getTest()->getStartLine()
,$result->getTest()->getEndLine()
,$result->getTest()->getFileName()
)
);
}
final public function RunTests()
{
$class = new ReflectionClass( $this );
foreach( $class->GetMethods() as $method )
{
$methodname = $method->getName();
if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
{
ob_start();
try
{
$this->$methodname();
$result = TestResult::CreateSuccess( $this, $method );
}
catch( Exception $ex )
{
$result = TestResult::CreateFailure( $this, $method, $ex );
}
$output = ob_get_clean();
$result->setOutput( $output );
$this->Log( $result );
}
}
}
}
/**
* a simple Test suite with two tests
**/
class MyTest extends Testable
{
/**
* This test is designed to fail
**/
public function TestOne()
{
Assert::AreEqual( 1, 2 );
}
/**
* This test is designed to succeed
**/
public function TestTwo()
{
Assert::AreEqual( 1, 1 );
}
}
// this is how to use it.
$test = new MyTest();
$test->RunTests();
This outputs:
Test: TestOne was a failure
/**
* This test is designed to fail
**/ (lines:149-152; file:/Users/kris/Desktop/Testable.php)
Test: TestTwo was a success
Get PHPUnit. It is very easy to use.
Then start with very simple assertions. You can do alot with AssertEquals before you get into anything else. That's a good way to get your feet wet.
You may also want to try writing your test first (since you gave your question the TDD tag) and then write your code. If you haven't done this before it is an eye-opener.
require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';
class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
private $ClassYouWantToTest;
protected function setUp ()
{
parent::setUp();
$this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
}
protected function tearDown ()
{
$this->ClassYouWantToTest = null;
parent::tearDown();
}
public function __construct ()
{
// not really needed
}
/**
* Tests ClassYouWantToTest->methodFoo()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);
/**
* Tests ClassYouWantToTest->methodBar()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}
For simple tests AND documentation, php-doctest is quite nice and it's a really easy way to get started since you don't have to open a separate file. Imagine the function below:
/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
return $a + $b;
}
If you now run this file through phpdt (command-line runner of php-doctest) 1 test will be run. The doctest is contained inside the < code > block. Doctest originated in python and is fine for giving useful & runnable examples on how the code is supposed to work. You can't use it exclusively because the code itself would litter up with test cases but I've found that it's useful alongside a more formal tdd library - i use phpunit.
This 1st answer here sums it up nicely (it's not unit vs doctest ).
phpunit is pretty much the defacto unit testing framework for php. there is also DocTest (available as a PEAR package) and a few others.
php itself is tested for regressions and the like via phpt tests which can also be run via pear.
Codeception tests are much like common unit tests but are much powerful in things where you need mocking and stubbing.
Here is the sample controller test. Notice how easily stubs are created. How easily you check the method was invoked.
<?php
use Codeception\Util\Stub as Stub;
const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;
class UserControllerCest {
public $class = 'UserController';
public function show(CodeGuy $I) {
// prepare environment
$I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
$I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
$I->setProperty($controller, 'db', $db);
$I->executeTestedMethodOn($controller, VALID_USER_ID)
->seeResultEquals(true)
->seeMethodInvoked($controller, 'render');
$I->expect('it will render 404 page for non existent user')
->executeTestedMethodOn($controller, INVALID_USER_ID)
->seeResultNotEquals(true)
->seeMethodInvoked($controller, 'render404','User not found')
->seeMethodNotInvoked($controller, 'render');
}
}
Also there are other cool things. You can test database state, filesystem, etc.
Besides the excellent suggestions about test frameworks already given, are you building your application with one of the PHP web frameworks that has automated testing built in, such as Symfony or CakePHP? Sometimes having a place to just drop in your test methods reduces the start-up friction some people associate with automated testing and TDD.
Way too much to re-post here, but here is a great article on using phpt. It covers a number of aspects around phpt that are often overlooked, so it could be worth a read to expand your knowledge of php beyond just writing a test. Fortunately the article also discusses writing tests!
The main points of discussion
Discover how marginally documented aspects of PHP work (or pretty much any part for that matter)
Write simple unit tests for your own PHP code
Write tests as part of an extension or to convey a potential bug to the internals or QA groups
I know there is a lot of info here already, but since this still shows up on Google searches i might as well add Chinook Test Suite to the list. It is a simple and small test framework.
You can easily test your classes with it and also create mock objects. You run the tests through a web browser and (not yet) through a console.
In the browser you can specify what test class or even what test method to run. Or you can simply run all tests.
A screenshot from the github page:
What i like about it is the way you assert tests. This is done with so called "fluent assertions". Example:
$this->Assert($datetime)->Should()->BeAfter($someDatetime);
And creating mock objects is a breeze too (with a fluent like syntax):
$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');
Anyway, more info can be found on the github page with a code example as well:
https://github.com/w00/Chinook-TestSuite
Related
I'm writing an unit test for my PHP project,
the unit test is to simulate a php://input data,
and I read the manual, it says:
php://input is a read-only stream that allows you to read raw data
from the request body.
How do I simulate the php://input, or write the request body in my PHP?
Here's my source code and unit test, both are simplified.
Source:
class Koru
{
static function build()
{
// This function will build an array from the php://input.
parse_str(file_get_contents('php://input'), $input);
return $input;
}
//...
Unit Test:
function testBuildInput()
{
// Trying to simulate the `php://input` data here.
// NOTICE: THIS WON'T WORK.
file_put_contents('php://input', 'test1=foobar&test2=helloWorld');
$data = Koru::build();
$this->assertEquals($data, ['test1' => 'foobar',
'test2' => 'helloWorld']);
}
Use a test double
Given the code in the question, the simplest solution is to restructure the code:
class Koru
{
static function build()
{
parse_str(static::getInputStream(), $input);
return $input;
}
/**
* Note: Prior to PHP 5.6, a stream opened with php://input could
* only be read once;
*
* #see http://php.net/manual/en/wrappers.php.php
*/
protected static function getInputStream()
{
return file_get_contents('php://input');
}
And use a test double:
class KoruTestDouble extends Koru
{
protected static $inputStream;
public static function setInputStream($input = '')
{
static::$inputStream = $input;
}
protected static function getInputStream()
{
return static::$inputStream;
}
}
The test method then uses the test double, not the class itself:
function testBuildInput()
{
KoruTestDouble::setInputStream('test1=foobar&test2=helloWorld');
$expected = ['test1' => 'foobar', 'test2' => 'helloWorld'];
$result = KoruTestDouble::build();
$this->assertSame($expected, $result, 'Stuff be different');
}
Avoid static classes if possible
Most of the difficulties with the scenario in the question are caused by the use of static class methods, static classes make testing hard. If at all possible avoid the use of static classes and use instance methods which allows solving the same sort of problem using mock objects.
See vfsStream package and this SO question and answers.
Basically, you would want to parametrize your service that reads data to accept a path:
public function __construct($path)
{
$data = file_get_contents($path); // you might want to use another FS read function here
}
And then, in a test, provide an vfsStream stream path:
\vfsStreamWrapper::register();
\vfsStream::setup('input');
$service = new Service('vfs://input')
In your code you would provide php://input as per usual.
This sort of extreme decomposition gains nothing and leads very brittle code. Your tests should express the expectations of your interfaces, and not the data you've supplied them with: Is PHP truly not free to return ["test2"=>"helloWorld","test1"=>"foobar"] in some future version? Is your code broken if it does? What exactly do you think you are testing?
I think you're overcomplicating this.
$a->doit should take $input as an argument and not call Koru::build as part of its initialisation. Then you can test $a->doit instead of testing parse_str.
If you insist on pressing on this example, then Koru::build needs to take an argument of 'php://input' – this is often called dependency injection, where you tell your functions everything they need to know. Then, when you want to "test" things, you can simply pass in some other file (or e.g. a data url).
With Kahlan you can monkey patch the file_get_contents function directly like so:
use My\Name\Space\Koru;
describe("::build()", function() {
it("parses data", function() {
allow('file_put_contents')->toBeCalled()->andRun(function() {
return 'test1=foobar&test2=helloWorld';
});
expect(Koru::build())->toBe([
'test1' => 'foobar',
'test2' => 'helloWorld'
]);
});
});
Use a Zend\Diactoros\Stream
https://zendframework.github.io/zend-diactoros/usage/
$_POST['foo'] = 'bar';
use Zend\Diactoros\ServerRequestFactory;
$psrRequest = ServerRequestFactory::fromGlobals();
var_dump($psrRequest->getParsedBody()); // foo => bar
var_dump($_POST); // foo => bar
more info https://laracasts.com/discuss/channels/general-discussion/psr-7?page=1
during unit testing i'm always get confused about what to test.
Do i need to test the API and only the API or also the method result values.
class SomeEventHandler
{
public function onDispatch (Event $event)
{
if ($event->hasFoo)
{
$model = $this->createResponseModel('foo');
}
else
{
$model = $this->createResponseModel('bar');
}
// End.
return $model;
}
private function createResponseModel ($foo)
{
$vars = array(
'someVare' => true,
'foo' => $foo
);
// End.
return new ResponseModel($vars);
}
}
So should i test if the method onDispatch returns a instance of ResponseModel or should i also test if the variable foo is set properly?
Or is the test below just fine?
class SomeEventHandlerTest
{
// assume that a instance of SomeEventHandler is created
private $someEventHandler;
public function testOnDispatch_EventHasFoo_ReturnsResponseModel ()
{
$e = new Event();
$e->hasFoo = true;
$result = $someEventHandler->onDispatch($e);
$this->assertInstanceOf('ResponseModel', $result);
}
public function testOnDispatch_EventHasNoFoo_ReturnsResponseModel ()
{
$e = new Event();
$e->hasFoo = false;
$result = $someEventHandler->onDispatch($e);
$this->assertInstanceOf('ResponseModel', $result);
}
}
If you were checking the code by hand what is it that you would check? Just that a ResponseModel was returned or that it also had the proper values?
If you weren't writing tests and executed the code what would you look for to ensure that the code was doing what it was supposed to. You would check that the values in the returned object were correct. I would do that by using the public API of the object and verify that the values are right.
One idea is to have the tests such that if the code were deleted, you would be able to recreate all the functionality via only having the tests. Only checking the returned object could result in a function that just has return new ResponseModel();. This would pass the test but would not be what you want.
In short, what you decide to test is subjective, however you should at the minimum test all your public methods.
Many people limit their tests to public methods and simply ensure code coverage on the protected/private methods is adequate. However, feel free to test anything you think warrants a test. Generally speaking, the more tests the better.
In my opinion you should certainly test for your response data, not just the return type.
I rely on Unit Tests to let me make code changes in the future and be satisfied my changes have not created any breaks, just by running the tests.
So in your case, if the "foo" or "bar" response data is important, you should test it.
That way if you later change the response strings by accident, your tests will tell you.
I have an interesting problem and have searched the internet, but haven't yet found an answer.
I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.
Consider the following function:
function get_setting_values_from_file( $parameter )
{
exec("/usr/var/binary --options $parameter", $output, $return);
$settings = file( $output[0] );
foreach( $settings as $setting ) {
if( strstr( $setting, "color") ) {
$setting = explode( ":", $setting );
return $setting[1];
}
}
return false;
}
I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?
All feedback is appreciated.
You could mock exec() by using a function mock library. I made one (php-mock) for you which requires you to use namespaces
namespace foo;
use phpmock\phpunit\PHPMock;
class ExecTest extends \PHPUnit_Framework_TestCase
{
use PHPMock;
public function testExec()
{
$mock = $this->getFunctionMock(__NAMESPACE__, "exec");
$mock->expects($this->once())->willReturnCallback(
function ($command, &$output, &$return_var) {
$this->assertEquals("foo", $command);
$output = "failure";
$return_var = 1;
}
);
exec("foo", $output, $return_var);
$this->assertEquals("failure", $output);
$this->assertEquals(1, $return_var);
}
}
Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.
For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.
$stub = $this->getMock('GetSettingsClass');
$stub->expects($this->any())
->method('get_settings_from_file')
->will($this->returnValue(0));
This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs
Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions.
Assumed Example in the main code:
...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;
function get_user_type($settings)
{
if($settings !== FALSE) // Returned from your function if parameter is not found
{
switch($settings)
{
case 1:
return 'User'; // Best to use Constants, but for example here only
break;
case 2:
return 'Admin';
break;
...
}
}
else
{
return FALSE;
}
}
Now, in your test, you can simply
$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...
These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.
This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.
I hope this helps to explain what I was thinking.
I'm building an application using Zend Framework. The application requires intensive logging for every action or function in the code.
so my code most of the time looks like this:
function SendMailsAction(){
$logger->log('Started sending mails.')
...
...
...Some Code...
...
...
foreach ($mails as $mail){
try{
$logger->log('Trying to send')
$mail->send()
$logger->log('Mail sent successfully.')
}catch(Exception $e){
$logger->log('Failed to send mail.')
}
}
...
...
...Some Code...
...
...
$logger->log('Finished sending mails.')
}
Sometimes I even have to log in 2 tables, so most of the logging code is doubled and the functions start to get complicated and long.
I use Zend framework's Zend_Log for logging so my problem is not the logging class itself, But it's how to separate the logging code from the code functionality itself and maintain separation of concerns.
Some people suggested Aspect Oriented Programming (AOP), but unfortunately AOP for PHP isn't acceptable for my costumer, so I'm looking for an Object Oriented solution or best practice.
Note:
Just to make things clear my problem isn't how to use Zend_Log, but how to add logging to my application code in general.
Sometimes I even have to log in 2 tables, so most of the logging code is doubled and the functions start to get complicated and long.
It'll be long. If your code does a lot of logging, it will be long, as it will have to log, and each line action it logs, will mean there's a line in your code. It shouldn't, however, be complicated in any case as logging is one of the most straightforward thing you can do. What worries me, is that you mention "sometimes I even have to log in 2 tables". In my book, one, two, five, sixty or one thousand tables is performed by one line. Code is not doubled for each logger. If you're copy-pasting a line, and changing $log to $log2, you're clearly doing it wrong (tm).
Some people suggested Aspect Oriented Programming (AOP), but unfortunately AOP for PHP isn't acceptable for my costumer, so I'm looking for an Object Oriented solution or best practice.
It is nice, AOP. It has downsides though; as with the debug_backtrace approach, there's a heavy performance hit. That, plus the code becomes increasingly more "magical" in that it does things that aren't clear when you're looking at the code itself. That increases the time you're debugging your application.
My $0.02? First of all, don't repeat yourself: one log entry per action should be enough. Use flexible loggers that can be attached to certain classes at runtime. Decide whether or not to actually log the message in the logger, based on "severity" or "type". All in all, just implement the Observer pattern:
<?php
namespace Foo;
class MailService {
public function attach( Observes $observer ) {
$this->observers[] = $observer;
}
public function notify( $message, $type = 'notice' ) {
foreach( $this->observers as $observer ) {
$observer->notify( $message, $type );
}
}
public function sendMail( ) {
$this->notify( 'Started sending mails', 'debug' );
$mails = array( );
foreach( $mails as $mail ) {
try {
$this->notify( 'Trying to send', 'debug' );
$mail->send( );
$this->notify( 'Mail sent succesfully', 'debug' );
}
catch( Exception $e ) {
$this->notify( 'Failed to send mail', 'notice' );
}
}
$this->notify( 'Finished sending mail', 'debug' );
}
}
interface Observes {
public function notify( $message, $type = 'notice' );
}
abstract class Logger implements Observes {
protected $types = array(
'debug' => 0,
'notice' => 1,
'warning' => 2,
'error' => 3
);
protected function code( $type ) {
return isset( $this->types[$type] ) ? $this->types[$type] : 0;
}
}
class FileLogger extends Logger implements Observes {
public function __construct( $filename ) {
$this->filename = $filename;
}
/**
* #todo replace the method body with a call to, say, file_put_contents.
*/
public function notify( $message, $type = 'notice' ) {
if( $this->code( $type ) > $this->code( 'notice' ) ) { // only for warning and error.
echo $message . "\n";
}
}
}
class DebugLogger extends Logger implements Observes {
public function notify( $message, $type = 'notice' ) {
if( $this->code( $type ) === $this->code( 'debug' ) ) { // only show "debug" notices.
echo $message . "\n";
}
}
}
$service = new MailService( );
$service->attach( new FileLogger( 'yourlog.txt' ) );
$service->attach( new DebugLogger( ) );
$service->sendMail( );
If you don't want to use any external tools you can write some sort of observer wrapper around debug_backtrace that loops through the backtrace and compares all function calls to an array map provided by the wrapper and if it's a hit, writes the respective log message with your custom message text. This would be a complete separation of code where you would just need to run this observer class at the end of every script.
As for an example I think all you need is in the examples of the PHP manual. Still, here is some pseudo-code to illuminate what I mean:
//use register_shutdown_function to register your logger function
function scanTrace(Zend_Log $logger, array $eventMap)
{
$trace = array_reverse(debug_backtrace());
foreach ($trace as $step)
{
//1. extract the needed info
//2. check if the event is in your eventMap
//3. if yes, log it
}
}
The eventMap should already contain the message you want to log for every event.
If you don't mind using an external tool (which I think is the better option) then you could work with xdebug and WebGrind or similar.
Btw: You may be interested in monitorix, which is an extension of Zend_Log with a lot of nice automated logging to a db table (like slow query logging, php error and exception logging, javascript error logging).
I have logging in my Zend2 service with the help of my Go! AOP PHP library. It fast enough and allows me to debug original source code with XDebug in development mode. However, it's only beta, be aware!
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\AfterThrowing;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
/**
* Logging aspect
*/
class LoggingAspect implements Aspect
{
/**
* #var Zend\Log\Logger
*/
protected $logger = null;
/**
* Constructs a logging aspect
*/
public function __construct()
{
$logger = new Zend\Log\Logger;
$writer = new Zend\Log\Writer\Stream('php://output');
$logger->addWriter($writer);
$this->logger = $logger;
}
/**
* Method that will be called before real method
*
* #param MethodInvocation $invocation Invocation
* #Before("execution(public ClassName->*(*))")
*/
public function beforeMethodExecution(MethodInvocation $invocation)
{
$msg = 'Before: '. $this->formatMessage($invocation);
$this->logger->log(Zend\Log\Logger::INFO, $msg);
}
/**
* Method that will be called after throwing an exception in the real method
*
* #param MethodInvocation $invocation Invocation
* #AfterThrowing("execution(public ClassName->*(*))")
*/
public function afterThrowingMethodExecution(MethodInvocation $invocation)
{
$msg = 'After throwing: '. $this->formatMessage($invocation);
$this->logger->log(Zend\Log\Logger::ERR, $msg);
}
/**
* Format a message from invocation
*
* #param MethodInvocation $invocation
* #return string
*/
protected function formatMessage(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
return is_object($obj) ? get_class($obj) : $obj .
$invocation->getMethod()->isStatic() ? '::' : '->' .
$invocation->getMethod()->getName() .
'()' .
' with arguments: ' .
json_encode($invocation->getArguments()) .
PHP_EOL;
}
}
```
you know that zend log can have more than one writer http://framework.zend.com/manual/en/zend.log.writers.html#zend.log.writers.compositing
its simple as creating a class single function
class logger {
public function log ($value , $type , $bothlogger = false){
$writer1 = new Zend_Log_Writer_Stream('/path/to/first/logfile');
$writer2 = new Zend_Log_Writer_Stream('/path/to/second/logfile');
$logger = new Zend_Log();
$logger->addWriter($writer1);
if($bothlogger === true){
$logger->addWriter($writer2);
}
// goes to both writers
$logger->info('Informational message');
return true;
}
}
of course you can modify this sample to be faster with many ways , but it should explain the idea
I am starting a new web application in PHP and this time around I want to create something that people can extend by using a plugin interface.
How does one go about writing 'hooks' into their code so that plugins can attach to specific events?
You could use an Observer pattern. A simple functional way to accomplish this:
<?php
/** Plugin system **/
$listeners = array();
/* Create an entry point for plugins */
function hook() {
global $listeners;
$num_args = func_num_args();
$args = func_get_args();
if($num_args < 2)
trigger_error("Insufficient arguments", E_USER_ERROR);
// Hook name should always be first argument
$hook_name = array_shift($args);
if(!isset($listeners[$hook_name]))
return; // No plugins have registered this hook
foreach($listeners[$hook_name] as $func) {
$args = $func($args);
}
return $args;
}
/* Attach a function to a hook */
function add_listener($hook, $function_name) {
global $listeners;
$listeners[$hook][] = $function_name;
}
/////////////////////////
/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');
function my_plugin_func1($args) {
return array(4, 5);
}
function my_plugin_func2($args) {
return str_replace('sample', 'CRAZY', $args[0]);
}
/////////////////////////
/** Sample Application **/
$a = 1;
$b = 2;
list($a, $b) = hook('a_b', $a, $b);
$str = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";
$str = hook('str', $str);
echo $str;
?>
Output:
This is my CRAZY application
4 + 5 = 9
4 * 5 = 20
Notes:
For this example source code, you must declare all your plugins before the actual source code that you want to be extendable. I've included an example of how to handle single or multiple values being passed to the plugin. The hardest part of this is writing the actual documentation which lists what arguments get passed to each hook.
This is just one method of accomplishing a plugin system in PHP. There are better alternatives, I suggest you check out the WordPress Documentation for more information.
So let's say you don't want the Observer pattern because it requires that you change your class methods to handle the task of listening, and want something generic. And let's say you don't want to use extends inheritance because you may already be inheriting in your class from some other class. Wouldn't it be great to have a generic way to make any class pluggable without much effort? Here's how:
<?php
////////////////////
// PART 1
////////////////////
class Plugin {
private $_RefObject;
private $_Class = '';
public function __construct(&$RefObject) {
$this->_Class = get_class(&$RefObject);
$this->_RefObject = $RefObject;
}
public function __set($sProperty,$mixed) {
$sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
$this->_RefObject->$sProperty = $mixed;
}
public function __get($sProperty) {
$asItems = (array) $this->_RefObject;
$mixed = $asItems[$sProperty];
$sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
return $mixed;
}
public function __call($sMethod,$mixed) {
$sPlugin = $this->_Class . '_' . $sMethod . '_beforeEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
if ($mixed != 'BLOCK_EVENT') {
call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
$sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
if (is_callable($sPlugin)) {
call_user_func_array($sPlugin, $mixed);
}
}
}
} //end class Plugin
class Pluggable extends Plugin {
} //end class Pluggable
////////////////////
// PART 2
////////////////////
class Dog {
public $Name = '';
public function bark(&$sHow) {
echo "$sHow<br />\n";
}
public function sayName() {
echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
}
} //end class Dog
$Dog = new Dog();
////////////////////
// PART 3
////////////////////
$PDog = new Pluggable($Dog);
function Dog_bark_beforeEvent(&$mixed) {
$mixed = 'Woof'; // Override saying 'meow' with 'Woof'
//$mixed = 'BLOCK_EVENT'; // if you want to block the event
return $mixed;
}
function Dog_bark_afterEvent(&$mixed) {
echo $mixed; // show the override
}
function Dog_Name_setEvent(&$mixed) {
$mixed = 'Coco'; // override 'Fido' with 'Coco'
return $mixed;
}
function Dog_Name_getEvent(&$mixed) {
$mixed = 'Different'; // override 'Coco' with 'Different'
return $mixed;
}
////////////////////
// PART 4
////////////////////
$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;
In Part 1, that's what you might include with a require_once() call at the top of your PHP script. It loads the classes to make something pluggable.
In Part 2, that's where we load a class. Note I didn't have to do anything special to the class, which is significantly different than the Observer pattern.
In Part 3, that's where we switch our class around into being "pluggable" (that is, supports plugins that let us override class methods and properties). So, for instance, if you have a web app, you might have a plugin registry, and you could activate plugins here. Notice also the Dog_bark_beforeEvent() function. If I set $mixed = 'BLOCK_EVENT' before the return statement, it will block the dog from barking and would also block the Dog_bark_afterEvent because there wouldn't be any event.
In Part 4, that's the normal operation code, but notice that what you might think would run does not run like that at all. For instance, the dog does not announce it's name as 'Fido', but 'Coco'. The dog does not say 'meow', but 'Woof'. And when you want to look at the dog's name afterwards, you find it is 'Different' instead of 'Coco'. All those overrides were provided in Part 3.
So how does this work? Well, let's rule out eval() (which everyone says is "evil") and rule out that it's not an Observer pattern. So, the way it works is the sneaky empty class called Pluggable, which does not contain the methods and properties used by the Dog class. Thus, since that occurs, the magic methods will engage for us. That's why in parts 3 and 4 we mess with the object derived from the Pluggable class, not the Dog class itself. Instead, we let the Plugin class do the "touching" on the Dog object for us. (If that's some kind of design pattern I don't know about -- please let me know.)
The hook and listener method is the most commonly used, but there are other things you can do. Depending on the size of your app, and who your going to allow see the code (is this going to be a FOSS script, or something in house) will influence greatly how you want to allow plugins.
kdeloach has a nice example, but his implementation and hook function is a little unsafe. I would ask for you to give more information of the nature of php app your writing, And how you see plugins fitting in.
+1 to kdeloach from me.
Here is an approach I've used, it's an attempt to copy from Qt signals/slots mechanism, a kind of Observer pattern.
Objects can emit signals.
Every signal has an ID in the system - it's composed by sender's id + object name
Every signal can be binded to the receivers, which simply is a "callable"
You use a bus class to pass the signals to anybody interested in receiving them
When something happens, you "send" a signal.
Below is and example implementation
<?php
class SignalsHandler {
/**
* hash of senders/signals to slots
*
* #var array
*/
private static $connections = array();
/**
* current sender
*
* #var class|object
*/
private static $sender;
/**
* connects an object/signal with a slot
*
* #param class|object $sender
* #param string $signal
* #param callable $slot
*/
public static function connect($sender, $signal, $slot) {
if (is_object($sender)) {
self::$connections[spl_object_hash($sender)][$signal][] = $slot;
}
else {
self::$connections[md5($sender)][$signal][] = $slot;
}
}
/**
* sends a signal, so all connected slots are called
*
* #param class|object $sender
* #param string $signal
* #param array $params
*/
public static function signal($sender, $signal, $params = array()) {
self::$sender = $sender;
if (is_object($sender)) {
if ( ! isset(self::$connections[spl_object_hash($sender)][$signal])) {
return;
}
foreach (self::$connections[spl_object_hash($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
else {
if ( ! isset(self::$connections[md5($sender)][$signal])) {
return;
}
foreach (self::$connections[md5($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
self::$sender = null;
}
/**
* returns a current signal sender
*
* #return class|object
*/
public static function sender() {
return self::$sender;
}
}
class User {
public function login() {
/**
* try to login
*/
if ( ! $logged ) {
SignalsHandler::signal(this, 'loginFailed', 'login failed - username not valid' );
}
}
}
class App {
public static function onFailedLogin($message) {
print $message;
}
}
$user = new User();
SignalsHandler::connect($user, 'loginFailed', array($Log, 'writeLog'));
SignalsHandler::connect($user, 'loginFailed', array('App', 'onFailedLogin'));
$user->login();
?>
I believe the easiest way would be to follow Jeff's own advice and have a look around the existing code. Try looking at WordPress, Drupal, Joomla, and other well-known PHP-based CMS to see how their API hooks look and feel. This way you can even get ideas you may have not thought of previously to make things a little more robust.
A more direct answer would be to write general files that they would "include_once" into their file that would provide the usability they would need. This would be broken up into categories and NOT provided in one MASSIVE "hooks.php" file. Be careful though, because what ends up happening is that files that they include end up having more and more dependencies and functionality improves. Try to keep API dependencies low. I.E fewer files for them to include.
There's a neat project called Stickleback by Matt Zandstra at Yahoo that handles much of the work for handling plugins in PHP.
It enforces the interface of a plugin class, supports a command line interface and isn't too hard to get up and running - especially if you read the cover story about it in the PHP architect magazine.
Good advice is to look how other projects have done it. Many call for having plugins installed and their "name" registered for services (like wordpress does) so you have "points" in your code where you call a function that identifies registered listeners and executes them. A standard OO design patter is the Observer Pattern, which would be a good option to implement in a truly object oriented PHP system.
The Zend Framework makes use of many hooking methods, and is very nicely architected. That would be a good system to look at.
I am surprised that most of the answers here seem to be geared about plugins that are local to the web application, ie, plugins that run on the local web server.
What about if you wanted the plugins to run on a different - remote - server? The best way to do this would be to provide a form that allows you to define different URLs that would be called when particular events occur in your application.
Different events would send different information based on the event that just occurred.
This way, you would just perform a cURL call to the URL that has been provided to your application (eg over https) where remote servers can perform tasks based on information that has been sent by your application.
This provides two benefits:
You don't have to host any code on your local server (security)
The code can be on remote servers (extensibility) in different languages other then PHP (portability)