Level-2 / Axel

A modular, extensible, fast, ultra-lightweight PHP autoloader with inbuilt caching
15 stars 5 forks source link

Missing code or PHP version of smr? #1

Closed LexLythius closed 6 years ago

LexLythius commented 9 years ago

I was curious about your autoloader implementation (especially after the PSR rant) but all I see is its announcing readme.

Or is this repo similar in spirit to smr?

TRPB commented 9 years ago

I had never got around to sharing this as it's nothing special really. Anyone could throw something like this together in a few minutes :)

I've uploaded my WIP implementation, usage is as follows. You'll need a class that implements \Axel\Cache like so (although after taking a look at it with fresh eyes, I'm inclined to remove the cache interface entirely and allow passing anything that implements ArrayAccess as it's more reusable)


class FileCache implements \Axel\Cache {
    private $dir;

    public function __construct($dir) {
        $this->dir = $dir;
    }

    public function save($name, $data) {
        file_put_contents($this->dir . DIRECTORY_SEPARATOR . $name, serialize($data));
    }

    public function load($name) {
        if (is_file($this->dir . DIRECTORY_SEPARATOR . $name,)) return unserialize(file_get_contents($this->dir . DIRECTORY_SEPARATOR . $name,));
        else return null;
    }
}

Then you can instantiate the autoloader

$cache = new FileCache('/tmp');
$axel = new \Axel\Axel($cache);

Once instantiated you can add modules to it. Each module takes a class name in its locate method and returns a file. I supplied an example which maps a namespace to a specified dir.

For example:


$axel->addModule(new \Axel\Modules\Namespacemap('../some/code', '\Some\Code'));

Which will map the namespace \Some\Code to the dir ../some/code/ the namespacemap example also supports lowercasing filenames/dirs as constructor args.

That module is only really an example, the important thing is that anyone could write a module that implements \Axel\Module and it will work with the autoloader.

You can register as many modules as you like with the autoloader, in an extensible project each library could register it's own module with the autoloader, alternatively it could re-use an existing implementation such as a the supplied NamespaceMap or a generic PSR-0 implementation.

This means:

TRPB commented 9 years ago

I wanted to quickly elaborate on this as you specifically mentioned my PSR-0 article. If you wanted this to replace PSR-0 you would need to add your own module which registered other modules in the autoloader. Sounds complicated but it's not really...

First create a module that knows about the autoloader and knows the directory that libraries are stored in. Then, find the root namespace of a loaded class to load a pre-defined file.

class DirectoryRegisterAxelModule implements \Axel\Module {
    private $libraryDir;
    private $axel;

    public function __construct(\Axel\Axel $axel, $libraryDir) {
        $this->axel = $axel;
        $this->libraryDir = $libraryDir;        
    }

    public function locate($className) {
        $rootNs = explode('\\', rtrim('\\', $className))[0];
        if (file_exists($this->libraryDir . DIRECTORY_SEPARATOR . $rootNs . DIRECTORY_SEPARATOR . 'autoload.json')) {
            $json = json_decode(file_get_contents($this->libraryDir . DIRECTORY_SEPARATOR . $rootNs . DIRECTORY_SEPARATOR . 'autoload.json'))));

            if (isset($json->include)) {
                foreach ($json->include as $file) require_once $this->libaryDir . DIRECTORY_SEPARATOR . $rootNs . DIRECTORY_SEPARATOR . $file;
            }

            foreach ($json->modules as $key => $value) {
                $this->axel->addModule(new $key(...$value));
            }
        }
    }
}

Which if registered with the autoloader:

$axel->addModule(new DirectoryRegisterAxelModule($axel, '../lib/'));

Then for example:

new \Smarty\Template();

would trigger the autoload then call DirectoryRegisterAxelModule::locate() which in turn would look for autoload.json at the location /lib/Smarty/autoload.json

The file could be structured like so:

{
 'include': 'smartycustomautoloder.php',
 'modules': {
    'namespacemap': ['/smarty/somefiles', '\Smarty\SomeFiles'],
    'customautoloader' : ['constructor', 'args', 'for', 'autoloader', 'in', 'smartycustomautoloader.php']
   }

So then, when a file in the \Smarty namespace was autoloaded, the namespace map and customautoloader would be registered, smarty's custom autoloader would be included automatically, then both would be registered by the autoloader.

The autoloader would then realise that DirectoryRegisterAxelModule hasn't located the class and call namesapcemap::locate then customautoloader::locate until one of them found it. Because of the caching the autoload.json file and custom autoloaders would only ever be loaded when a name => location path wasn't cached.. so only the first time the smarty class was used.

This isn't perfect as the implementation here as a dependency on axel, if I were to suggest this as a standard I'd provide a generic interface that any autoloader could recognise and standardise the .json format

interface Autoloader {
 public function locate($className);
}

That way any extensible autoloader could use the interface and json format while having it's own implementation.

Better than PSR-0 because it gives developers more control how their files are structured and legacy libraries can easily be added by providing the relevant autoload.json rather than restructuring all the files and putting them into namespaces.... if you wanted you could even have multiple library directories by registering multiple DirectoryRegisterAxelModule instances.