开发者

Autoloader for functions

Last week I learned that classes can be included in your project by开发者_StackOverflow社区 writing an __autoload() function. Then I learned that using an autoloader isn't only a technique but also a pattern.

Now I'm using the autoloader in my project and I've found it very very useful. I was wondering if it could be possible to do the same thing with functions. It could be very useful to forget about including the right PHP file with functions inside it.

So, is it possible to create a function autoloader?


There is no function auto-loader for functions. You have four realistic solutions:

  1. Wrap all functions into namespacing classes (context appropriate). So let's say you have a function called string_get_letters. You could add that to a class called StringFunctions as a static function. So instead of calling string_get_letters(), you'd call StringFunctions::get_letters(). You would then __autoload those namespaced classes.

  2. Pre-load all functions. Since you're using classes, you shouldn't have that many functions, so just pre-load them.

  3. Load functions prior to using them. In each file, require_once the function files that are going to be used in that file.

  4. Don't use functions in the first place. If you are developing OOP code (which it seems like you are anyway), there should be little to no need for functions at all. Everything you would need a function (or multiple) for, you could build in a OO manner and avoid the need for functions.

Personally, I'd suggest either 1, 2 or 4 depending on your exact need and the quality and size of your codebase...


If you are using Composer in your Project, you can add a files directive to the autoload section.

This will than actually generate a require_once in the autoloader, but it feels like real autoloading, because you dont have to take care of that.
Its not lazy loading though.

Example taken from Assetic:

"autoload": {
        "psr-0": { "Assetic": "src/" },
        "files": [ "src/functions.php" ]
    }


I read something a while back about an ugly hack that caught fatal errors and tried to include and execute the missing function(s), but I definitely wouldn't go that road.

The closest thing you have is the __call() magic method, which is sort of a __autoload() for methods, not functions. It might be good enough for your needs; if you can afford to call a class and require each different function separately. Since PHP 5.3.0, you also have __callStatic().

An example using __callStatic():

class Test
{
    public function __callStatic($m, $args)
    {
        if (function_exists($m) !== true)
        {
            if (is_file('./path/to/functions/' . $m . '.php') !== true)
            {
                return false;
            }

            require('./path/to/functions/' . $m . '.php');
        }

        return call_user_func_array($m, $args);
    }
}

Test::functionToLoad(1, 2, 3);

This would call the functionToLoad() function defined in ./path/to/functions/functionToLoad.php.


Well, as usual there is a PECL extension for that:

  • automapPECL

