Best way to allow plugins for a PHP application - php

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)

Related

PHP returns object as null occasionally and unintentionally

We have PHP code in production that sometimes fails with "Call to member function on null", although the same code path executes fine several times before that in one invocation. We have a test that reproduces the error consistently at the same run of the loop.
I already proved that the object gets created correctly in the factory even if it gets returned as null. The factory method must not return null in any case, as indicated in the DocBlock. This question is not related to nullable return types or something like that.
The process does not exceed memory or runtime limitations and I already tried turning off the garbage collector, but no luck. The error happens both in PHP 7.0 and 7.3 on Debian, did not try on other versions or operating systems.
I am not allowed to paste the real code here, but I wrote a simple mockup to explain in more detail. Please keep in mind that this demo code will not result in the error, it is just meant to show the general structure of the program that runs into this fault.
// Three placeholder classes with common methods
class Bender
{
public function common()
{
echo "Bend, bend!" . PHP_EOL;
}
}
class Clamper
{
public function common()
{
echo "Clamp, clamp!" . PHP_EOL;
}
}
class Worker
{
public function common()
{
echo "Work, work!" . PHP_EOL;
}
}
// abstract class with static factory to produce objects
abstract class MomCorp
{
/**
* Factory to create one of several objects
*
* #param string $name
* #return Bender|Clamper|Worker
*/
public static function factory($name)
{
$type = self::managementDecision($name);
switch ($type)
{
case "bender":
$robot = new Bender();
break;
case "clamper":
$robot = new Clamper();
break;
default:
$robot = new Worker();
}
// optional QA works flawlessly here, object is fine all the time!
// $robot->common();
return $robot;
}
public static function managementDecision($name)
{
// irrelevant magic happens on $name here
return "bender";
}
}
foreach (['Rodriguez', 'Angle-ine', 'Flexo', 'Billie'] as $newName)
{
echo "$newName: ";
// The next two lines break after some loops - why?
// The perfectly functional object in factory gets returned as null
$bot = MomCorp::factory($newName);
$bot->common();
}
// SAMPLE OUTPUT
// Rodriguez: Bend, bend!
// Angle-ine: Bend, bend!
// Flexo: Bend, bend!
// Billie: Call to a member function common() on null
Has anyone experienced the same and has any hints on what might cause such an error and how to fix it?

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.

Best practice for tiny code reuse in PHP

