PHP Class Function List in Comments - php

Whenever I write a class in PHP, I like to put a list of public functions at the top of the class like this:
// add_project($apikey, $post, $email)
// create_user($apikey, $email, $firstname, $lastname, $rolename, $pid)
// user_join_project($apikey, $email, $pid)
I was wondering if there were any tools that can automate this process. Like perhaps I could load the class file and it could generate a list of the function names, variables and such?
Thanks!

Try phpDocumentor. You use DocBlock syntax for comments (similar to Javadoc and in other languages) then pass your PHP source files through the phpDocumentor parser, and it generates API documentation for you.
A rough example:
/**
* Adds a project.
*
* #param string $apikey The API key.
* #param object $post The post.
* #param string $email A supplied email address.
* #return void
*/
function add_project($apikey, $post, $email) {
...
}

There's a not great deal of value in this approach.
First off, if you're not already doing so you should mark each of your methods (and indeed member variables) with the appropriate visibility (public/protected/private). You could then use a solution such as phpDoc, which to properly document each method argument in addition to providing an overall purpose for each method/class, etc.
You can then automatically generate documentation for your project in HTML format (amongst others).

There are various tools for this. They are called PHP documentor's. I use an IDE called PHPStorm that has a documentor integrated.
Here is some more information on PHPDoc.

This can be done pretty easily with Parsekit.
Using this ("tmp.php") as sample data:
<?php
class Fruit
{
public function apple($cored) {}
public function orange($peeled) {}
public function grape($colour, $seedless) {}
}
Here's a simple Parsekit example to dump a class's functions:
<?php
$parsed = parsekit_compile_file($argv[1]);
foreach ($parsed['class_table'] as $class => $classdat) {
foreach ($classdat['function_table'] as $func => $funcdat) {
echo "{$class}::{$func}(";
$first = true;
foreach ($funcdat['arg_info'] as $arg => $argdat) {
if (!$first) {
echo ', ';
}
echo "\${$argdat['name']}";
$first = false;
}
echo ")\n";
}
}
…and here it is in use:
$ php parse.php tmp.php
Fruit::grape($colour, $seedless)
Fruit::orange($peeled)
Fruit::apple($cored)

Related

How do I simulate php://input in PHP?

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

Behat - Using an example table String in an xpath selector array

