La semaine dernière, j'ai appris que des classes peuvent être incluses dans votre projet en écrivant une fonction __autoload()
. Ensuite, j'ai appris que l'utilisation d'un autochargeur n'est pas seulement une technique mais aussi un motif.
Maintenant, j'utilise l'autoloader dans mon projet et je l'ai trouvé très très utile. Je me demandais s'il serait possible de faire la même chose avec les fonctions. Il pourrait être très utile d’oublier d’inclure le bon fichier PHP contenant des fonctions.
Alors, est-il possible de créer un autochargeur de fonction?
Il n'y a pas de chargeur automatique de fonction pour les fonctions. Vous avez quatre solutions réalistes:
Enveloppez toutes les fonctions dans des classes d'espacement de noms (en fonction du contexte). Alors disons que vous avez une fonction appelée string_get_letters
. Vous pouvez ajouter cela à une classe appelée StringFunctions
en tant que fonction statique. Ainsi, au lieu d'appeler string_get_letters()
, vous appelez StringFunctions::get_letters()
. Vous devriez alors __autoload
ces classes namespaced.
Pré-charger toutes les fonctions. Puisque vous utilisez des classes, vous ne devriez pas avoir que beaucoup de fonctions, il suffit donc de les précharger.
Charger des fonctions avant de les utiliser. require_once
dans chaque fichier, les fichiers de fonctions à utiliser dans ce fichier.
N'utilisez pas de fonctions en premier lieu. Si vous développez du code OOP (ce qui semble être le cas de toute façon), les fonctions ne devraient pas être nécessaires. Tout ce pour quoi vous auriez besoin d’une fonction (ou de plusieurs), vous pourriez construire de manière OO et éviter d’avoir besoin de fonctions.
Personnellement, je suggérerais soit 1, 2 ou 4 selon votre besoin exact, la qualité et la taille de votre base de code ...
Si vous utilisez Composer dans votre projet, vous pouvez ajouter une directive files à la section autoload.
Cela générera en fait un require_once dans l'autoloader, mais cela ressemble à un véritable autoloading, car vous n'avez pas à vous en occuper.
Son chargement pas paresseux cependant.
Exemple tiré de Assetic :
"autoload": {
"psr-0": { "Assetic": "src/" },
"files": [ "src/functions.php" ]
}
J'ai lu quelque chose il y a quelque temps à propos d'un vilain piratage qui avait des erreurs fatales et qui tentait d'inclure et d'exécuter la ou les fonctions manquantes, mais je ne voudrais absolument pas aller dans cette voie.
La chose la plus proche que vous ayez est la __call()
méthode magique , qui est en quelque sorte une __autoload()
pour les méthodes, pas pour les fonctions. Cela pourrait suffire à vos besoins; si vous pouvez vous permettre d’appeler une classe et d’exiger chaque fonction séparément. Depuis PHP 5.3.0, vous avez également __callStatic()
.
Un exemple utilisant __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);
Ceci appellerait la fonction functionToLoad()
définie dans ./path/to/functions/functionToLoad.php.
Comme d’habitude, il existe une extension PECL pour cela:
(via: http://phk.tekwire.net/joomla/support/doc/automap.htm )
Il est supposé charger automatiquement des fonctions ainsi que des classes. Ce qui cependant ne fonctionne pas avec l'interpréteur actuel PHP.
(Une autre option, btw, génère des fonctions de stub qui chargent et exécutent des homologues espacés de noms.)
Cela étant dit. Le chargement automatique n'est pas universellement considéré comme une bonne pratique. Cela conduit à des hiérarchies de classes trop fracturées et au bonheur des objets. Et la vraie raison PHP - le chargement automatique est dû au fait que les systèmes d'inclusion et de gestion des dépendances sont immatures.
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);
}
}
Et en utilisant les espaces de noms, nous pouvons faire: Fn::myFunc()
et spl_autoload_register()
. J'ai utilisé ce code avec des exemples sur: https://goo.gl/8dMIMj
J'utilise une classe et __invoke . La méthode __invoke
est appelée lorsqu'un script appelle une classe en tant que fonction. Je fais souvent quelque chose comme ça:
<?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',
],
]
];
}
}
Je peux alors appeler comme une fonction:
$config = API\Config\Slim;
$app = Slim\App($config())
Bien que vous ne puissiez pas charger automatiquement les fonctions et les constantes, vous pouvez utiliser quelque chose comme jesseschalken/autoload-generator qui détectera automatiquement les fichiers contenant des éléments qui ne peuvent pas être chargés automatiquement et les charger rapidement.
Voici un autre exemple assez complexe, basé sur les suggestions de cette discussion . Le code est également visible ici: 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);
}
}
Inclure tous les fichiers de fonctions dans un fichier, puis l'inclure
// Fichier 1
db_fct.php
// Fichier 2
util_fct.php
// Dans un functions.php inclure tous les autres fichiers
<?php
require_once 'db_fct.php';
require_once 'util_fct.php';
?>
Incluez functions.php chaque fois que vous avez besoin de fonctions ..
new Functions\Debug () avec autoload chargera les fonctions dans l’espace de noms racine.
namespace Fonctions { classe Debug { } } namespace { if (! function_exists ('printr')) { /** * * @param mixed $ expression */ fonction printr () { foreach (func_get_args () en tant que $ v) { if (is_scalar ($ v)) { echo $ v. "\ n"; } autre { print_r ($ v); } } sortie(); } } }