PHP: how to autoload interfaces and abstracts - php

I have this autoloader class to autoload classes initially, but now I want to autoload interfaces and abstracts as well.
So I made the change following this answer,
$reflection = new ReflectionClass($class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
I tested it but this autoloader class does not load interfaces at all. Any ideas what I have missed?
For instance, this is my interface file,
interface_methods.php
and its content,
interface methods
{
public function delete();
}
Below is my entire this autoloader class.
class autoloader
{
/**
* Set the property.
*/
public $directory;
public $recursive;
public function __construct($directory, $recursive = array('search' => 'models') )
{
# Store the data into the property.
$this->directory = $directory;
$this->recursive = $recursive;
# When using spl_autoload_register() with class methods, it might seem that it can use only public methods, though it can use private/protected methods as well, if registered from inside the class:
spl_autoload_register(array($this,'get_class'));
}
private function get_class($class_name)
{
# List all the class directories in the array.
if ($this->recursive)
{
$array_directories = self::get_recursive_directory($this->directory);
}
else
{
if (is_array($this->directory)) $array_directories = $this->directory;
else $array_directories = array($this->directory);
}
# Determine the class is an interface.
$reflection = new ReflectionClass($class_name);
$file_pieces = explode('\\', $class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
# Loop the array.
foreach($array_directories as $path_directory)
{
if(file_exists($path_directory.$file_name))
{
include $path_directory.$file_name;
}
}
}
public function get_recursive_directory($directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator($directory),
RecursiveIteratorIterator::CHILD_FIRST
);
# This will hold the result.
$result = array();
# Loop the directory contents.
foreach ($iterator as $path)
{
# If object is a directory and matches the search term ('models')...
if ($path->isDir() && $path->getBasename() === $this->recursive['search'])
{
# Add it to the result array.
# Must replace the slash in the class - dunno why!
$result[] = str_replace('\\', '/', $path).'/';
//$result[] = (string) $path . '/';
}
}
# Return the result in an array.
return $result;
}
}

PHP makes no difference between any class or interface or abstract class. The autoloader function you define always gets the name of the thing to autoload, and no kind of hint which one it was.
So your naming strategy cannot be autoloaded because you prefix interfaces with "interface_" and classes with "class_". Personally I find such a naming convention rather annoying.
On the other hand, your autoloader is completely unperformant. It scans whole directory trees recursively just to find one class! And the next class has to do all the work again, without the benefit of having done it before!
Please do implement a PSR-0 autoloader if you really want to do it on your own (and not use things like composer to do it for you) and stick to this naming scheme for classes and interfaces.
And please select a distinguishing classname prefix or namespace, and as a first step check inside your autoloader if the class to be loaded has this prefix. Return instantly if it has not. This frees you from having to spin the harddrive and see if the filename for the class exists.
If the prefix does not match, it is not "your" class that wants to be loaded, so your autoloader cannot know how to do it and shouldn't even try, but a different autoloader that was registered will know.

Related

Autoload only registers one class at a time

I have this autoloader which loads only one class at a time. I can't figure out what is wrong with it.. I initially made to learn as much about PSR-0 as possible, though according to code review I did everything required, but it just won't load two different files, from different namespaces as seen below.
class Autoloader
{
private $pathToClass;
//register the path
function __construct($pathToClass)
{
$this->pathToClass = $pathToClass;
}
//load the file
public function load($class)
{
// expload the namespaces ex: foo\bar\tar array(foo, bar, tar)
$explode = explode('\\', $class);
//get the last exploaded string and append .php so it becomes tar.php
$class = $explode[count($explode) - 1].'.php';
// required tar.php in the path it is found ex:
// require '/foo/bar/tar.php' in lowecase to avoid windows/unix conflict
if(file_exists(strtolower($this->pathToClass.$class))){
require strtolower($this->pathToClass.$class);
return true;
}
return false;
}
// autoload
public function register()
{
spl_autoload_register([$this, 'load']);
}
}
Here is how it is instantiated:
$myLibLoader = new Autoloader(__DIR__.'/foo/bar/');
$myLibLoader->register();
$foo = new foo();
EDIT
The above was the first autoloader I had created, but below I am showing the improved autoloader, which which I have the same problem.
<?php
class AutoloaderException extends Exception{}
class AutoLoader
{
private $classDir;
private $namespace;
public $dirSeparatorSymbol = '\\';
public function __construct($namespace, $classDir)
{
$this->classDir = $classDir;
$this->namespace = $namespace;
}
private function load($class)
{
$include_path = str_replace($this->dirSeparatorSymbol, DIRECTORY_SEPARATOR, $this->classDir);
$classFilename = strtolower(substr($class, strrpos($class, '\\') + 1) . '.php');
if(file_exists($include_path.$classFilename)){
require $include_path.$classFilename;
return true;
}
throw new AutoloaderException('Class '.$classFilename. ' could not be loaded');
}
public function register()
{
spl_autoload_register([$this, 'load']);
}
}
/* INITIALIZING The autloader */
$b = new Autoloader('mercury\venus\earth', __DIR__.'/mercury/venus/earth/');
$b->register();
$a = new Autoloader('bar\tar', __DIR__.'/foo/bar/tar/');
$a->register();
$x = new bar\tar;
$y = new mercury\venus\earth;
I think the crux of your issue was identified in your original CodeReview question, specifically this comment:
You still don't construct a path from the class qualifier passed to
load(). As the question how to do this is offtopic on CodeReview,
consider posting on StackOverflow (with a clear explanation on the
parts you don't understand). – #ComFreek
I think, given two classess with no namespace, your autoloader as-is should perform fine. Once you introduce namespaces though you're going to have an issue because you're not taking it into account at all.
Namespaces are important in PSR-0 because they contain further path information. For example, you may want all of your classes to live in /some/directory, but if you add namespaces to those files (extending your example using the \Foo\Bar\Tar class) then according to PSR-0 that class must be declared in the file /some/directory/Foo/Bar/Tar.php.
Taking this into consideration, these lines must be changed in your autoloader in order for it to become PSR-0 compliant:
if(file_exists(strtolower($this->pathToClass.$class))){
require strtolower($this->pathToClass.$class);
return true;
}
Specifically, the filepath of the class file you're trying to require should be something like this:
$this->pathToClass . DIRECTORY_SEPARATOR . implode('/', $class) . '.php';
(That is, assuming you take this line out: $class = $explode[count($explode) - 1].'.php'; )
I'm not sure if you've had the chance, but this article on Site Point is a great read and covers pretty much everything you need to know regarding PSR-0 and autoloading.

Dynamic class name in PHP

I'm trying to create a system that it has a GeneralObj. The GeneralObj allows user to initiate special objects for database's tables with a String of the table name. So far, it works perfect.
class GeneralObj{
function __construct($tableName) {
//...
}
}
However, it is too tired to type new GeneralObj(XXX) every time.
I am wondering is that possible to simplify the process to like new XXX(), which is actually running the same as new GeneralObj(XXX)?
I spot PHP provided __autoload method for dynamic loading files in the setting include_path but it requires a the actually definition file existing. I really don't want to copy and copy the same definition files only changing a little.
For cause, eval is not an option.
Maybe you can just auto-create the files in the autoloader:
function __autoload($class_name) {
// check for classes ending with 'Table'
if (preg_match('/(.*?)Table/', $class_name, $match)) {
$classPath = PATH_TO_TABLES . '/' . $match[1] . '.php';
// auto-create the file
if (!file_exists($classPath)) {
$classContent = "
class $class_name extends GeneralObj {
public __construct() {
parent::__construct('{$match[1]}');
}
}";
file_put_contents($classPath, $classContent);
}
require_once $classPath;
}
}
Use inheritance. Make GeneralObj the superclass of the table specific classes. This way you can dynamically derive class names and instantiate objects. Example:
class someTable extends GeneralObj {
}
$tableName = 'some';
$className = $tableName . 'Table';
$obj = new $className;
No, this is not possible.
The runkit extension allows programmatic manipulation of the PHP runtime environment, but it cannot do this. Even if it could, it would IMHO be a very bad idea, greatly impacting the requirements and complexity of the application in exchange for saving a few keystrokes.
In an unrelated note, your GeneralObj class has functionality that sounds suspiciously like that of a dependency injection container. Perhaps you should consider replacing it with one?
Something like this autoloader:
myAutoloader::Register();
class myAutoloader
{
/**
* Register the Autoloader with SPL
*
*/
public static function Register() {
if (function_exists('__autoload')) {
// Register any existing autoloader function with SPL, so we don't get any clashes
spl_autoload_register('__autoload');
}
// Register ourselves with SPL
return spl_autoload_register(array('myAutoloader', 'Load'));
} // function Register()
/**
* Autoload a class identified by name
*
* #param string $pClassName Name of the object to load
*/
public static function Load($pClassName){
if (class_exists($pClassName,FALSE)) {
// Already loaded
return FALSE;
}
$pClassFilePath = str_replace('_',DIRECTORY_SEPARATOR,$pClassName) . '.php';
if (file_exists($pClassFilePath) === FALSE) {
// Not a class file
return new GeneralObj($pClassName);
}
require($pClassFilePath);
} // function Load()
}
And it's up to GeneralObj to throw an exception if the table class can't be instantiated

PHP classes with a namespace cannot be loaded via spl_autoload_register?

I have a problem in loading classes with spl_autoload_register when a namespace is implemented in a class.
the class autoloader below, but I have no problem loading any class when no namespace is used,
class autoloader
{
/**
* Set the property.
*/
public $directory;
public $recursive;
/**
* Receive the supplied data.
* #string $directory
* #array $recursive default: models
*/
public function __construct($directory, $recursive = array('search' => 'models') )
{
# Store the data into the property.
$this->directory = $directory;
$this->recursive = $recursive;
# When using spl_autoload_register() with class methods, it might seem that it can use only public methods, though it can use private/protected methods as well, if registered from inside the class:
spl_autoload_register(array($this,'get_class'));
}
private function get_class($class_name)
{
# List all the class directories in the array.
if ($this->recursive)
{
$array_directories = self::get_recursive_directory($this->directory);
}
else
{
if (is_array($this->directory)) $array_directories = $this->directory;
else $array_directories = array($this->directory);
}
# Set the class file name.
$file_name = 'class_'.strtolower($class_name).'.php';
# Loop the array.
foreach($array_directories as $path_directory)
{
if(file_exists($path_directory.$file_name))
{
# There is no need to use include/require_once. Autoload is a fallback when the system can't find the class you are instantiating.
# If you've already included it once via an autoload then the system knows about it and won't run your autoload method again anyway.
# So, just use the regular include/require - they are faster.
include $path_directory.$file_name;
}
}
}
# However the memory consumption of this can be huge if you have a very large directory tree with only a few matches.
# #source: http://stackoverflow.com/questions/9294543/search-and-list-specific-directories-only
public function get_recursive_directory($directory)
{
# Create an object that allows us to iterate directories recursively.
# Stolen from here:
# http://www.php.net/manual/en/class.recursivedirectoryiterator.php#102587
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator($directory),
RecursiveIteratorIterator::CHILD_FIRST
);
# This will hold the result.
$result = array();
# Loop the directory contents.
foreach ($iterator as $path)
{
# If object is a directory and matches the search term ('models')...
if ($path->isDir() && $path->getBasename() === $this->recursive['search'])
{
# Add it to the result array.
# Must replace the slash in the class - dunno why!
$result[] = str_replace('\\', '/', $path).'/';
//$result[] = (string) $path . '/';
}
}
# Return the result in an array.
return $result;
}
}
a tag class for instance,
namespace hello;
class tag extends \core
{
}
now load the class via the autoloader class,
# Autoload the classes from the specific folder.
$autoloader = new autoloader("C:/wamp/www/website/local/applications/master/sides/models/", $recursive = false);
# Instantiate the tag.
$tag = new hello\tag($connection);
result,
Fatal error: Class 'hello\tag' not found in
C:\wamp\www\website\local\applications\master\sides\views\tags.php
on line 7
Any idea how I can fix my autoloader class so that I can load the classes whether there is a namespace or not?
EDIT:
The folder where I keep the classes and the class naming,
C:\wamp\www\website\local\applications\master\sides\models\
class_tag.php
class_something.php
Your error is in your get_class() method. $class_name contains the fully qualified classname with it's namespace. In your case you have to strip the namespace from your classname.
$file_name = 'class_'.strtolower(array_pop(explode('\\', $class_name))).'.php';
I highly recommend using PSR-0 standard for autoloading. If you will be using any libraries it is very likely that they are using the same standard and you only have one autoloader.

Basic autoload and namespace in php

I'm in the process of updating a framework that I wrote a while ago. I would like to adopt new standards such as namespaces and using the autoload feature.
Right now my framework has a very rudimentary but functional autoload function that looks like this:
protected function runClasses()
{
$itemHandler = opendir( V_APP_PATH );
while( ( $item = readdir( $itemHandler ) ) !== false )
{
if ( substr( $item, 0, 1 ) != "." )
{
if( !is_dir( $item ) && substr( $item, -10 ) == ".class.php" )
{
if( !class_exists( $this->registry->$item ) )
{
require_once( V_APP_PATH . $item );
$item = str_replace( ".class.php", "", $item );
$this->registry->$item = new $item( $this->registry );
}
}
}
}
}
As you can see in the code this function is limited to a single folder only, but the advantage to it is it loads the class into my registry allowing me to access that specific class in other files by doing something similar to $this->registry->Class->somevar which is the functionality I need.
What I'm needing/wanting to accomplish is to use the autoloader function but have that function not limited to a single folder, instead be able to navigate through multiple folders and instantiate the needed classes.
I have just some test files going and here is my current file structure:
For MyClass2 I have:
namespace model;
Class MyClass2 {
function __construct() {
echo "MyClass2 is now loaded!";
}
}
For MyClass1 I have:
Class MyClass1 {
function __construct() {
echo "MyClass1 is now loaded!<br />";
}
}
And for Autoload I have:
function __autoload( $className ) {
$file = $className . ".php";
printf( "%s <br />", $file );
if(file_exists($file)) {
require_once $file;
}
}
$obj = new MyClass1();
$obj2 = new model\MyClass2();
My Question is the way that is set up it can't find the file for MyClass2 so I'm wondering what I've done wrong and secondly, is there a way like my first "autoload" function to not need to specify the namespace in the autoload file and assign it to my registry?
Sorry for such a lengthy question but any help is greatly appreciated.
I see two things here.
The first one makes your problem a bit complicated. You want to make use of namespaces, but your current configuration is via the file-system. The file-names of the class definition files does not contain the namespace so far. So you can not just continue as you actually do.
The second is that you do not have what's covered by PHP autoloading, you just load a defined set of classes and register it with the registry.
I'm not really sure if you need PHP autoloading here. Sure it might look promising for you to bring both together. Solving the first point will probably help you to solve the later, so I suggest to start with it first.
Let's make the hidden dependencies more visible. In your current design you have got three things:
The name under which an object is registered in the registry.
The filename which contains the class definition.
The name of the class itself.
The values of 2. and 3. are in one, you parse the name of the class itself from the filename. As written, namespaces make this complicated now. The solution is easy, instead of reading from a directory listing, you can read from a file that contains this information. A lightweight configuration file format is json:
{
"Service": {
"file": "test.class.php",
"class": "Library\\Of\\Something\\ConcreteService"
}
}
This contains now the three needed dependencies to register a class by a name into the registry because the filename is known as well.
You then allow to register classes in the registry:
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
And add a loader class for the json format:
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
There is not much magic here, file-names in the json file are relative to the directory of it. If a class is not yet defined (here with triggering PHP autoloading), the file of the class is being required. After that is done, the class is registered by it's name:
$registry = new Registry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
This example as well is pretty straight forward and it works. So the first problem is solved.
The second problem is the autoloading. This current variant and your previous system did hide something else, too. There are two central things to do. The one is to actually load class definitions and the other is to instantiate the object.
In your original example, autoloading technically was not necessary because the moment an object is registered within the registry, it is instantiate as well. You do this to assign the registry to it as well. I don't know if you do it only because of that or if this just happened that way to you. You write in your question that you need that.
So if you want to bring autoloading into your registry (or lazy loading), this will vary a bit. As your design is already screwed, let's continue to add more magic on top. You want to defer the instantiation of a registry component to the moment it's used the first time.
As in the registry the name of the component is more important than it's actual type, this is already pretty dynamic and a string only. To defer component creation, the class is not created when registered but when accessed. That is possible by making use of the __get function which requires a new type of Registry:
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class)
{
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
The usage example again is quite the same, however, the type of the registry has changed:
$registry = new LazyRegistry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
So now the creation of the concrete service objects has been deferred until first accessed. However this still yet is not autoloading. The loading of the class definitions is already done inside the json loader. It would not be consequent to already make verything dynamic and magic, but not that. We need an autoloader for each class that should kick in in the moment the objects is accessed the first time. E.g. we actually want to be able to have rotten code in the application that could stay there forever unnoticed because we don't care if it is used or not. But we don't want to load it into memory then.
For autoloading you should be aware of spl_autoload_register which allows you to have more than one autoloader function. There are many reasons why this is generally useful (e.g. imagine you make use of third-party packages), however this dynamic magic box called Registry of yours, it's just the perfect tool for the job. A straight forward solution (and not doing any premature optimization) is to register one autoloader function for each class we have in the registry definition. This then needs a new type of loader and the autoloader function is just two lines of code or so:
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
The usage example again didn't change much, just the type of the loader:
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
Now you can be lazy as hell. And that would mean, to actually change the code again. Because you want to remote the necessity to actually put those files into that specific directory. Ah wait, that is what you asked for, so we leave it here.
Otherwise consider to configure the registry with callables that would return the instance on first access. That does normally make things more flexible. Autoloading is - as shown - independent to that if you actually can leave your directory based approach, you don't care any longer where the code is packaged in concrete (http://www.getcomposer.org/).
The whole code-example in full (without registry.json and test.class.php):
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class) {
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
I hope this is helpful, however this is mainly playing in the sandbox and you will crush that the sooner or later. What you're actually want to learn about is Inversion of Control, Dependency Injection and then about Dependency Injection containers.
The Registry you have is some sort of smell. It's all totally full of magic and dynamic. You might think this is cool for development or for having "plugins" in your system (it's easy to extend), however you should keep the amount of objects therein low.
Magic can be hard to debug, so you might want to check the format of the json file if it makes sense in your case first to prevent first-hand configuration issues.
Also consider that the registry object passed to each constructor is not one parameter but represents a dynamic amount of parameters. This will start to create side-effects the sooner or later. If you are using the registry too much, then more the sooner. These kind of side-effects will cost you maintenance a lot because by design this is already flawed, so you can only control it with hard work, heavy integration tests for the regressions etc..
However, make your own experiences, it's just some outlook not that you tell me later I didn't notice it.
For your second question: the use of __autoload is discouraged and should be replaced with a spl_autoload_register. What the autoloader should do is split namespace and class:
function __autoload( $classname )
{
if( class_exists( $classname, false ))
return true;
$classparts = explode( '\\', $classname );
$classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
$namespace = implode( '\\', $classparts );
// at this point you have to decide how to process further
}
Depending on you file structure I would suggest building a absolute path based on the namespace and classname:
define('ROOT_PATH', __DIR__);
function __autoload( $classname )
{
if( class_exists( $classname, false ))
return true;
$classparts = explode( '\\', $classname );
$classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
$namespace = implode( '\\', $classparts );
$filename = ROOT_PATH . '/' . $namespace . $classfile;
if( is_readble($filename))
include_once $filename;
}
I've took the PSR0 approach, where the namespace is part of the path.

Autoloader for functions

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?
There is no function auto-loader for functions. You have four realistic solutions:
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.
Pre-load all functions. Since you're using classes, you shouldn't have that many functions, so just pre-load them.
Load functions prior to using them. In each file, require_once the function files that are going to be used in that file.
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);
}

Categories