66

Last week I learned that classes can be included in your project by 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?

Shoe
  • 74,840
  • 36
  • 166
  • 272
  • 1
    Related: [autoload functions in php (16 Nov 2010)](http://stackoverflow.com/questions/4196881/autoload-functions-in-php), ['autoload' functions in php? (25 Jul 2011)](http://stackoverflow.com/questions/6812948/autoload-functions-in-php), [Load specific functions PHP (28 Jul 2011)](http://stackoverflow.com/questions/6856179/load-specific-functions-php), [Automatically include missing functions? (5 Jul 2012)](http://stackoverflow.com/questions/11352996/automatically-include-missing-functions) – hakre Aug 27 '12 at 10:19

13 Answers13

62

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...

Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 5
    +1 Actually it's a good design pattern to gather common functions in classes with static methods such as `StringUtils`, `WebHelpers`, `CollectionUtils`, etc. So I'd go for option 1 or 4. – scoffey Jan 19 '11 at 15:50
  • 76
    +1, but not everything that you might do via loose functions naturally translates to an OOP setting. What if you just want a utility method that normalizes a path format, or manipulates an array in a particular way? Don't try to shoehorn everything into an OO style :) – Will Vousden Jan 19 '11 at 16:01
  • 4
    @Alix: That's not a function, it's a method and it acts against classes. It does not enable lazy-loading, but calling non-existent methods. @Will: It's not shoehorning. If you need to maniuplate an array in a particular way, then either make it a protected/private member method on the class that needs it, or if it's generic enough add it to a utility class. It's not about shoehorning, it's about designing. Sure, you can shoehorn and wind up with unmaintainable code, but if you design properly you get the benefits of both worlds. But mixing loosely paradigms is almost never good... – ircmaxell Jan 19 '11 at 16:04
  • I'll use the 1 and 4 solution. It seems weird to use too many classes, thanks to have pointed out that OOP means few lonely functions. – Shoe Jan 19 '11 at 16:05
  • @ircmaxell: So is #1? It's possible to implement our own lazy loader mechanism when the method isn't defined. – Alix Axel Jan 19 '11 at 16:17
  • 12
    @ircmaxell "if it's generic enough add it to a utility class" Such a utility class would just be a bag of miscellaneous things that would otherwise be written as independent functions. The only reason to do that is because PHP can't autoload functions. There's nothing OO about it. – Jesse Aug 21 '15 at 11:43
  • @Jesse I don't see where I said that was oop in any way. Using classes != oop in the first place. – ircmaxell Aug 21 '15 at 11:48
  • 9
    @ircmaxell Err, true, but "It's not about shoehorning, it's about designing." seems to imply that there is some specifically _design_ reason to move utility functions into a utility class, besides that it means PHP can autoload them. – Jesse Aug 21 '15 at 12:01
  • 1
    @WillVousden The benefit of having functions outside of classes is less syntax. Sometimes you will find yourself wanting to use the same function over and over again all over the place and you know of a good, short, intuitive name and it would be really nice to just type call that short function. These are basically functions you wish were already part of the language or that you would like to add to the language for the type of project you are working on. – still_dreaming_1 Aug 03 '16 at 16:55
  • 1
    @WillVousden Unlike popular believe less syntax is not just a matter of less typing or laziness. When you are writing code and your thoughts more quickly translate into written code, this frees up your thinking for better flow, thus allowing you to experience better flow while you are coding. Also when reading code, shorter syntax means your brain can read it more automagically which frees it up to pick up on the bigger picture algorithms or other important patterns or insights in the code. These may have taken you longer to see otherwise or they may not happen at all. – still_dreaming_1 Aug 03 '16 at 16:58
  • 1
    I really do wish function autoloading was a thing. @WillVousden I forgot there is one more practical reason to use functions in combination with OO in PHP. I use it as a hack to overcome a limitation of constructors. Normally (at least in the versions of PHP I have tested this on), you can't do method chaining on a new object without first assigning it to a variable. If you create a function that is used to return a new object instead of using the constructor directly, this overcomes that limitation. This is great for composability. – still_dreaming_1 Aug 05 '16 at 01:47
  • 1
    I have to agree with the statement that using static methods in a class is for no other reason than because PHP lacks a competent way to autoload functions. The class, in this case, is just acting as a more clumsy namespace. There isn't any pragmatic difference between \Foo\Bar\my_function and \Foo\Bar::my_function. Where the former could be conveniently renamed in a `use` statement or used as-is, the latter can be auto-included, and that's literally the only reason to use the latter. – Marcus Harrison Mar 24 '17 at 10:54
  • After using NodeJS for almost 2 years I came back to PHP trying hoping there was a way to export functions and variables in a similar way. Obv. they're not the same, but anyone who has used Node will know what I mean. Using a class with an `__invoke` does the trick nicely though. – BugHunterUK Sep 24 '18 at 22:07
  • On top of what Will Vousden and Jesse say, it is often more convenient to have one file per function. You can't acheive this with a wrapping class since you can't split class' contents across multiple files in PHP. – 1234ru May 18 '20 at 16:09
  • @BugHunterUK `__invoke` requires creating an instance of the class with `new` first, so you can't just call the class directly... – tonix Sep 22 '20 at 14:15
41

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" ]
    }