For a long time I have a problem - should I reuse small parts of code and if so, how should I do it so it would be the best practice.
What I mean about small code is for example:
if (!is_array($table)) {
$table = array($table);
}
or
$x = explode("\n", $file_content);
$lines = array();
for ($i=0, $c = count($x); $i<$c; ++$i) {
$x[$i] = trim($x[$i]);
if ($x[$i] == '') {
continue;
}
$lines[] = $x[$i];
}
Such tiny parts of code may be used in many classes in one project but some of them are used also in many projects.
There are many possible solutions I think:
create simple function file and put them all reusable piece of codes as function, include them simple in project and use them whenever I want
create traits for those piece of codes and use them in classes
reuse code by simple copy paste or creating function in specific class (??)
other ?
I think all of those solutions have their pros and cons.
Question: What method should I use (if any) to reuse such code and why is this approach the best one in your opinion?
I think that "the best way" depends on many factors including the technology your applications use (procedural, OOP), versions of PHP they run on, etc. For example, traits are interesting and useful but they are available only since php 5.4.0 so using this tool to group your code snippets you will not be able to reuse them in systems running on earlier PHP versions. On the other hand if your app uses an OOP style and you organized your resuable small code snippets in functions, their usage may seem awkward in an OOP app and conflict with the function names in a particular class. In this case I think grouping your functions in classes would seem more natural.
Putting everything together, it seems that classes provide better tool for grouping resuable code snippets in terms outline above, namely backward compatibility with earlier PHP versions, avoiding function names conflicts, etc.) Personally I code mostly in OOP, so i have a Util class where I group small functions representing resuable pieces of code snippets that do not directly relate to each other and thus could not be logically groupped in other classes.
As mentioned already traits are a good thing. But might be a bit hard to manage after a while, and it might not be supported everywhere since its new.
What I do is to create Tool classes that have a lot small static functions, like:
class ArrayTools
{
static public function CheckArray($array)
{
if (!is_array($array))
{
$array = array($array);
}
return $array;
}
}
So you can call it with ArrayTools::CheckArray($array)
Please go with traits if your code mainly involves classes and objects.. As the the concept of traits exclusively focusses on code reuse ability.
Following are the code snippets which actually I use with Plain PHP projects, these code snippets are used from various frameworks good traits and best practices.
1.
The following code is used to check the environment in which your working, based on the environment you can set the some global variables, error reporting as so on.
if(!defined('ENVIRONMENT')){
define('ENVIRONMENT','DEVELOPMENT');
}
if (defined('ENVIRONMENT'))
{
switch (ENVIRONMENT)
{
case 'DEVELOPMENT':
case 'TESTING':
$base_url = 'http://localhost/project_name/';
error_reporting(E_ALL);
break;
case 'PRODUCTION':
$base_url = 'http://hostname/project_name/';
error_reporting(0);
break;
default:
exit('The application environment is not set correctly.');
}
}
2.
/* This function is used to PRINT the ARRAY data in the pre formatted manner */
if (!function_exists('pr')) {
function pr($data) {
echo '<pre>', print_r($data), '</pre>';
}
}
3.
/* This function is used to Sanitize the user data and make data safe to insert into the database */
function sanitize($data) {
global $link;
$data = trim($data);
return htmlentities(strip_tags(mysqli_real_escape_string($link, $data)));
}
4.
/* Used to get the difference of 2 arrays
Returns the array with difference
*/
function multi_diff($arr1,$arr2){
$result = array();
foreach ($arr1 as $k=>$v){
if(!isset($arr2[$k])){
$result[$k] = $v;
} else {
if(is_array($v) && is_array($arr2[$k])){
$diff = multi_diff($v, $arr2[$k]);
if(!empty($diff))
$result[$k] = $diff;
}
}
}
return $result;
}
5.
/* This fnction is used to generate the random keys of specific length
Accepts parameter of certain length if not specified it will generate 20 bit length automatically
*/
function generate_random_key($length = 20) {
//Initializing the varialble
$keystring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
$random_key = '';
for ($i = 0; $i < $length; $i++) {
$random_key.=$keystring[rand(0, strlen($keystring) - 1)];
}
//Return the randomly generated key
return $random_key;
}
6.
/* This function outputs the errors in ul>li format with unstyled
* To get the bullets styling remove class='list-unstyled' in <ul> tag */
function output_errors($errors){
$output = array();
foreach ($errors as $error) {
$output[] = '<li>'.$error.'</li>';
}
return '<ul class="list-unstyled">'.implode('', $output).'</ul>';
}
7.
/* Checks whether the user is loggedin else will redirect to the protectect page */
function protected_page(){
if(is_loggedin() === false){
// header('Location: protected.php');
header('Location: logout.php');
exit();
}
}
8.
/* If user tries to access the page directly accessing through the URL,
* If already loggedin then redirect him to any of the inner page
*/
function login_redirect(){
if(is_loggedin() === true){
header('Location: home.php');
}
}
9.
/* This function is used to check whether the user exists or not */
function email_exists($email){
/* Your Code */
}
/* This function is used to check whether the user isActive or not */
function is_active($email){
/* Your Code */
}
/* This function will get the userid from the email */
function userid_from_email($email) {
/* Your Code */
}
/* This fucntion is used to login the user based on the email-id and password */
function login($email,$password){
/* Your Code */
}
/* Check whether the USER is loggedin or not */
function is_loggedin(){
return (isset($_SESSION['userid'])) ? true : false;
}
Hope this helps you. Cheers!

Logging design pattern in a Zend framework application

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

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

Categories