(via: http://phk.tekwire.net/joomla/support/doc/automap.htm)

It's supposed to autoload functions as well as classes. Which however doesn't work with the current PHP interpreter yet.

(An alternative option btw, is generating stub functions that load and run namespaced counterparts.)

That being said. Autoloading is not universally considered a good practice. It leads to overly fractured class hierarchies and object happiness. And the real reason PHP has autoloading is because include and dependency management systems are inmature.


namespace MyNamespace;

class Fn {

    private function __construct() {}
    private function __wakeup() {}
    private function __clone() {}

    public static function __callStatic($fn, $args) {
        if (!function_exists($fn)) {
            $fn = "YOUR_FUNCTIONS_NAMESPACE\\$fn";
            require str_replace('\\', '/', $fn) . '.php';
        }
        return call_user_func_array($fn, $args);
    }

}

And using namespaces, we can do: Fn::myFunc() and spl_autoload_register(). I've used this code with examples at: https://goo.gl/8dMIMj


I use a Class and __invoke. The __invoke method is called when a script calls a class as a function. I often do something like this:

<?php

namespace API\Config;

class Slim {
  function __invoke() {
    return [
      'settings' => [
        'displayErrorDetails' => true,
        'logger' => [
          'name' => 'api',
          'level' => Monolog\Logger\Logger::DEBUG,
          'path' => __DIR__ . '/../../logs/api.log',
        ],
      ]
    ];
  }
}

I can then call like a function:

$config = API\Config\Slim;
$app = Slim\App($config())


new Functions\Debug() will load functions to root namespace.

namespace Functions
{

    class Debug
    {
    }
}
namespace
{

    if (! function_exists('printr')) {

        /**
         *
         * @param mixed $expression
         */
        function printr()
        {
            foreach (func_get_args() as $v) {
                if (is_scalar($v)) {
                    echo $v . "\n";
                } else {
                    print_r($v);
                }
            }
            exit();
        }
    }
}


Here is another rather complex example, based on the suggestions in this discussion. The code can also be seen here: lib/btr.php

<?php
/**
 * A class that is used to autoload library functions.
 *
 * If the function btr::some_function_name() is called, this class
 * will convert it into a call to the function
 * 'BTranslator\some_function_name()'. If such a function is not
 * declared then it will try to load these files (in this order):
 *   - fn/some_function_name.php
 *   - fn/some_function.php
 *   - fn/some.php
 *   - fn/some/function_name.php
 *   - fn/some/function.php
 *   - fn/some/function/name.php
 * The first file that is found will be loaded (with require_once()).
 *
 * For the big functions it makes more sense to declare each one of them in a
 * separate file, and for the small functions it makes more sense to declare
 * several of them in the same file (which is named as the common prefix of
 * these files). If there is a big number of functions, it can be more
 * suitable to organize them in subdirectories.
 *
 * See: http://stackoverflow.com/questions/4737199/autoloader-for-functions
 */
class btr {
  /**
   * Make it TRUE to output debug info on '/tmp/btr.log'.
   */
  const DEBUG = FALSE;

  /**
   * The namespace of the functions.
   */
  const NS = 'BTranslator';

  /**
   * Relative directory where the functions are located.
   */
  const FN = 'fn';

  private function __construct() {}
  private function __wakeup() {}
  private function __clone() {}

  /**
   * Return the full name (with namespace) of the function to be called.
   */
  protected static function function_name($function) {
    return self::NS . '\\' . $function;
  }

  /**
   * Return the full path of the file to be loaded (with require_once).
   */
  protected static function file($fname) {
    return dirname(__FILE__) . '/' . self::FN . '/' . $fname . '.php';
  }

  /**
   * If a function does not exist, try to load it from the proper file.
   */
  public static function __callStatic($function, $args) {
    $btr_function = self::function_name($function);
    if (!function_exists($btr_function)) {
      // Try to load the file that contains the function.
      if (!self::load_search_dirs($function) or !function_exists($btr_function)) {
        $dir = dirname(self::file($fname));
        $dir = str_replace(DRUPAL_ROOT, '', $dir);
        throw new Exception("Function $btr_function could not be found on $dir");
      }
    }
    return call_user_func_array($btr_function, $args);
  }

  /**
   * Try to load files from subdirectories
   * (by replacing '_' with '/' in the function name).
   */
  protected static function load_search_dirs($fname) {
    do {
      self::debug($fname);
      if (file_exists(self::file($fname))) {
        require_once(self::file($fname));
        return TRUE;
      }
      if (self::load_search_files($fname)) {
        return TRUE;
      }
      $fname1 = $fname;
      $fname = preg_replace('#_#', '/', $fname, 1);
    } while ($fname != $fname1);

    return FALSE;
  }

  /**
   * Try to load files from different file names
   * (by removing the part after the last undescore in the functin name).
   */
  protected static function load_search_files($fname) {
    $fname1 = $fname;
    $fname = preg_replace('/_[^_]*$/', '', $fname);
    while ($fname != $fname1) {
      self::debug($fname);
      if (file_exists(self::file($fname))) {
        require_once(self::file($fname));
        return TRUE;
      }
      $fname1 = $fname;
      $fname = preg_replace('/_[^_]*$/', '', $fname);
    }

    return FALSE;
  }

  /**
   * Debug the order in which the files are tried to be loaded.
   */
  public static function debug($fname) {
    if (!self::DEBUG) {
      return;
    }
    $file = self::file($fname);
    $file = str_replace(DRUPAL_ROOT, '', $file);
    self::log($file, 'Autoload');
  }

  /**
   * Output the given parameter to a log file (useful for debugging).
   */
  public static function log($var, $comment ='') {
    $file = '/tmp/btr.log';
    $content = "\n==> $comment: " . print_r($var, true);
    file_put_contents($file, $content, FILE_APPEND);
  }
}


While you can't autoload functions and constants, you can use something like jesseschalken/autoload-generator which will automatically detect what files contain things which can't be autoloaded and load them eagerly.


The solution I came up with. As lightweight as I could come up with.

class functions {

  public static function __callstatic($function, $arguments) {

    if (!function_exists($function)) {
      $file = strtok($function, '_') .'.php';
      include '/path/to/functions/'.$file;
    }

    return call_user_func_array($function, $arguments);
  }
}

Use it by calling functions::foo_bar($anything).


I try to use the autoloading of classes to my advantage. So, when a class is auto-loaded, the class file is executed. Therefore, I create a class with a static method called 'boot' that does nothing. When I invoke that method, the class will be autoloaded, hence every function in that file will be defined in the global scope. What's even more interesting is that the functions will be defined in the namespace of the class, so there are no clashes.

For example:

<?PHP

namespace Functions;
// functions are defined in this file

class GlobalFunctions{
  public static function boot(){};
}

function foo(){ // code... }
?>

// the client file
<?php
  
  // VS Code automatically does this.
  use Functions\GlobalFunctions;
  use function Functions\foo; 

  // I usually put this in the bootstrap file
  GlobalFunctions::boot();


  
  // call foo() from the Functions namespace
  foo();
?>


Include all functions file in one file and then include it

//File 1
db_fct.php

//File 2
util_fct.php

//In a functions.php include all other files

<?php

require_once 'db_fct.php';
require_once 'util_fct.php';
?>

Include functions.php whenever you need functions ..


try this

if ($handle = opendir('functions')) {
    while (false !== ($entry = readdir($handle))) {
        if (strpos($entry, '.php') !== false) {
            include("functions/$entry");
        }
    }
    closedir($handle);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