ivoba
  • 5,780
  • 5
  • 48
  • 55
  • 1
    Thanks for pointing that out. See: [Docs](https://getcomposer.org/doc/04-schema.md#files). – flu Mar 28 '14 at 09:29
  • 1
    If you combine this with function namespacing (PHP 5.6+) then you have a neat solution IMHO (apart from the lazy loading aspect of course but then https://wiki.php.net/rfc/function_autoloading as proposed by @ircmaxell) – liquorvicar Feb 03 '16 at 11:50
  • 2
    spotted in guzzlehttp\psr7: ` "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" }, "files": ["src/functions_include.php"] } ` ...seems legit! – sfscs Jun 13 '18 at 00:14
18

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.

Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • Yes, but you'd need to still prefix the call with a class name, so it's not really lazy-loading but intercepting the call (adding a `stat` and an additional method call to each and every function call). And it's not an autoload, but a fallback if it can't find the called method. – ircmaxell Jan 19 '11 at 16:20
  • 3
    @ircmaxell: Following your line of though `__autoload()` also isn't an autoloader, just a fallback if it can't find the specified class... – Alix Axel Jan 19 '11 at 16:22
  • That's true, but it's a language based fallback for that particular context (classes). I'm not saying it's "wrong" to do it this way, just that realize that doing something like this is a slight abuse of the system... But it's not "bad" (aside from checking if the file exists prior to the function exists, and other tiny inefficiencies that border on micro-optimizations)... – ircmaxell Jan 19 '11 at 16:25
  • @ircmaxell: Yeah, I was fixing that. =) – Alix Axel Jan 19 '11 at 16:26
  • 2
    This works, except when it doesn't. Returning references (*in-often as you do*) fails with this. There are some other caveats; I'll try to dig through my own questions, I think I had one about this. – Dan Lugg Jan 20 '14 at 13:07
  • @DanLugg: Yup, I'm aware of the deficiencies handling references (I don't really advocate following this approach at all). Curious about your implementation though. – Alix Axel Jan 20 '14 at 13:38
  • 1
    @ircmaxell, Your answer's suggestion number #1 also seems much of an abuse isn't it? – Pacerier Oct 16 '14 at 20:53
9

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

(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.

hakre
  • 193,403
  • 52
  • 435
  • 836
mario
  • 144,265
  • 20
  • 237
  • 291
  • Exactly. Autoloading is no better than error handler based last-minute function call fixers. Autoloading leads to fear, fear leads to anger. At least when going to production, one should already know which modules to load and when. – dkellner Sep 13 '22 at 08:53
2
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

Bryan Horna
  • 167
  • 2
  • 12
  • 1
    What exactly are you trying to contribute to the question? When posting answers, ask yourself *"If I removed the link in this answer (if any) would the answer still make sense, and be helpful"* If the answer is no, then change the answer to add key-points of the link in your answer – Jojodmo Mar 15 '14 at 03:29
  • 1
    very smart solution to me, i like it very much. Unfortunately, in my case i m having namespaced functions. Without class. And thus, this solution does not do the work. I m afraid we shall wait 1 year more given that rfc which targets php 7.1 https://wiki.php.net/rfc/function_autoloading. Sorry to say that but what a shame.... : [ This say for now on best solution is to rely on composer capabilities, i guess. –  Sep 06 '15 at 02:05
  • Yes, I think for now the only `reliable` way to go is Composer autload features. But you can use it with namespaced functions also; the only thing to do is to notice `call_user_func_array` (used in the code) also handles what you want. – Bryan Horna Sep 18 '15 at 19:43
  • The broken link `https://goo. gl/8dMIMj` was [archived here](https://web.archive.org/web/20170623212906/https://bryanjhvtk.wordpress.com/2014/03/14/functions-autoloading-php/). – Binar Web Jul 28 '22 at 08:42
2

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())
BugHunterUK
  • 8,346
  • 16
  • 65
  • 121
  • 1
    The unfortunate thing is that PHP does not support this syntax w/o creating a same-named function which, of course, cannot be autoloaded: `Slim\App(API\Config\Slim())` – MikeSchinkel Dec 01 '19 at 22:20
  • How do you invoke `Slim` if you didn't create the instance with `new Slim()` first? Does PHP allow invoking a class? – tonix Sep 22 '20 at 14:10
1

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();
        }
    }
}
Ares
  • 21
  • 1
  • 3
0

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);
  }
}
dashohoxha
  • 221
  • 1
  • 5
0

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.

Jesse
  • 6,725
  • 5
  • 40
  • 45
0

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 ..

Az.Youness
  • 2,167
  • 1
  • 24
  • 33
  • @jirigracik How should we name them ? and Why ? – Az.Youness Oct 02 '17 at 12:49
  • 2
    You should not use plain functions in first place. Anyways, there you should name the file so everyone knows, what does it contain. `DatabaseFunctions.php` or `Functions\Database.php` might do it. Utilities is not a good name because it can be anything. You should rather use specific names (string functions, array functions, database functions, ...). I think you get the idea. – jirig Oct 02 '17 at 14:19
0

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).

tim
  • 2,530
  • 3
  • 26
  • 45
0

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();
?>
AExplosion
  • 51
  • 2
-2

try this

if ($handle = opendir('functions')) {
    while (false !== ($entry = readdir($handle))) {
        if (strpos($entry, '.php') !== false) {
            include("functions/$entry");
        }
    }
    closedir($handle);
}
  • 3
    First, you should follow SO guidelines. Answers should barely ever entirely consist of code. Second, this does not provide an autoloader as it exists for classes. This snippet just loads all PHP files within a directory. Therefore, this does not answer the question. – Tobias May 20 '15 at 19:47