Location: PHPKode > scripts > Friendly class > friendly-class/friendly_class.class.php
<?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
Return current item: Friendly class