aura/includer

Include multiple files from specified directories, in order, with variables extracted into a limited include scope.

Installs: 770

Dependents: 1

Stars: 9

Watchers: 8

Forks: 6

Language: PHP

2.0.2 2015-03-27 19:54 UTC

README

Provides a facility to include multiple files from specified directories, in order, with variables extracted into a limited include scope.

Foreword

Installation

This library requires PHP 5.3 or later; we recommend using the latest available version of PHP as a matter of principle. It has no userland dependencies.

It is installable and autoloadable via Composer as aura/includer.

Alternatively, download a release or clone this repository, then require or include its autoload.php file.

Quality

Scrutinizer Code Quality Code Coverage Build Status

To run the unit tests at the command line, issue phpunit at the package root. (This requires PHPUnit to be available as phpunit.)

This library attempts to comply with PSR-1, PSR-2, and PSR-4. If you notice compliance oversights, please send a patch via pull request.

Community

To ask questions, provide feedback, or otherwise communicate with the Aura community, please join our Google Group, follow @auraphp on Twitter, or chat with us on #auraphp on Freenode.

Getting Started

The Example Scenario

Let's say you have a series of packages, modules, plugins, etc. To do its setup work, your framework or foundation needs to include certain files from each of thse modules, such as configuration or routing files.

For our examples, the module directory structure will look like this:

modules/
    foo/
        autoload.php
        config/
            default.php
            testing.php
        routes.php
    bar/
        autoload.php
    baz/
        autoload.php
        config/
            default.php

An example autoload.php file might look like this:

<?php
$loader->addNamespace('Module\Foo', __DIR__);
?>

An example config file might look like this:

<?php
$config->setValue('db_host', 'localhost');
?>

An example routes.php file might look like this:

<?php
$router->setPath('/blog/read/{id}', function ($id) {
    // logic for the blog "read" action
});
?>

Because of the shared variables being used in each file, we need them to be available, but we also want each file to be kept separate from the global scope.

When including the configuration files, we need both the default and an additional "mode" for overrides to the defaults.

If a file is missing, we can skip it without ill effect.

Accomplishing The Task

The Includer makes this scenario, and others like it, relatively easy. First, we instantiate the Includer:

<?php
use Aura\Includer\Includer;

$includer = new Includer;
?>

Next, we set the various directories we need to look through for files to include:

<?php
$includer->setDirs(array(
    '/path/to/modules/foo',
    '/path/to/modules/bar',
    '/path/to/modules/baz',
));
?>

Then we set the files to look for in each of the directories (we will include both the default config and an override testing config):

<?php
$includer->setFiles(array(
    'autoload.php',
    'config/default.php',
    'config/testing.php',
    'routes.php',
));
?>

Because the files happen to need local variables, we create them first, then make them available to the include files:

<?php
$loader = new Loader(...);
$config = new Config(...);
$router = new Router(...);

$includer->setVars(array(
    'loader' => $loader,
    'config' => $config,
    'router' => $router,
));
?>

Finally, after setting up the directory, files, and variables, we call the load() method:

<?php
$includer->load();
?>

This will create a separate scope for each include file, extract the variables into that limited scope, and then include the file within that limited scope. This means that no include file can affect the global state of the application, except through the injected variables.

Include Order

By default, the Includer will include files in "directory order", represented by the constant Includer::DIR_ORDER. This means that the Includer visits the first directory and attemps to load all the files noted in that directory, then proceeds to the next directory. Given our above example, the loading for Includer::DIR_ORDER would be:

# first dir
modules/foo/autoload.php
modules/foo/config/default.php
modules/foo/config/testing.php
modules/foo/routes.php
# second dir
modules/bar/autoload.php
# third dir
modules/baz/autoload.php
modules/baz/config/default.php

Alternatively, you can specify load(Includer::FILE_ORDER) to load files in "file order". This means that the loader attemps to load the first file from each directory, then the second file from each directory, and so on. Given our above example, the loading for Includer::FILE_ORDER would be:

# first file
modules/foo/autoload.php
modules/bar/autoload.php
modules/baz/autoload.php
# second file
modules/foo/config/default.php
modules/baz/config/default.php
# third file
modules/foo/config/testing.php
# fourth file
modules/foo/routes.php

Strict Processing

By default, the Includer is relatively strict about what path combinations it will actually include. It will convert the directory + file path using realpath() to get the absolute path, and then check to see if that absolute path is in the same directory as specified in the Includer. (This is because it's possible to use ../ and symbolic links to point to file locations outside the specified directory.) Files that are not readable, or that are outside the specified directory, will not be included.

This type of processing is sometimes too strict; if you use symbolic links, for example, the strict processing may exclude those files. To turn off strict process, and only check if the file is readable, call setStrict(false).

<?php
// turn off strict processing
$includer->setStrict(false);
?>

Globbing

Under the hood, the Includer uses glob() to find files. This means you can use wildcards in the filenames to include files.

<?php
// load all '.php' files in each of the directories
$includer->addFiles(array(
    'config/*.php',
    'routes/*.php'
);
?>

Cache File

If you have dozens or scores of files that need to be included, that amount of file system activity can be a performance drain. To mitigate this, it can be useful to cache the files that would have been included.

The Includer has a read() method to get the contents of the files to be included and concatenate them, returning the concatenated contents for you to cache in a file of your choosing. You can then point the Includer to that cached file; if it exists, the Includer will use that file instead of including the various different directory and file path combinations.

First, we get the text of the concatenated files using the read() method. By default, it will concatenate the files in Includer::DIR_ORDER, but you can specify read(Includer::FILE_ORDER) if you prefer.

<?php
$text = $includer->read();
?>

The read() method will get the contents of each file, trim it, strip any leading and trailing <?php ?> tags, replace the __FILE__ constant with the equivalent string file name, and replace the __DIR__ constant with the equivalent string directory name. (These replacements reflect the fact that the code is being copied from its original location to a new location, and the constants expect the value of thr original location.)

Now that we have the contents of the files, we add an opening <?php tag and the time we created it, and then save it as a cache file:

<?php
$text = '<?php /** '
      . date('Y-m-d H:i:s')
      . ' */' . PHP_EOL . PHP_EOL
      . $text;

file_put_contents('/path/to/cache_file.php', $text);
?>

Finally, we tell the Includer where the cache file is. If it is readable, the Includer will use it on load(); otherwise, it will include the various directory and file combinations.

<?php
$includer->setCacheFile('/path/to/cache_file.php');
$includer->load(); // uses the cache file if it exists
?>

Debugging

Sometimes it will be useful to see what files the Includer actually found. Use the getDebug() method to return an array of information about what the Includer found, in what order, and in what mode.