I have been using Behat for a year or so at a level fine for the automation of most websites but I now need to start using it more for user generated content, I am relatively new to PHP and at the moment I am struggling how to use a String entered in an Example table in an x-path array:
Feature: Campaign
Scenario Outline: Pass campaign string to xpath array
Then I add a new campaign name of "<campaign>"
Examples:
|campaign |
|Automation|
The context file looks like this
/**
* #Then /^I add a new campaign name of "([^"]*)"$/
*/
public function iAddANewCampaignNameOf($campaign)
{
/**
* #var CreateCampaign $createCampaign
*/
$createCampaign= $this->getPage('CreateCampaign');
$createCampaign->campaignName($campaign);
}
Then I use the Page Object extension for the class Campaign.php
class CreateCampaign extends AutomationPage
{
protected $path = 'someURL';
public $campaign;
protected $elements = array(
'campaignHeader' => array('xpath' => "//*[#id='site-navigation-campaigns']"),
);
public function campaignName ($campaign)
{
$this->campaign = $campaign;
$this->getSession()->wait(5000);
$this->getElement('campaignName')->setValue($campaign);
}
So far so good, the tester can enter a campaign name of "Automation" - it gets passed through the context file and the campaign name is set in the browser.
What I am lacking is to be able to retain this $campaign name string and use it in another page so I can reference it in another array i.e. for selecting an existing campaign as follows:
SecondPageObjectPage.php
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
protected $elements = array(
'referenceCampaign' => array('xpath' => "//*[contains(#id,'***HERE I NEED TO GET THE
$campaign value"),
);
public function editExistingCampaign ($campaign)
{
$this->getElement('referenceCampaign')->click();
}
}
I have tried my best to simplify things and I can explain further if any of this isnt clear - hopefully its just a simple PHP question and not really Behat specific
Thanks Ian
Your example is a much better way of doing things, I have only recently started using partial contains and it expands the flexibility of finding stubborn xpaths especially if you combine more than one, like the working example below:
public function editExistingCampaign ($campaign)
{
$this->getSession()->wait(5000);
$element = $this->find('xpath', '//*[contains(#id,"'.$campaign.'")]
[contains(#id,"actionbuttons")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
The only slight change was to add a ] at the end of the x-path
I'm sure it's a simple question, but I think that I am missing a point. If all you want is to get hold of the value that was used on the page then you need to review your code structure. First, you cannot pass method argument to the property definition in another class, but you can find the element inside editExistingCampaign.
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
public function editExistingCampaign ($campaign)
{
$element = $this->find('xpath', '//*[contains(#id, "' . $campaign . '")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
}
I'm assuming you are using Symfony Page Object extension, which you should mention. I'm not sure if I've got the syntax right, but the idea is to find your element inside the method.

Using $this when not in object context error within array_walk

I am experiencing an odd problem with using array_walk with closures within a class. The problem does not arise within my development environment using php version 5.4.7 but it does on my deployment environment 5.3.3.
The following code runs fine on my production box, but crashes on my deployment environment:
<?php
error_reporting(-1);
Class TestArrayWalk
{
/** #var null|array */
protected $userInput = null;
/**
* This expects to be passed an array of the users input from
* the input fields.
*
* #param array $input
* #return void
*/
public function setUserInput( array $input )
{
$this->userInput = $input;
// Lets explode the users input and format it in a way that this class
// will use for marking
array_walk( $this->userInput, function( &$rawValue )
{
$rawValue = array(
'raw' => $rawValue,
'words' => $this->splitIntoKeywordArray( $rawValue ),
'marked' => false,
'matched' => array()
);
}
);
}
public function getUserInput()
{
return $this->userInput;
}
protected function splitIntoKeywordArray( $input )
{
if ( ! is_string( $input )){ return array(); }
return preg_split('/(\s|[\.,\/:;!?])/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
}
$testArrayWalk = new TestArrayWalk();
$testArrayWalk->setUserInput(
array(
'This is a test input',
'This is another test input'
)
);
var_dump( $testArrayWalk->getUserInput() );
The error I am getting is: Using $this when not in object context on line 26 which is the only usage of $this within that test class. I am assuming something changed between the versions I am using that has made the above code possible in my development environment.
I also assume that as I cant change the deployment environment (its the clients and they wont change it) that I am going to have to use a foreach rather than array_walk.
My question is this: Given the above, is this possible in 5.3.3 using array_walk if not how do I use foreach in the same way that I am using array_walk ( more specifically the &$rawValue bit)?
My environments are:
My development environment is PHP version 5.4.7
My server (deployment) environment is PHP version 5.3.3
Thanks.
Edit 2
Thanks to everyone who helped. With your help I got this working and have posted my working code to https://gist.github.com/carbontwelve/6727555 for future reference.
This is described in PHP manual:
Version Description
5.4.0 $this can be used in anonymous functions.
Anonymous functions
Possible workaround would be to re-assign this to another variable and pass it via use:
$_this = $this;
function() use($_this) { ... }
but keep in mind you will not be able to access private and protected members so you will have to make splitIntoKeywordArray public
You may also use if you are using PHP < 5.4
public function setUserInput( array $input )
{
$this->userInput = $input;
$userInput_array = &$this->userInput;
array_walk( &$userInput_array, function( &$rawValue ) use (&$userInput_array) {
// use $userInput_array instaed of $this->userInput_array
});
}
I had a similar problem with a lambda function defined in a private function within a class:
MyClass{
private $str="doesn't work :-(";
private function foo(){
$bar=function(){
echo $this->str; // triggers an "Using $this when not in object context" error
};
$bar();
}
}
Solution for PHP 5.3.0: declare a variable $obj=&$this in your parent scope (ie. the private function) and pass it to the anonymouse function by using the use language construct. Also make sure the function/variable that you access has a public visibility (protected|private might not work).
MyClass{
public $str="it just works :-)";
private function foo(){
$obj=&$this;
$bar=function() use(&$obj){
echo $this->str; // it works!
};
$bar();
}
}

How do I write unit tests in PHP? [closed]

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

Best way to allow plugins for a PHP application

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)

Categories