<?php
/**
* Abstract class friendly_class
* This abstract class provides friend support to PHP5 classes.
* Copyright (C) 2008 Rubens Takiguti Ribeiro
*
* Description:
* This abstract class use __call PHP magic method to provide friend support
* to protected methods. The __call method in this class can call protected
* methods of child classes, but not private methods. If you want to provide
* friend support to private methods, you should copy these methods directly
* in your class. In this case, you should not use "extends" feature.
* If your class already has the __call method, it should call the parent
* method at the end (example: "parent::__call($method, $args)").
*
* How to provide friend support to PROTECTED methods:
* 1 - Create a class that extends this one;
* 2 - Add protected flag to friendly methods;
* 3 - Implements is_friendly_method to determine wich method is friendly;
* 4 - Implements is_friend_class to determine wich class is friend of
* specified method (optional);
* 5 - Implements is_friend_function to determine wich function is friend of
* specified method (optional);
* 6 - Call friendly methods using "friend_" prefix from friend classes or
* friend functions.
*
* Notice:
* If your class has a method prefixed with "friend_" or you want to change
* default prefix, you should also call set_friend_prefix method. In this
* case, you should use the new prefix to access friendly methods.
* This method should be called in constructor or earlier as posible.
*
* Copyright Notice:
* This class is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This class is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this class. If not, see <http://www.gnu.org/licenses/>.
*
* @author Rubens Takiguti Ribeiro
* @date 2008-07-11
* @version 1.0 2008-07-14
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3 (LICENSE.TXT)
* @copyright Copyright (C) 2008 Rubens Takiguti Ribeiro
*/
abstract class friendly_class {
/**
* Regular Expression used to match called method.
*
* @var string
*/
private $expression = '/^friend_([A-z0-9_]+)$/';
/**
* Should return whether specified method is friendly.
*
* @param string $method Name of friendly method.
* @return bool Whether the method is friendly or not.
*/
abstract protected function is_friendly_method($method);
/**
* Modify the prefix used to access friendly methods.
* Default is "friend_".
* Remember to use "_" (underscore) as last char if your project
* use this character as word separator in methods.
*
* @trigger E_USER_ERROR If prefix param is not a string.
* @param string $prefix
* @return void
*/
protected function set_friend_prefix($prefix) {
if (is_string($prefix)) {
$this->expression = '/^'.$prefix.'([A-z0-9_]+)$/';
} else {
trigger_error('Prefix should be a string', E_USER_ERROR);
}
}
/**
* Should return if specified class can call specified friendly method.
*
* @param string $class Name of class to be checked.
* @param string $method Name of friendly method to be checked.
* @return bool Whether the class can call the method.
*/
protected function is_friend_class($class, $method) {
return false;
}
/**
* Should return if specified function can call specified friendly method.
*
* @param string $function Name of function to be checked.
* @param string $method Name of friendly method to be checked.
* @return bool Whether the function can call the method.
*/
protected function is_friend_function($function, $method) {
return false;
}
/**
* Magic PHP method that is called when a method that does not exists in
* current class is called.
*
* @trigger E_USER_WARNING Method does not exists.
* @trigger E_USER_WARNING Method is called directly.
* @trigger E_USER_WARNING Method is called from a non-friend.
* @param string $method Called method.
* @param array[string => mixed] $args Informed arguments.
* @return mixed Returns values acording to called friendly method.
*/
public function __call($method, $args) {
// If the method is not called with friend prefix.
if (!preg_match($this->expression, $method, $match)) {
trigger_error($method.' does not exists in '.__CLASS__, E_USER_WARNING);
return;
}
$method = $match[1];
// If the method not exists or is not friendly
if (!method_exists($this, $method) ||
!$this->is_friendly_method($method)) {
trigger_error($method.' does not exists in '.__CLASS__, E_USER_WARNING);
return;
}
$backtrace = debug_backtrace();
/**
* Check in backtrace who calls this friendly method
* $backtrace[0] = __call
* $backtrace[1] = friend_{method_name}
* $backtrace[2] = {caller_we_need}
*/
if (count($backtrace) <= 2) {
trigger_error($method.' can not be called directly', E_USER_WARNING);
return;
}
// If the method is called from a class
if (isset($backtrace[2]['class'])) {
$friend_class = $backtrace[2]['class'];
// If class is friend: call protected method
if ($this->is_friend_class($friend_class, $method)) {
return call_user_func_array(array($this, $method), $args);
} else {
trigger_error($friend_class.' is not a friend class of '.$method.' method', E_USER_WARNING);
return;
}
// If the method is called from a function
} elseif (isset($backtrace[2]['function'])) {
$friend_function = $backtrace[2]['function'];
// If function is friend: call protected method
if ($this->is_friend_function($friend_function, $method)) {
return call_user_func_array(array($this, $method), $args);
} else {
trigger_error($friend_function.' is not a friend funciton of '.$method.' method', E_USER_WARNING);
return;
}
}
// Callback did not return "function" position in array
trigger_error('Error in returned value of debug_backtrace', E_USER_WARNING);
}
}//class