Open Power Libs 3.0
| Copyright © Invenzzia Group 2008-2011 |
| Available under the terms of license: Open Publication License |
| Generated: 23.07.2011 |
Table of Contents
- 1. Introduction
- 2. Installation
- 3. Conventions
- 4. Open Power Autoloader
- 5. Open Power Collector
- 6. Open Power Error Handler
- 7. Appendices
- Table of Contents
1. Introduction - 2. Installation
Next »
1. Introduction
What Open Power Libs is?
Open Power Libs is a collection of specialized utility libraries for PHP 5.3+. While it is not a complete framework, because it focuses on certain problems only, it can be used to support some existing frameworks or to build a new one. The libraries have been designed with strict code and API quality standards that guarantee you simple integration and flexibility.
The collection currently consists of the following libraries:
- Open Power Autoloader - collection of fast, universal class autoloaders.
- Open Power Cache - caching library implementing different, efficient caching strategies.
- Open Power Collector - generic data collector. Possible usage includes system configuration and HTTP request data collection.
- Open Power Dependency - dependency injection container.
- Open Power Pagination - pagination component.
- Open Power Template - universal template engine.
History
The origins of Open Power Libs are dated to 2004, when a group of programmers decided to create an open-source discussion board. In addition, the group planned to create a set of utility libraries for the purpose of the main project that could be also used as standalone components. The main project failed very quickly, but the libraries were being still developed, resulting in creation of Open Power Libs 1.x foundation which consisted of three libraries: Open Power Driver, a PDO-based database driver, Open Power Forms, a form processor, and Open Power Template, a template engine.
In 2008, the programmers involved in the project, led by Tomasz Jędrzejewski, formed an open-source development group called Invenzzia, focused on creating Open Power Libs 2.0. Next year, it managed to release a stable core and the new version of template engine, which turned out to be the biggest success of the group. The other libraries, although being usable, stuck at various development stages due to the design problems. In addition, the appearance of PHP 5.3 made the library structure a bit outdated and it become clear that the work must be started again.
Followed by a proper research and gaining experiences of the new PHP projects, especially Doctrine 2 and Symfony 2, in the early 2011, works on Open Power Libs 3 finally began.
Requirements
PHP requirements:
- PHP 5.3.2+,
- Intl enabled,
- PCRE enabled,
- PHAR enabled,
- SPL enabled,
- APC or Memcached or chdb (or any combintation of them) in order to have the caching support.
Except the caching PECL extensions, the remaining ones are available by default in a typical PHP installation. Unless you disabled them explicitely, they are here.
Library requirements:
- Any class autoloader compatible with PSR-0 class naming standard.
- Symfony 2 Console Component - for the CLI operations.
- Symfony 2 YAML Component - for operations on YAML files,
How the libraries are designed?
Open Power Libs 3, as an Invenzzia Group project, is a subject to strict code and API quality standards. Their purpose is to ensure that the final result is free of design problems, flexible and easy-to-deploy. It is achieved by implementing the following goals:
- No magic - magic behaviours and PHP language elements make the code harder to read and debug, by hiding the real nature of the element. In addition, they are not supported properly by IDE-s.
- Explicit dependency injection - all the object dependencies must be injected explicitely by the programmer using the OOP design rules. It simplifies the customization and proper testing.
- No static class fields - static class fields introduce a global state which is hard to debug, test and manage.
- Only local side effects - a side effect is every system state change made by the function. Despite the name, sometimes side effects are good and the intended effects. We allow only local, well-defined side effects, so that they are easy to track and debug.
- Focusing on code reuse - reinventing the wheel is not a good idea. There is a plenty of good code created so far. We do not reinvent wheel, if there is a third party, well-designed component that is a subject of similar quality standards and satisfies our needs.
- Table of Contents
2. Installation - 1. Introduction
« Previous - 3. Conventions
Next »
2. Installation
In this chapter, we describe, how to install Open Power Libs projects in your development environment. There are three different ways for this:
- manual installation,
- PHAR archives,
- PEAR installation.
Component dependency diagram
The component diagram below shows the dependencies between OPL libraries and third party components. You can use it to check whether you have installed everything you need for proper work.

Installation procedure
Before installing Open Power Libs 3, please ensure that you understand PSR-0 class naming standard, because it is very important to the project file organization.
Manual installation
We assume that your project directory tree contains some /lib or /src directory, where we can store libraries and (typically) the framework source code.
- download the archive from www.invenzzia.org,
- extract the downloaded archive somewhere,
- copy the
/src/Opldirectory from the downloaded archive to the/src/Opldirectory in your project directory tree, - configure the autoloader (see below).
This is the basic installation that would allow you to run the downloaded code. Note that other libraries may require some additional steps here.
PHAR installation
Open Power Libs 3 can be also downloaded as a single, self-contained PHAR archive. Just put it somewhere in your project directory and configure the autoloader to load the Opl namespace from that archive.
PEAR installation
Open Power Libs 3 libraries can be also installed via PEAR installer:
pear channel-discover pear.invenzzia.org
pear install OPL_Libraryname
After installing the libraries, you must configure the autoloader in your project to load OPL classes.
Configuring autoloaders
Once we have successfully copied the files, we must configure some autoloader. Open Power Libs will work with any autoloader compatible with PSR-0 standard, so you do not have to install or use Open Power Autoloader. The libraries share the same top-level namespace: Opl, and the same primary directory - you just have to configure the path to that namespace and it should handle properly any OPL library installed later.
A sample configuration for the SplClassLoader:
$symfonyLoader = new SplClassLoader('Symfony', '/path/to/src'); $symfonyLoader->register(); $oplLoader = new SplClassLoader('Opl', '/path/to/src'); $oplLoader->register();
A sample configuration for the OPL generic autoloader:
require('../src/Opl/Autoloader/GenericLoader.php'); $loader = new Opl\Autoloader\GenericLoader('../src/', '\\'); // 'Opl' is in ../src $loader->addNamespace('Opl'); // 'Symfony' is in ../src too. $loader->addNamespace('Symfony'); $loader->register();
See the autoloader chapter in order to learn, how to use the available OPL autoloader.
- Table of Contents
3. Conventions - 2. Installation
« Previous - 3.1. Command line interface
Next »
3. Conventions
This section describes some general conventions used in Open Power Libs.
- 3. Conventions
3.1. Command line interface - 3. Conventions
« Previous - 3.2. Exceptions
Next »
3.1. Command line interface
Currently, many PHP applications provide a command-line interface tool which allows the developer and administrator to perform certain tasks from the operating system command line, instead of a web browser. Some of the libraries from the OPL foundation contain default implementation of various CLI commands that can be used in your system. However, OPL does not introduce yet another CLI environment, but rather uses the one from Symfony 2. In order to use them in your application, you must install Symfony\Component\Console component in your project.
Conventions
In OPL, the CLI commands are always located under Opl\Projectname\Command namespace. You are guaranteed that these classes are not loaded by any other part of the library, so if you do not want to use a command-line interface, you can easily ignore them. The command names are prefixed with opl:projectname: string, allowing you to avoid potential naming conflicts. Example commands:
opl:autoloader:build-class-map-Opl\Autoloader\Command\ClassMapBuild
Building a CLI
Open Power Libs does not provide a ready-to-use CLI. Instead, you must combine your own one, using the Symfony 2 CLI API, and binding requested OPL commands to it:
<?php // Configure the autoloader require_once(__DIR__.'./autoload.php'); // Configure the CLI environment and other tools, if necessary $cli = new \Symfony\Component\Console\Application('My CLI', '1.0'); $cli->setCatchExceptions(true); // Bind commands $cli->addCommands(array( new \Opl\Autoloader\Command\ClassMapBuild(), )); $cli->run();
Integration with third party tools
Trinity
Because Trinity Framework is built upon Open Power Libs, the CLI commands are available there by default. You do not have to install or configure anything in order to use them.
Symfony 2
Open Power Libs CLI commands can be directly integrated with Symfony 2, by writing or using an existing OPL bundle, and overwriting the registerCommands() method:
<?php use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\Console\Application; class OplBundle extends Bundle { public function registerCommands(Application $application) { $application->add(new \Opl\Autoloader\Command\ClassMapBuild()); // etc. } // end registerCommands(); } // end OplBundle;
Zend Framework
OPL commands cannot be integrated with Zend Framework, because this framework uses a different API for its CLI tasks.
See also:
- 3. Conventions
3.2. Exceptions - 3.1. Command line interface
« Previous - 4. Open Power Autoloader
Next »
3.2. Exceptions
Open Power Libs projects report problems with the exception mechanism available in PHP 5. The exception class hierarchy from Standard PHP Library is used. It introduces several basic exception classes, and defines their semantics. In addition, OPL defines a number of custom exceptions that follow these conventions.
SPL exceptions
The two basic exception classes are RuntimeException and LogicException. The first class is used for all the exceptions that are caused by the invalid input data or system configuration. They can be usually treated as user errors. Sample problems that fall under this category:
- specifying a negative database row identifier,
- attempting to access some page without permissions.
The second category represents various problems with the internal program logic. According to the manual, such exceptions are typically caused by bugs in the code and should lead to a fix.
Note that sometimes the code that wants to throw an exception cannot discover whether the problem has been caused by the end user error, or a bug in the system code. For example, when we attempt to empty a collection of elements, we can encounter an UnderflowException. Depending on the context and our needs, it can be caused both by the invalid data that have been entered, and a bug in the algorithm. In this case, a RuntimeException is preferred.
Unfortunately, there are lots of errors and inconsequences in the SPL exception descriptions in the official PHP manual, especially when compared to the earlier Doxygen documentation. Fortunately, those issues are known to the PHP team and we hope they will fix them soon.
OPL extensions
The default SPL exception classes do not cover all the possible problems. OPL libraries are allowed to define their own, domain-specific exceptions that extend either the base Exception class, or one of the SPL exceptions. The following conventions are used here:
- All the custom exceptions belong to
Opl\ProjectName\Exceptionnamespace. - All the custom exceptions use the
FooExceptionname. - The custom exceptions are allowed to extend the default exception interface with new methods.
Handling exceptions
Open Power Libs does not enforce using any concrete exception handling strategy. It is up to the programmer, where he would catch them, and what he would do with them. However, for the programmer's convenience, the foundation provides an Open Power Error Handler library, responsible for formatting and printing the error information from exceptions. The error handler can be extended in a number of ways, so that it could display lots of useful debugging information. We encourage you to check it out and see if it matches your needs.
See also:
- Table of Contents
4. Open Power Autoloader - 3.2. Exceptions
« Previous - 4.1. Theory of operation
Next »
4. Open Power Autoloader
Open Power Autoloader is a collection of universal class loaders for PHP 5.3+ with additional management tools. They handle PSR-0 class naming standard which has been recently adapted by many mainstream PHP projects. A single standard improves interoperability and allows OPA to support such projects, as Symfony 2 or Doctrine without any problems. The opposite relationship is also true - you can use autoloaders from these projects to load OPL code.
The autoloaders provided by this package have been designed to achieve the biggest possible performance. In modern PHP applications, class loading has a critical influence on speed. Even if we use opcode caching, such as APC, the autoloader must still translate the class name to the filesystem path. For projects with a greater number of classes, the parser can spend up to the 50% of the overall execution time in the autoloader code, so proper optimization in this place can give us a significant boost.
- 4. Open Power Autoloader
4.1. Theory of operation - 4. Open Power Autoloader
« Previous - 4.2. Available autoloaders
Next »
4.1. Theory of operation
In order to use Open Power Autoloader properly, the programmer must agree to a couple of assumptions. The assumptions have been chosen to maximize the performance and the application scalability, and reduce the number of dependencies.
Autoloader's place
Here, we meet the first and probably the most important assumption. Without it, using Open Power Autoloader has no sense.
Autoloader is a part of the runtime environment, not the application.
This assumption states that the application should not be aware of the autoloader's existence, and even it should not contain any reference to it. The only application responsiblity is to follow the class naming convention and hoping that there is a configured autoloader which is able to load them.
The autoloader is instantiated and configured before the application, in the entry script (typically, index.php file). Once everything works, index.php fires the rest of the application:
<?php require('../src/Opl/Autoloader/GenericLoader.php'); $loader = new Opl\Autoloader\GenericLoader('../src/'); $loader->addNamespace('Opl'); $loader->addNamespace('Framework'); $loader->addNamespace('Application'); $loader->register(); $application = new Application\MyApplication(); $application->run();
Note that we do not pass the autoloader reference to the application.
What can the application do?
Create the class objects:
$object = new Application\MyClass();
Use spl_autoload_call() to load the class explicitely:
spl_autoload_call('Application\\Controller\\FooController');
Use reflection to discover the path to the class source, if it is really necessary:
$selfReflection = new \ReflectionObject($this); $namespace = $selfReflection->getNamespaceName(); $directory = dirname($selfReflection->getFileName()); if(file_exists($directory.'Controller/'.$controllerName.'.php')) { $controllerClass = $namespace.'\\Controller\\'.$controllerName; $controller = new $controllerClass(); }
What practices are forbidden?
The application must not rely on the autoloader interfaces:
public function setAutoloader(Opl\Autoloader\GenericLoader $loader) { $this->autoloader = $loader; } // end setAutoloader();
The application must not use the autoloader objects directly to load classes:
$this->loader->loadClass('Foo\\Bar'');
The application must not include the PHP files with classes explicitely:
require_once('Framework/Router/StandardRouter.php'); class SomeStuff { } // end SomeStuff;
The application should not use include_path.
Discover your needs
There is no autoloader suitable for every possible scenario.
This convention explains why Open Power Autoloader provides six different autoloaders instead of one. An attempt to write an universal class loader that would work everywhere results in heavily overloaded and slow code full of unnecessary options. In OPA, each autoloader is specialized to work in a single, strictly defined scenario and has well-defined costs and benefits. You will use one autoloader in your development environment, and a different one in production systems. Because the application is not aware of the concrete autoloader, you can change them without any problems, and experiment with different configurations to achieve the biggest possible performance or other design goals.
Application deployment
Once the application is deployed, the class structure changes rarely. The autoloader does not have to check anything dynamically.
This convention tells us, how OPA works on production servers. The available autoloaders can be divided into two primary groups:
- dynamic class loaders, which translate the class name into the file path on-the-fly. It gives us a great flexibility, because every change in the class structure is visible immediately, but costs us the performance.
- class map loaders, which use an array containing all the system class names and the paths to them. The map is precomputed during the deployment process, using the command-line additional tools and loaded by the autoloader at the start of the requests.
Is it usable?
The conventions might seem to be very restrictive at the first sight, but the practice shows they are not. Basically, everything you could need and you could do without them, is still possible. The main change is the clear responsibility separation which makes the code more portable and scalable. All you need to do is to change the way you think about class loading. The benefits are obvious.
These conventions might be hard to apply in the first-generation frameworks, such as Zend Framework 1.x. Although OPA supports Zend naming convention, some classes have hard-coded dependencies on Zend_Loader. They must be either removed by the programmer and replaced by some kind of generic code, or we must configure Zend_Loader, too. However, OPA can work without any problems with Symfony 2, which also adapted similar assumptions and conventions for the class loading process.
- 4. Open Power Autoloader
4.2. Available autoloaders - 4.1. Theory of operation
« Previous - 4.2.1. ApcLoader
Next »
4.2. Available autoloaders
Currently, Open Power Autoloader provides six autoloaders. Below, you can find a short characteristics:
Production versus development usage
Production server autoloaders:
Opl\Autoloader\ApcLoaderOpl\Autoloader\ChdbLoaderOpl\Autoloader\ClassMapLoader
Development environment and low-traffic website autoloaders:
Opl\Autoloader\GenericLoaderOpl\Autoloader\UniversalLoader
PHAR archive loaders:
Opl\Autoloader\PHARLoader
Subnamespace support
Subnamespaces can be defined in:
Opl\Autoloader\ApcLoaderOpl\Autoloader\ChdbLoaderOpl\Autoloader\ClassMapLoaderOpl\Autoloader\UniversalLoaderOpl\Autoloader\PHARLoader
Top-level namespaces only:
Opl\Autoloader\GenericLoader
Caching support
The autoloaders with shared memory cache support:
Opl\Autoloader\ApcLoader(requires APC extension)Opl\Autoloader\ChdbLoader(requires chdb extension)
- 4.2. Available autoloaders
4.2.1. ApcLoader - 4.2. Available autoloaders
« Previous - 4.2.2. ChdbLoader
Next »
4.2.1. ApcLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.3.0 |
This autoloader is a modification of ClassMapLoader which supports caching the class map in the shared memory provided by Advanced PHP Cache extension. The extension is not currently bundled with the standard PHP distribution, but it is available on many hostings.
When the autoloader is run for the first time, it reads the class map from the same file, as ClassMapLoader, but also stores it in the shared memory. Since then, it uses the map in the shared memory, reducing the number of disk operations.
Basic usage
require('../src/Opl/Autoloader/ApcLoader.php'); $loader = new \Opl\Autoloader\ApcLoader('../src/', '../output/classMap.txt', 'apcKey'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->register();
The only difference between ApcLoader and ClassMapLoader is the third constructor argument which defines the APC key, where the map should be stored. The autoloader does not set any expiration time.
Limitations
APC does not provide an inter-process shared memory. In the FastCGI installation, each FastCGI process uses its own shared memory. In addition, the command line interface cannot see the FastCGI/PHP module shared memory, too. If the class map is changed, we must either wait until the processes are respawned or restart PHP.
See also:
- 4.2. Available autoloaders
4.2.2. ChdbLoader - 4.2.1. ApcLoader
« Previous - 4.2.3. ClassMapLoader
Next »
4.2.2. ChdbLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.3.0 |
This autoloader is a modification of ClassMapLoader which supports caching the class map in the shared memory provided by chdb extension. The extension is not currently bundled with the standard PHP distribution.
The chdb extension provides a constant, read-only hash database stored in a memory-mapped file, a standard shared memory mechanism provided by Unix systems. The file works like a swap memory - the pages are loaded into RAM, when they are needed for the first time. Chdb does not permit random-access modifications of the file contents in order to achieve greater performance and constant-time O(1) read complexity, which is not necessarily true for APC.
Basic usage
require('../src/Opl/Autoloader/ChdbLoader.php'); $loader = new \Opl\Autoloader\ChdbLoader('../src/', '../output/classMap.chdb'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->register();
The class map for the ChdbLoader must be generated with opl:autoloader:build-class-map CLI command with the --type=chdb option. If the class hierarchy is changed, we simply call this command again, and the new map will be available for the processes immediately. The command line tool uses a nice trick that allows to avoid race conditions during saving the modified map into the file.
Limitations
The chdb does not permit random-access modifications of the class map file. If the class hierarchy is changed, the entire memory-mapped file must be regenerated. For the class map, this is not actually a problem, because the class hierarchy does not change very often on production servers.
Chdb extension does not work on Windows.
See also:
- 4.2. Available autoloaders
4.2.3. ClassMapLoader - 4.2.2. ChdbLoader
« Previous - 4.2.4. GenericLoader
Next »
4.2.3. ClassMapLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.1.0 |
ClassMapLoader is a standard universal class loader designed for big traffic websites. It uses an externally generated class map loaded into memory at the beginning of the script. The map contains the filesystem locations of all the classes found in the system. It is accompanied by extra tools that allow you to build such a map automatically.
Basic usage
Before using this autoloader, we must generate a class map using the command-line interface. Please refer to the CLI and Configuration chapters to read more about generating the class maps and writing the Open Power Autoloader configuration files.
Class map generation:
$ ./cli opl:autoloader:build-class-map path/to/configuration.xml --type=serialized
Once the map is ready, we can use ClassMapLoader in our application. The configuration is very similar to GenericLoader:
require('../src/Opl/Autoloader/ClassMapLoader.php'); $loader = new \Opl\Autoloader\ClassMapLoader('../src/', '../output/classMap.txt'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->register();
The map does not keep absolute paths, so you can still arrange the project layout through the autoloader reconfiguration.
Namespace configuration
The namespace configuration for the autoloader is very similar to the one in GenericLoader. There only difference is that you cannot set a file extension, as it is the part of the class map.
Handling legacy code
The autoloader can handle both the new and old, legacy code in a single instance. The full class name is stored in the map, and the map building utility recognizes both classes in the namespaces, and those without them. You do not need any extra configuration.
Using cache for storing maps
The autoloader allows to use an optional memory cache for storing the class maps. The Open Power Cache interface object can be injected as a third argument to the class constructor. Please note that the cache classes must be loaded manually, because the autoloader is not configured yet. The example below shows, how to store maps with APC:
require('../src/Opl/Autoloader/ClassMapLoader.php'); require('../src/Opl/Cache/Cache.php'); require('../src/Opl/Cache/APC.php'); $loader = new \Opl\Autoloader\ClassMapLoader('../output/classMap.txt', '../src/', new Opl\Cache\APC(array('prefix' => 'project.', 'lifetime' => 86400)) ); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->register();
It is recommended to save the created cache instance somewhere and pass it to the initialized system later.
Note that APC storage will not work in the command line interface and CGI (but not FastCGI), as it requires a constantly working interpreter in order to gain any benefits from it.
Theory of operation
The class map is loaded during the autoloader initialization and there is no way to reload it later. When a class loading request arrives, the loader looks for the appropriate row in the map. If the class is not found, the request is passed to the next autoloader in the chain. Finally, it prepends the namespace path from the configuration to the path read from the map and loads the class file. The details about the map structure can be found in [Building class maps][autoloader.class-maps] section.
When to use?
This autoloader offers a bigger performance than GenericLoader at a cost of reduced flexibility. It is dedicated for big-traffic websites, where the performance is a critical issue. We do not recommend using it in the development environment, as it requires map regeneration on every class hierarchy change. For web and console applications stored in PHAR-s, you should choose PHARLoader.
Note that this autoloader can be used also for projects that do not support PSR-0, because the classes are extracted directly from the source.
In order to make use of the caching mechanisms, please use ApcLoader or ChdbLoader.
See also:
- 4.2. Available autoloaders
4.2.4. GenericLoader - 4.2.3. ClassMapLoader
« Previous - 4.2.5. PHARLoader
Next »
4.2.4. GenericLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.1.0 |
GenericLoader is a standard universal class loader designed for the development environment and low-traffic websites. It translates the class name to the filesystem path dynamically, so it is very scalable and flexible. Any change in the class hierarchy is supported without reconfiguration.
Basic usage
In order to use the autoloader, you must load the Opl\Autoloader\GenericLoader.php file manually and create an object of GenericLoader class:
require('../src/Opl/Autoloader/GenericLoader.php'); $loader = new \Opl\Autoloader\GenericLoader('../src/'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->register();
The class constructor takes up to two (optional) arguments:
- The default path to the namespace sources.
- The namespace separator (default is \ - PHP 5.3 namespaces).
Once we have an object, we must specify, what top-level namespaces will be handled by it. If the class does not belong to any of the registered namespaces, the autoloader skips it, passing the request to the next loader in the chain. Finally, we register the autoloader in the PHP core by calling register().
Namespace configuration
There are three methods for managing the top-level namespaces registered in the autoloader:
addNamespace($name)- registers a new top-level namespace in the autoloader,hasNamespace($name)- checks if the specified namespace is registered in the autoloader,removeNamespace($name)- removes the namespace from the autoloader.
By default, newly registered namespaces are assumed to be located under the path specified in the class constructor, and the files to have .php extension. If these values are different for a particular namespace, we can configure them as optional addNamespace() arguments:
$loader->addNamespace('Application', '../app/', '.class.php');
The registered namespace must not contain any namespace separator. It means that you cannot register subnamespaces in the autoloader, or set them custom paths. The following code is invalid and will not work:
$loader->addNamespace('Application\\Subnamespace', '../extra/');Note that the sub-namespaces are correctly handled as parts of the top-level namespaces. You just can't register them, and set a different location for them. In order to get the full sub-namespace support, please use UniversalLoader.
Handling legacy code
The namespace separator setting is applied for all the namespaces registered in the autoloader. However, it is possible to use the autoloader for the legacy code that does not use PHP 5.3 namespaces, by registering two autoloaders. The example below shows, how to load Open Power Template 2.1 together with the new code:
$nsLoader = new GenericLoader('../src/', '\\'); // OPL 3.0 $nsLoader->addNamespace('Opl'); // Symfony 2 $nsLoader->addNamespace('Symfony'); $nsLoader->register(); $legacyLoader = new GenericLoader('../src/', '_'); $legacyLoader->addNamespace('Opt'); $legacyLoader->register();
The second autoloader uses underscores as namespace separators which allows us to load the legacy code that follows the Foo_Bar_Joe naming convention.
Theory of operation
The class name translation algorithm is relatively simple. The good thing about it is that the average complexity does not depend on the number of the namespaces registered in the autoloader. This is the key to the performance. The translation procedure steps are:
- Find the first occurence of the namespace separator.
- Cut the substring from the beginning to the found namespace separator. This is the class top-level namespace.
- Check, if the top-level namespace is registered in the autoloader. If not, pass the request to the next autoloader in the chain.
- Find the last occurence of the namespace separator.
- Cut the substring from the last namespace separator to the end. This is the actual class name.
- Cut the substring from the beginning to the last namespace separator. This is the full namespace of the class.
- Replace underscores and namespace separators in the actual class name with the directory separators.
- Replace the namespace separators, but not underscores in the full namespace with the directory separators.
- Prepend the namespace path and append the extension.
- Load the file.
When to use?
You should use this autoloader in:
- the development environment,
- low-traffic websites.
You should not use this autoloader in:
- big-traffic websites (consider using ClassMapLoader instead),
- the applications, when you have to configure lots of specific paths to the sub-namespaces (consider using UniversalLoader instead).
See also:
- 4.2. Available autoloaders
4.2.5. PHARLoader - 4.2.4. GenericLoader
« Previous - 4.2.6. UniversalLoader
Next »
4.2.5. PHARLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.1.0 |
PHARLoader is a special version of the class map loader for self-contained PHAR-s. It has been designed with the following observations:
- the paths within the archive are constant and well-known,
- the contents of the archive usually do not change. If there is a change, we can build a new archive,
- when the archive is launched, PHP executes so-called stub, so there is no need to create an extra file for storing maps.
This means that the API for the autoloader can be maximally simplified, and it can be registered as a part of the stub, together with the map.
Building a PHAR
The example below shows an example code that constructs a PHAR archive and installs PHARLoader in the stub, together with the class map:
<?php $phar = new Phar('./output/myarchive.phar'); $phar->startBuffering(); $phar->buildFromDirectory('./source/'); $builder = new ClassMapBuilder(); $builder->addNamespace('MyNamespace', './source/'); $builder->buildMap(); $phar->setStub(file_get_contents(__DIR__.'/../src/Opl/Autoloader/PHARLoader.php').' $loader = new PHARLoader('.var_export($builder->getMap()).'); $loader->register(); __HALT_COMPILER();'); $phar->stopBuffering();
We use the [ClassMapBuilder][autoloader.class-maps] to generate a class map, which is later exported to the stub. Once the archive is completed, we can load it and use:
<?php require('./output/myarchive.phar'); $object = new \MyNamespace\MyClass();
When to use?
The autoloader has been designed for packed, self-contained console and web applications. It should not be used for archives that contain only code libraries, as it could lead to the namespace conflicts, if more than one is loaded in a single application.
See also:
- 4.2. Available autoloaders
4.2.6. UniversalLoader - 4.2.5. PHARLoader
« Previous - 4.3. Toolset
Next »
4.2.6. UniversalLoader
| Construct | Class |
|---|---|
| Namespace | Opl\Autoloader |
| Stability | beta |
| Versions | since 3.0.2.0 |
UniversalLoader is a standard universal class loader designed for the development environment and low-traffic websites. It translates the class name to the filesystem path dynamically, so it is very scalable and flexible. Any change in the class hierarchy is supported without reconfiguration.
The primary difference between UniversalLoader and GenericLoader is the support for subnamespaces. In GenericLoader, you can only register top-level namespaces, whereas in UniversalLoader registering subnamespaces is also allowed (at the performance cost).
Basic usage
In order to use the autoloader, you must load the Opl\Autoloader\UniversalLoader.php file manually and create an object of UniversalLoader class:
require('../src/Opl/Autoloader/UniversalLoader.php'); $loader = new \Opl\Autoloader\UniversalLoader('../src/'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->addNamespace('Doctrine\DBAL'); $loader->addNamespace('Doctrine\ORM'); $loader->register();
The class constructor takes up to two (optional) arguments:
- The default path to the namespace sources.
- The namespace separator (default is \ - PHP 5.3 namespaces).
Once we have an object, we must specify, what namespaces will be handled by it. If the class does not belong to any of the registered namespaces, the autoloader skips it, passing the request to the next loader in the chain. Finally, we register the autoloader in the PHP core by calling register().
Namespace configuration
There are three methods for managing the top-level namespaces registered in the autoloader:
addNamespace($name)- registers a new namespace in the autoloader,hasNamespace($name)- checks if the specified namespace is registered in the autoloader,removeNamespace($name)- removes the namespace from the autoloader.
By default, newly registered namespaces are assumed to be located under the path specified in the class constructor, and the files to have .php extension. If these values are different for a particular namespace, we can configure them as optional addNamespace() arguments:
$loader->addNamespace('Application', '../app/', '.class.php');
Handling legacy code
The namespace separator setting is applied for all the namespaces registered in the autoloader. However, it is possible to use the autoloader for the legacy code that does not use PHP 5.3 namespaces, by registering two autoloaders. The example below shows, how to load Open Power Template 2.1 together with the new code:
$nsLoader = new \Opl\Autoloader\UniversalLoader('../src/', '\\'); // OPL 3.0 $nsLoader->addNamespace('Opl\'); // Symfony 2 $nsLoader->addNamespace('Symfony'); $nsLoader->register(); $legacyLoader = new \Opl\Autoloader\UniversalLoader('../src/', '_'); $legacyLoader->addNamespace('Opl_'); $legacyLoader->register();
The second autoloader uses underscores as namespace separators which allows us to load the legacy code that follows the Foo_Bar_Joe naming convention.
Theory of operation
This autoloader has worse performance than GenericLoader, and furthermore, it depends on the number of the registered namespaces. For each loaded class, it iterates through the list of namespaces and attempts to match the namespace prefix to the class name. If the namespace is matched, the rest of the name is translated according to the PSR-0 rules and the file is loaded.
The most commonly used namespaces should be registered first, because they are matched in the first place.
When to use?
You should use this autoloader in:
- the development environment,
- low-traffic websites.
You should not use this autoloader in:
- big-traffic websites (consider using ClassMapLoader instead).
See also:
- 4. Open Power Autoloader
4.3. Toolset - 4.2.6. UniversalLoader
« Previous - 4.3.1. Configuration
Next »
4.3. Toolset
The toolset contains the implementations of various utility tools that can be used to diagnose or manage the autoloaders within our application. Note that this section describes the class interfaces and the theory of operation, whereas all of them are also available as Command-Line Interface commands.
- 4.3. Toolset
4.3.1. Configuration - 4.3. Toolset
« Previous - 4.3.2. ClassMapBuilder
Next »
4.3.1. Configuration
| Namespace | Opl\Autoloader\Toolset |
|---|---|
| Stability | beta |
| Versions | since 3.0.3.0 |
The OPA toolset uses a common configuration file format. The configuration file is an XML document with the following structure:
<?xml version="1.0" encoding="UTF-8"?> <autoload> <file-header><![CDATA[ <?php /** * The index.php beginning */ ]]></file-header> <file-footer><![CDATA[ $application = new Application(); $application->start(); ]]></file-footer> <export-files> <file type="serialized-class-map">./data/classMap.txt</file> <file type="chdb-class-map">./data/classMap.chdb</file> <file type="core-dump">./data/coreDump.txt</file> <file type="core-export">./web/core.php</file> <file type="index">./web/index.php</file> <file type="cli">./cli/cli.php</file> </export-files> <separator value="\"> <namespace name="Opl">./src/Opl</namespace> <namespace name="Symfony">./src/Symfony</namespace> <namespace name="Doctrine\DBAL">./src/DBAL</namespace> <namespace name="Doctrine\ORM" extension=".php5">./src/ORM</namespace> </separator> <separator value="_"> <namespace name="Zend">./src/Zend</namespace> </separator> </autoload>
Tag reference:
<autoload>- The main document tag.
<file-header>- Defines the file header for the scaffolding tool. It is pasted before the autoloader setup code.
<file-footer>- Defines the file footer for the scaffolding tool. It is pasted after the autoloader setup code.
<export-files>- This section contains paths to different files that might be generated by the toolset.
<file>- Defines a single file used by the toolset. It has one required attribute:
typewhich allows to specify an unique name for the defined path. <separator>- Defines a new namespace separator. The
valueattribute is required and defines the namespace separation string. Typically, we would want to have the default PHP namespace separator:\, and sometimes also_to support the legacy code. <namespace>- Defines a single namespace within the given separator and the path to it. The same namespace might be defined for different separators. Note that some tools (i.e. class map builder) might have problems with distinguishing the namespaces defined within different separators unless we append the separator to them, i.e.
Zend\andZend_.nameis the required attribute, andextensionis optional. It is used to define the non-standard file extension for the namespace.
Handling sub-namespaces
We are allowed to define both top-level namespaces and subnamespaces in the configuration file. All the tools are required to handle the subnamespaces correctly. However, not all of the autoloaders support them, and the scaffolding tool uses the information about subnamespaces to select the one that handles them.
File types
The following file types are considered as special:
serialized-class-map- path to the serialized class map used byClassMapLoaderandApcLoader.chdb-class-map- path to the chdb memory-mapped file with the class map used byChdbLoader. Note that this class map format is not portable among different operating systems!core-dump- path to the file with the application core dump generated byCoreTracker.core-export- path to the PHP file, where theCoreDumptool exports the data from the core dump.
The other file types are used by the scaffolding tool.
Class reference
The Opl\Autoloader\Toolset\Configuration class reads the OPA configuration files:
$config = new Opl\Autoloader\Toolset\Configuration('./config.xml'); if($config->hasSimpleNamespacesOnly()) { echo 'No subnamespaces defined.'; } echo $config->getFile('index');
Method reference:
__construct($filename)- Reads the configuration file. Might throw
FileNotFoundExceptionandFileFormatExceptionexceptions. getSeparators()- Returns the list of the defined separators.
getSeparatorNamespaces($separator)- Returns the namespaces defined for the given separator. The namespace list is an associative array, and each namespace definition consists of two keys:
pathandextension. getFiles($filterSpecial = false)- Returns the associative array of all the defined files. If the optional argument is set to true, the array does not contain the special file types.
getFile($type)- Returns the path to the given file type. If the type is not defined, an exception is thrown.
hasFile($type)- Returns true, if the given file type is defined.
getFileHeader()- Returns the file header. If the configuration does not contain the
<file-header>tag, it returns<?php getFileFooter()- Returns the file footer. If the configuration does not contain the
<file-footer>tag, it returns null. hasSimpleNamespacesOnly()- Returns true, if the configuration file does not have any subnamespaces defined.
- 4.3. Toolset
4.3.2. ClassMapBuilder - 4.3.1. Configuration
« Previous - 4.3.3. CoreDump
Next »
4.3.2. ClassMapBuilder
| Namespace | Opl\Autoloader\Toolset |
|---|---|
| Stability | beta |
| Versions | since 3.0.1.0 |
The class map builder is used to generate project class maps. A class map contains the names of all the classes, interfaces and traits (as of PHP 5.4) found within the specified directories and paths to the files declaring them.
Using ClassMapBuilder
The Opl\Autoloader\Toolset\ClassMapBuilder has a relatively simple interface:
$builder = new Opl\Autoloader\Toolset\ClassMapBuilder(); $builder->addNamespace('Opl', './src/'); $builder->addNamespace('Zend', './src/'); $builder->addNamespace('Symfony', './src/'); $builder->buildMap(); echo serialize($builder->getMap());
The namespace management methods are declared in the base class Opl\Autoloader\Toolset\AbstractTool. The class map builder does not contain any methods allowing the export to the given format. The programmer must use the getMap() method and save the returned result explicitely.
You can also use the opl:autoloader:build-class-map CLI command to build class maps. The CLI command supports the OPA configuration files.
Class map format
The map format is very simple. It is an associative array, where the index represents the fully qualified class name. The value is a pair of two values:
- 0 - the top-level namespace name.
- 1 - the relative path to the file with the class.
The path is specified relatively to the root directory specified in the addNamespace() method. Suppose that we have Foo\Bar class in ../src/Foo/Bar.php. The entry in the map will contain just the Foo/Bar.php part of the path. This allows to move the class hierarchy without the need to generate a new map, or define relative positions from different places. To identify the class hierarchy location, the autoloader must know the top-level namespace name.
The class map builder supports subnamespaces and PHP 5.4 traits.
Parsing rules
The class map builder extracts the class name by tokenizing the encountered PHP file and looking for namespace, class, interface, and trait (as of PHP 5.4) components. It assumes that a single file contains only one class, and once it finds one, the searching is terminated. You cannot use this class map builder for projects, where the files can contain more than one class. Note that it also does not check whether the class name is compatible with PSR-0 class naming standard, so it can be also used for other styles.
- 4.3. Toolset
4.3.3. CoreDump - 4.3.2. ClassMapBuilder
« Previous - 4.4. Core tracker
Next »
4.3.3. CoreDump
| Namespace | Opl\Autoloader\Toolset |
|---|---|
| Stability | beta |
| Versions | since 3.0.3.0 |
The Opl\Autoloader\Toolset\CoreDump class manipulates the application code, using the information generated by the CoreTracker. Currently, two forms of manipulation are supported:
- generate the list of
requireinstructions that load the application core explicitely, - concatenate the files containing the application core into a single PHP file.
Both of the manipulations allow to improve the performance by skipping the autoloading process for the classes, interfaces and traits that are needed in every HTTP request to our application. The concatenation reduces also the number of disk operations and file lookup. Even if we are using APC, the extension performs the stat operation on every file to check whether it has not been modified. Although this check can be disabled, sometimes we might not want to do so, or the administrator forbids us to do so.
Usage
The API of the class is relatively simple:
$dump = new Opl\Autoloader\Toolset\CoreDump(); $dump->addNamespace('Opl', './src/'); $dump->addNamespace('Zend', './src/'); $dump->addNamespace('Symfony', './src/'); $dump->loadCore('./core.txt'); $dump->exportRequireList('../web/requires.php'); $dump->exportConcatenated('../web/core.php'); $dump->exportConcatenated('../web/core2.php', true);
If the second argument of exportConcatenated() is set to true, the method cuts off the file header comments. The concatenation is safe for both namespaced and non-namespaced code. Both of them can be freely mixed within the single core file.
You can also use the opl:autoloader:core-dump-export CLI command to generate the core files. The CLI command supports the OPA configuration files.
See also:
- 4. Open Power Autoloader
4.4. Core tracker - 4.3.3. CoreDump
« Previous - 4.5. CLI commands
Next »
4.4. Core tracker
| Namespace | Opl\Autoloader |
|---|---|
| Stability | beta |
| Versions | since 3.0.2.0 |
Opl\Autoloader\CoreTracker is an autoloader decorator used for finding the application core. By core we understand a group of classes and interfaces that are necessary to start the application, and have to be loaded in every request. If we know they are needed, using traditional autoloading techniques does not bring us any benefits, but just costs us the execution time, where their names are translated into the file paths. Once we have found the core, we can skip the autoloading process for them and load them explicitely. A number of extra tools available in Open Power Autoloader helps us in this task.
Theory of operation
CoreTracker decorates the actual autoloader which is responsible for loading a class. Next, it remembers the class name. The constructed core is persisted between HTTP requests in a text file. If the text file is empty, the tracker simply writes the noted class names into it to create the initial list of classes. In the subsequent requests, it calculates the difference between the request-specific class list and the list from the file. Only the classes that appear in both of them, are saved in the file. By definition, the class that appears in the file and does not appear in the request (or vice versa) cannot be a part of the core.
Each request improves the core approximation. The programmer should walk through the application, generating as many different HTTP requests, as possible, to find the best core approximation. It is he who decides when to stop collecting the data and use the constructed list as the core.
How to use?
The core tracker is instantiated in the entry script, together with the autoloaders. Currently, it can decorate only a single autoloader instance:
require(LIB_DIR.'Opl/Autoloader/GenericLoader.php'); require(LIB_DIR.'Opl/Autoloader/CoreTracker.php'); $loader = new Opl\Autoloader\GenericLoader(LIB_DIR); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->addNamespace('Doctrine'); $tracker = new Opl\Autoloader\CoreTracker($loader, '../cache/core.txt'); $tracker->register(); // run the application here
Instead of registering the autoloader, we pass it to the CoreTracker class constructor. It also accepts the path to the core file. The location must be writeable by PHP. Then, we register the tracker in the PHP autoloader stack. Except registering, and unregistering, CoreTracker class does not have any other public operations.
How to process the core?
Once we have a core, we would like to do something with the collected data. The toolset contains one class responsible for processing the core: Opl\Autoloader\Toolset\CoreDump. Its functionality is packed into a CLI command Opl\Autoloader\Command\CoreDump:
./cli opl:autoloader:core-dump-export ../config.xml --export-type=require
This command exports the core to the form of a list of require statements that load the core classes explicitely. The statements are saved into the file core-export defined in the configuration file. The core must be saved in the location pointed by the file core-dump defined in the configuration file or specified with the --core option.
Currently, Open Power Autoloader supports the following export types:
--export-type=require- Creates the list of
requirestatements that load the core classes. --export-type=concat- Concatenates the core files into a single PHP file saved in the specified location. The concatenation is safe for both namespaced and non-namespaced code which can be freely mixed in a single file.
--export-type=concat --strip-comments- This option is similar to
--export-type=concat. It also strips the file header comments, reducing the output file size.
The core concatenation should not be used, if the core does not follow the PSR-0 class naming conventions!
- 4. Open Power Autoloader
4.5. CLI commands - 4.4. Core tracker
« Previous - 4.6. Performance
Next »
4.5. CLI commands
Open Power Autoloader tools are available through the Symfony 2 command-line interface. See the [conventions.cli] chapter to read more about it.
Configuration files
In order to use the CLI tools, a complete autoloader definition must be saved in an XML file. The definition contains the list of all the namespaces and namespace separators we want to use, as well as various file locations, where the autoloaders can find or export different data. The detailed structure of the configuration file is described here.
The path to the configuration file must be provided directly after the command name.
Available commands
Currently, two commands are available to the programmers.
opl:autoloader:build-class-map
The command builds a list of classes, interfaces and (as of PHP 5.4) traits that appear in the specified filesystem locations. The list is called a class map and is necessary for the ClassMapLoader, ApcLoader and ChdbLoader to work.
./cli opl:autoloader:build-class-map ../config.xml --type=serialized
The class map will be saved as a text file containing a serialized PHP array. This format is suitable for ClassMapLoader and ApcLoader.
./cli opl:autoloader:build-class-map ../config.xml --type=chdb
The class map will be saved as a memory-mapped file for the chdb extension. Once loaded, the file resides in the RAM till the modification. This format is suitable for ChdbLoader.
In order to use the
chdbexport type, you must have the chdb extension installed. It is not available for Windows systems.
The chdb memory map files are not portable between different operating systems and architectures.
The following file types must be defined in the configuration file:
serialized-class-map- the path to the serialized class map file.chdb-class-map- the path to the chdb class map file.
opl:autoloader:core-dump-export
Exports the application core to a single PHP file that loads it explicitely. The following combinations of options are supported:
./cli opl:autoloader:core-dump-export ../config.xml --export-type=require
Produces a list of require statements that load the core.
./cli opl:autoloader:core-dump-export ../config.xml --export-type=concat
Concatenates the sources of the PHP files containing the core into a single PHP file. The operation is safe for both namespaced and non-namespaced code which can be mixed within a single file.
./cli opl:autoloader:core-dump-export ../config.xml --export-type=concat --strip-comments
The result is similar to the ordinary --export-type=concat option, but also strips the file header comments from the concatenated files.
The following file types must be defined in the configuration file:
core-dump- the path to the core dump generated by theOpl\Autoloader\CoreTracker.core-export- the path to the location, where the commands save the resulting PHP file.
The path to the core dump can be also provided by the optional --core=PATH option.
Adding the commands to the CLI
Open Power Libs does not provide its own command-line interface. Instead, it provides some commands that can be used to build one. The details concerning installing the commands in the CLI application are described in the Command-line interface chapter. The example below shows, how to install the OPA commands in the Symfony CLI:
$cli->addCommands(array( new \Opl\Autoloader\Command\ClassMapBuild(), new \Opl\Autoloader\Command\CoreDump(), ));
See also:
- 4. Open Power Autoloader
4.6. Performance - 4.5. CLI commands
« Previous - 5. Open Power Collector
Next »
4.6. Performance
In the modern web applications with heavy object-oriented programming usage, autoloader operations can take a significant amount of the execution time. Caching systems do not help here, becasue even if they cache a particular file, PHP still must translate the class name into the file path in order to locate it in the shared memory. By optimizing the autoloader code, your application can get a remarkable boost. Performance was the primary goal while designing the autoloaders available in this package.
Theory of optimization
In order to make the fastest autoloader in the world, we must meet the following criteria:
- the autoloader should not have any external dependencies,
- the class name translation code should be short,
- the class name translation should not do unnecessary string operations,
- a single translation process should handle as many classes, as possible - breaking the process and passing the task to the next autoloader in the queue should not happen at all.
What makes autoloaders slow, is overloaded configuration, and rich feature set.
The autoloader benchmark
In the benchmark, we tested loading 200 files grouped in 10 top-level namespaces representing 10 different libraries. The test was repeated 20 times, and after removing the slowest result, the average time was calculated. The test was made on PHP 5.3.5 with APC, only the effective time spent in the autoloading routine was measured.
| Autoloader name | Average time | Comments |
|---|---|---|
| Manual loading | 0.0907 s | |
| OPL ClassMapLoader | 0.1005 s | |
| OPL GenericLoader | 0.1218 s | |
| Doctrine 2 Class Loader | 0.1450 s | A single instance can handle only one namespace - we have to register 10 autoloaders, not compatible with PSR-0 |
| SplClassLoader | 0.1649 s | A single instance can handle only one namespace - we have to register 10 autoloaders. |
| Symfony 2 Universal Class Loader | 0.1826 s | Allows to set different paths for sub-namespaces |
| Zend Framework 1.11 Loader | 0.2092 s | Advanced configuration and feature set, based on include_path settings which can make the result even worse, if the namespace are in completely different locations. |
If the project uses only a single top-level namespace, the Doctrine and SPL class loaders can actually be slightly faster, because they use simpler string operations. However, in reality this is a very rare situation. Typically, the project uses a couple of top-level namespaces.
Optimization with OPL autoloaders
In order to achieve the biggest performance, you should choose the proper autoloader to your situation. Each of them recognizes basically the same set of classes, so you can use them interchangeably in different environments. GenericAutoloader is designed for the development environments and low-traffic websites. The class names are translated dynamically, so it is the best for the development process, where the new classes appear all the time.
However, the dynamic translation costs us time. During the internal experiments, we used the algorithm from GenericAutoloader in a web application project. Despite using APC and active caching, the autoloader operations took more than 40% of overall request processing time. If we replace this 10-line class name translation code with something faster, the performance boost is significant.
As we can see from the benchmark, ClassMapLoader is much faster than GenericAutoloader, but it requires us to generate a static class map. If we modify the class structure, the map must be generated again in order to reflect the changes. It means that this autoloader is suitable for production environments and websites with bigger traffic.
For self-contained web and console applications packaged with PHAR, we should use PHARLoader installed directly in the archive stub. It is optimized for this use by removing all the unnecessary path configuration, as we can safely assume that once we have an archive, its content won't change, or the archive will be generated again from scratch. In addition, it allows to save the class map directly in the stub, instead of loading it from a serialized array.
- Table of Contents
5. Open Power Collector - 4.6. Performance
« Previous - 5.1. Getting started
Next »
5. Open Power Collector
In a typical web application there are many components that want to share some data to the other parts of the system. The most obvious case is the system configuration - a set of values loaded from a file or database which determines the way the application works. Open Power Collector provides a generic data collector that can be used for various purposes. It supports nested value sets, caching and loading the data from different sources. By default, the library provides some general-purpose data loaders that allow you to build a configuration system and a user request data collector. However, it is very easy to extend the library with custom loaders, adapting it to new situations.
Open Power Collector is also the default configuration system for other projects from the OPL foundation. You will certainly meet it every time you configure any OPL project.
- 5. Open Power Collector
5.1. Getting started - 5. Open Power Collector
« Previous - 5.2. Theory of operation
Next »
5.1. Getting started
Open Power Collector can be adapted to many different situations. First, we must choose the places, where we would use it. The most obvious case is a configuration system. The example below shows, how to configure the collector to work in this way:
<?php use Opl\Collector\Collector; use Opl\Collector\Configuration\IniFileLoader; $loader = new IniFileLoader('./config/'); $loader->setFile('config.ini'); $collector = new Collector(); $collector->loadFromLoader(Collector::ROOT, $loader); // the system: if($collector->get('system.module.option')) { doSomething(); } else { doSomethingElse(); }
As we can see, the data collector is a single object of Collector class. It contains a couple of methods for importing the data from arrays and objects called data loaders. Once it is populated, we can use the get() method to retireve the data. In this case, we used one of the predefined loaders, IniFileLoader which loads the data from an INI file. It is shown in the following listing:
system.module.option = true system.module.anotherOption = false system.module.someLimit = 30
The loaded options are organized into a hierarchical tree of nodes. Dots are used as node name separators. Let's consider a situation, where we must extract the entire system.module group in order to pass it to the interested module:
use Opl\Collector\ProviderInterface; class OurModule { public function doOperation(ProviderInterface $moduleConfig) { if($moduleConfig->get('option')) { doSomething(); } } // end doOperation(); } // end OurModule; $module = new OurModule; $module->doOperation($collector->get('system.module'));
The collector object can also produce so-called providers. They simply distribute the data. In our example, the module requires the system.module.option to be defined. However, it only knows the last part of the name: option, and the rest of the path is application-dependent. The solution is to get a provider for the entire system.module node and pass it to our module. If the module options are moved to some other location, we just change the key in the module operation call.
Internally, collectors are also data providers, but extended with extra methods to load the data from different sources. This means that the collector itself can be also passed to the module.
Visit data
Another possible usage is collecting the user visit information. Open Power Collector provides a couple of loaders for this task, as well:
<?php use Opl\Collector\Collector; use Opl\Collector\Visit\HostLoader; use Opl\Collector\Visit\ConnectionLoader; $visit = new Collector(); $visit->loadFromLoader(Collector::ROOT, new HostLoader()); $visit->loadFromLoader(Collector::ROOT, new ConnectionLoader()); echo 'The user comes to us from '.$visit->get('ip').'.<br/>'; echo 'He uses IPv'.$visit->get('ipVersion').' protocol<br/>'; echo 'The connection is '.($visit->get('secure') ? '' : 'not').' secured with SSL.';
- 5. Open Power Collector
5.2. Theory of operation - 5.1. Getting started
« Previous - 5.3. Caching
Next »
5.2. Theory of operation
Open Power Collector offers simple yet effective way for retrieving the data from various sources. There are three primary concepts upon which the library is built:
- Loaders - small classes that know, where the data are stored and how to load them into the system.
- Providers - classes that know how to share the data with the script.
- Collectors - extended providers with the ability to load the data with the loaders.
Loaders
A valid loader must extend the Opl\Collector\LoaderInterface class. It provides a single import() method. Its task is to produce a nested array of keys and their values. Below, you can see a sample implementation that returns some static data:
class StaticLoader implements LoaderInterface { public function import() { return array( 'user' => array( 'id' => 5, 'login' => 'John Doe', 'email' => 'john@example.com' ), 'session' => array( 'id' => 'foo', 'lastActivity' => 123456789 ) ); } // end import(); } // end StaticLoader;
There are no limits on the nesting level other than the PHP and system resource limits.
Loaders can load the data from everywhere. As you can see in the next chapters, they are used to build a configuration system thanks to a couple of loaders that load the XML, YAML or INI files. On the other hand, the Visit namespace collects various information about the HTTP request, and in the example above, we showed that they can also return session data. The whole library can be seen as a big data storage that offers them in the unified structure through a unified interface, no matter what those data are.
Collectors
The Collector class provides the interface to collect and access the data. It organizes them into a hierarchical tree of keys and their values. Each value is identified by a path of node names separated with a dot, such as foo.bar.joe. We can access both the scalar values, and the whole groups of nodes. For example, let's say that application.database.host keeps the database host setting. In simple applications, we can simply access each of the database connection properties explicitely, but what if the database connection is constructed by some factory? The collector allows you to pass the entire application.database group into the factory:
use Opl\Collector\ProviderInterface; use Opl\Collector\Collector; $collector = new Collector(); function dbFactory(ProviderInterface $provider) { return new PDO( $provider->get('dbtype').':'. 'host='.$provider->get('host').';'. 'dbname='.$provider->get('dbname'), $provider->get('dbuser'), $provider->get('dbpass') ); } // end dbFactory(); $pdo = dbFactory($collector->get('application.database'));
The data can be inserted into a collection from loaders or from arrays. In each case, we can specify the node, where the new keys should be mounted:
$collector->loadFromArray(Collector::ROOT, array( 'foo' => 'value 1', 'bar' => 'value 2', 'joe' => array( 'goo' => 'value 3', 'hoo' => 'value 4' ) )); // prints "value 1" echo $collector->get('foo');
In the example above, we mounted the new keys as root nodes, but there are also other possibilities:
$collector->loadFromArray('application.database', array( 'dbtype' => 'mysql', 'dbname' => 'foo', 'host' => 'localhost', 'dbuser' => 'root', 'dbpass' => 'root' )); // prints "mysql" echo $collector->get('application.database.dbtype');
Providers
`` specifies the interface to access the data. The class Provider is the default data provider that organizes the values into a hierarchical tree. The plain provider class does not, however, contain anything to insert the data into the object, so we cannot construct it explicitely. In fact, Provider objects are returned by collectors, where we attempt to get a whole group of keys. Let's get back to our database example:
use Opl\Collector\ProviderInterface; use Opl\Collector\Collector; $collector = new Collector(); function dbFactory(ProviderInterface $provider) { return new PDO( $provider->get('dbtype').':'. 'host='.$provider->get('host').';'. 'dbname='.$provider->get('dbname'), $provider->get('dbuser'), $provider->get('dbpass') ); } // end dbFactory(); $pdo = dbFactory($collector->get('application.database'));
Here, the $collector->get() call constructs a provider object and internally initializes it to point to the application.database node. In this way, the factory can still use the same interface to retrieve the data and does not have to know the fully qualified path to the connection configuration. Because we do not need to modify the tree anymore, the providers simply offer a way to access the data and nothing more. The new nodes can be added only with a collector.
For simple unit testing, mocking or more specific needs, the Opl\Collector\ProviderInterface interface specifies, how to access the data:
interface ProviderInterface
{
const THROW_EXCEPTION = 0;
const THROW_NULL = 1;
public function get($key, $errorReporting = self::THROW_EXCEPTION);
} // end ProviderInterface;
As the first argument of the get() method, we specify the key we want to get, and as the second argument - the error reporting mode. The providers should throw exceptions, if the key do not exists and $errorReporting is set to THROW_EXCEPTION. THROW_NULL indicates that the get() method should return null in this case.
- 5. Open Power Collector
5.3. Caching - 5.2. Theory of operation
« Previous - 5.4. Available loader sets
Next »
5.3. Caching
In some usage scenarios such as the configuration system, loading the data from the files every request is very inefficient. In this case we would like to cache the configuration data tree in a shared memory. Open Power Collector can be integrated with Open Power Cache in order to provide a caching service. The example below shows, how to use the Collector objects with APC:
use Opl\Cache\APC; use Opl\Collector\Collector; use Opl\Collector\Configuration\IniFileLoader; $apc = new APC(array( 'prefix' => 'myapp:', 'lifetime' => 86400 )); $collector = new Collector($apc, 'configuration'); if(!$collector->isCached()) { $loader = new IniFileLoader('./config/'); $collector->loadFromLoader(Collector:ROOT, $loader->setFile('config.ini')); $collector->save(); } echo $collector->get('some.key');
The second argument of the class constructor specifies the cache key, where the data should be stored. To check if the data are successfully loaded from a cache, we use isCached() method and to populate the cache again - save(). Note that this gives you a control over what data are cached and what are not. You can safely load part of the data from the cache, and generate the rest dynamically.
- 5. Open Power Collector
5.4. Available loader sets - 5.3. Caching
« Previous - 5.4.1. Configuration
Next »
5.4. Available loader sets
Open Power Collector provides a couple of default data loader sets that allow you to adapt it to the most common situations. In this section, we are going to describe them.
- 5.4. Available loader sets
5.4.1. Configuration - 5.4. Available loader sets
« Previous - 5.4.2. Visit
Next »
5.4.1. Configuration
| Namespace | Opl\Collector\Configuration |
|---|---|
| Stability | beta |
The configuration loaders allow to load the data from the various types of files. The currently supported formats are:
- INI files
- XML files (through the SimpleXML extension)
- YAML files (through the Symfony 2 YAML Component)
The loaders extend a common Opl\Collector\Configuration\FileLoader class which provides a unified interface for specifying paths and files to load. The list of sequentially scanned paths is provided in the constructor as an array:
$loader = new IniFileLoader(array( './directory1/', './directory2/', './directory3/', ));
If we have a single path, we can omit the array and pass the string as an argument.
The file to load is specified with the setFile() method, and to get the name of the current file, we can use getFile() respecitvely:
$loader->setFile('file.ini');
The method implements fluent interface:
$collector->loadFromLoader(Collector::ROOT, $loader->setFile('file.xml'));
Loading the data from configuration files takes a while. In order to optimize the configuration loading, you can combine your collector with Open Power Cache.
IniFileLoader
This loader loads the data from INI files. Each line defines a single option, and the key is the fully qualified path:
application.name = "My Application" application.database.dbtype = "mysql" application.database.dbname = "foo" application.database.host = "localhost" application.database.dbuser = "root" application.database.dbpass = "root"
XmlFileLoader
This loader loads the data from XML files. The XSD Schema is provided with the installation package in the xml/collector.xsd file. Below, you can find a sample XML structure:
<?xml version="1.0" encoding="UTF-8"?> <group xmlns="http://xml.invenzzia.org/opl/collection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="./xml/collection.xsd"> <group name="application"> <item name="name">My application</item> <group name="database"> <item name="dbtype">mysql</item> <item name="dbname">foo</item> <item name="host">localhost</item> <item name="dbuser">root</item> <item name="dbpass">root</item> </group> </group> </group>
The first <group> tag represents the root node and does not have a name. For the rest of the tags, providing the name attribute is obligatory. Each <group> tag may contain any number of both <group> and <item> tags in any order.
For the XML document validation, we can use the xmlint application.
YamlFileLoader
This loader loads the data from YAML files. It simply returns the array produced by Symfony 2 YAML Component parser:
application:
name: 'My application'
database:
dbtype: 'mysql'
dbname: 'foo'
host: 'localhost'
dbuser: 'root'
dbpass: 'root'
- 5.4. Available loader sets
5.4.2. Visit - 5.4.1. Configuration
« Previous - 5.5. Writing custom loaders
Next »
5.4.2. Visit
| Namespace | Opl\Collector\Configuration |
|---|---|
| Stability | alpha |
The visit loaders collect various data about the user visit.
HostLoader
Returns the data about the remote host. The registered keys:
ip- the remote IP addressbinaryIp- the binary form of the remote IP address (4 or 16 bytes, depending on the IP version)ipVersion- either 4 or 6.
ConnectionLoader
Returns the data about the connection. The registered keys:
port- the connection portisSecure- true, if this is a secure SSL connectionmethod- the HTTP request method nameprotocol- the protocol name. Currently the loader recognizes the following protocols:https,httpandwap.
- 5. Open Power Collector
5.5. Writing custom loaders - 5.4.2. Visit
« Previous - 6. Open Power Error Handler
Next »
5.5. Writing custom loaders
In order to write a custom loader, you must implement the Opl\Collector\LoaderInterface:
interface LoaderInterface { public function import(); } // end LoaderInterface;
The only method is import() which must collect the data and return them in a form of an associative array. The array can be nested to get a hierarchical structure:
class StaticLoader implements LoaderInterface { public function import() { return array( 'user' => array( 'id' => 5, 'login' => 'John Doe', 'email' => 'john@example.com' ), 'session' => array( 'id' => 'foo', 'lastActivity' => 123456789 ) ); } // end import(); } // end StaticLoader;
For loading the data from a file, we can utilize the Opl\Collector\Configuration\FileLoader abstract class which provides the features related to locating the files on the disk and managing the paths. Below you can find, how to begin your import() method in this case:
class MyFileLoader extends FileLoader { public function import() { if(null === $this->currentFile) { throw new BadMethodCallException('Cannot load a file: no file specified'); } $data = load_the_file($this->findFile($this->currentFile)); // process the data here return $dataArray; } // end import(); } // end MyFileLoader;
The findFile() method scans all the defined paths, looking for the first occurence of the specified file. If it finds one, it returns the complete path to the file that should be loaded. If no file is found, it throws an exception. Note that it is a good practice to check if the programmer actually set any file name, just like we have done this in the first three lines.
- Table of Contents
6. Open Power Error Handler - 5.5. Writing custom loaders
« Previous - 7. Appendices
Next »
6. Open Power Error Handler
To be written.
- Table of Contents
7. Appendices - 6. Open Power Error Handler
« Previous - A. Versioning scheme
Next »
7. Appendices
Appendices.
- 7. Appendices
A. Versioning scheme - 7. Appendices
« Previous - B. Coding style
Next »
Appendix A. Versioning scheme
In this appendix, we are going to describe the versioning scheme used by OPL projects.
Version number structure
Open Power Libs projects use the four-digit version number: a.b.c.d. The first number is characteristic for the whole series, and the others are project-specific.
- Edition - defines the OPL series the project belongs to.
- Major number - project major version number. They can break backward compatibility and be written from scratch. Odd numbers represent stable releases, and even - development.
- Minor number - project minor version number. With these versions, new features and improvements come.
- Release number - project release number. New releases bring bugfixes, and no new features.
For the projects covered by this document, the edition number is always 3, as there were also Open Power Libs 1 and Open Power Libs 2 in the past. The usage conventions for the other numbers are similar to those known from other pieces of software. For example, 3.0.3.5 number can be seen as a development version 0.3.5 in Open Power Libs series 3. The major number equal 0 is used for libraries that have not got any stable release yet, and only development snapshots are available.
Examples:
- 3.0.1.0 - the first development release for OPL series 3.
- 3.0.2.7 - a typical development release for OPL series 3.
- 3.1.0.0 - the first stable release for OPL series 3.
- 3.1.1.3 - a bugfix release from 1.1 stable branch for OPL series 3.
- 3.2.1.0 - the first development release of the next major revision for OPL series 3.
- 3.3.0.0 - the first stable release of the next major revision for OPL series 3.
- 4.0.1.0 - this version is for OPL series 4.
Per-project versioning
The only number shared by the OPL projects is the edition number that defines the OPL series the project belongs to. The other numbers are incremented independently for each project. This means that you cannot use them to determine the version compatibility between them. For example, Open Power Foo 3.1.1.x can be fully compatible with Open Power Bar 3.1.3.x despite the fact they have different minor numbers.
The rationale for this decision is relatively straightforward. OPL consists of both big libraries, like Open Power Template, and small ones, like Open Power Visitor with only a couple of classes. In the first case, there is always a number of places, where the library can be improved or extended, and this is not always true in the second case. We do not want to make artifical releases, where the only change is the incremented number.
Why version 3?
Open Power Libs foundation has been developed since 2005, and some of the projects (especially Open Power Template) are very old ideas. There were three fundamental revisions of the idea, where the foundation was rewritten from scratch with the new design goals, and often different versioning schemes. We wanted to avoid version number conflicts with the older editions, but we did not want to be locked to 3 as the major version number, especially for the new projects, where bumping straight to 3.0.0 would be a bit confusing.
We hope that there will be no need to create Open Power Libs 4 at all.
- 7. Appendices
B. Coding style - A. Versioning scheme
« Previous - C. Reporting bugs
Next »
Appendix B. Coding style
This appendix describes the coding style used in the OPL project.
General guidelines
- We do not end the files with the trailing
?> - We use tabs, not spaces to make indentations. Every new indentation level is one tab-long.
- Every file must begin with a header specifying the project name, licensing information and copyrights.
Code organization
- We use the PSR-0 class naming convention.
- We use PHP 5.3 namespaces.
- Every PHP file that contains a class, an interface or a trait must belong to a namespace.
- Every PHP file may contain at most one class, interface or trait.
- We avoid using traditional PHP functions.
- It is disallowed to create static class fields. Static methods are allowed, but their side effects should be local.
- We assume that the classes are loaded with a PSR0-compatible autoloader.
- The namespaces have a direct 1:1 mapping to the file structure.
- The file names use the
.phpextension. - The class and file names are both case-sensitive.
Basic code formatting
We increase the indentation level with the new code block, long array content, multiline function/method arguments. The indentation level is decreased once the block/long array/arguments are finished.
The curly brackets are always opened in a new line:
if($foo) { bar(); }
We do not use the short forms of control structures. The following code is invalid:
if($foo) bar();
We do not use the alternative control structure syntax. The following code is invalid:
if($foo) bar(); endif;
For do .. while the trailing while is always written in a new line:
do { bar(); } while($foo);
elseif, else and catch are always written in a new line:
try { if($foo) { } elseif($bar) { } else { } } catch(Exception $exception) { }
It is allowed to split the longer interface import in the class declaration into many lines. In this case every line specifies a single interface and is indented with a single tab:
class Foo implements Interface1, Interface2, Interface3 { } // end Foo;
We do not use the curly bracket form of the namespace declaration. The following code shows the only valid namespace declaration:
namespace Opl\Foo; class Bar { } // end Bar;
We do not prepend the imported class names with a slash in the
useblock:namespace Opl\Foo; use Opl\Bar\Joe; use Symfony\Component\Something; use PDO;
The ending curly bracket that closes the method body, must contain an additional comment with the words "end methodName();"
public function foo() { doSomething(); } // end foo();
The ending curly bracket that closes the class, interface or trait body, must contain an additional comment with the words "end unitName;"
class Foo { } // end Foo;
Naming standards
- The variable names must follow the camelCase convention:
$myVariable - The class field names must follow the camelCase convention:
$this->myField - The method and function names must follow the camelCase convention:
$this->myMethod() - The class field and method names may be prepended with an underscore, if their intended usage does not match to the possible usage suggested by a visibility modifier. For example, if we have a method that must be accessible to some parts of the internal code, but located in another class, we must use the public modifier, but by prepending the function name with
_we suggest that the programmer should not call it externally. - The underscore can be also prepended, if the interface contains a general method, but does not want to open all the features to the public. In this case, the more general method can be underscored, and we provide a simpler public method with the same name that calls the first method.
- The class, interface, namespace and trait names must not use underscores.
- The class, interface, namespace and trait names must have the first letter capitalized.
- If the class, interface, namespace or trait name consists of more than one words, the first letter of each word is also capitalized:
FooInterface. - The exception classes must end with the
Exceptionword, for example:FooException. - The interface names must end with the
Interfaceword:FooInterface, or use the verb adjective form:Countable - The method, variable and field names should be self-descriptive.
- 7. Appendices
C. Reporting bugs - B. Coding style
« Previous - D. License and authors
Next »
Appendix C. Reporting bugs
Open Power Libs issue trackers are parts of the code repositories at Github. If you find any bug, please report it there.
How to use the bugtracker?
- In order to report a bug, you must have a valid Github account.
- If it is possible, the report should contain a standalone test case that is ready to run. This will speed up reproducing the bug on our computers and creating the patch. If we cannot reproduce the bug, probably it will not be fixed.
- Include the information on OPT and PHP versions, as well as other projects that could interfere with the library.
- Please describe the expected behavior and the actual result.
- We include the full error message, if it is provided.
- The report must be written in English.
- Remember: if we need extra information, we will ask for them.
- 7. Appendices
D. License and authors - C. Reporting bugs
« Previous
Appendix D. License and authors
Open Power Libs 3 was designed and created by the Invenzzia programming group which is also the holder of the copyrights. The people that worked on this:
- Tomasz "Zyx" Jędrzejewski - idea, project, programming, documentation, tests.
- Jacek "eXtreme" Jędrzejewski - tests, support, ideas, documentation.
- Amadeusz "megawebmaster" Starzykiewicz - additional programming
Copyright © Invenzzia Group 2008-2011 - www.invenzzia.org
License
Open Power Libs 3 is available under the terms of new BSD license. It is included in the library archives.
This documentation is available under the terms of GNU Free Documentation License 1.2. Its content is included in the library archives and the source files are available to download on the Invenzzia website.
The license body:
Copyright (c) 2008-2011, Invenzzia Group <http://www.invenzzia.org/>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Invenzzia Group nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY INVENZZIA GROUP AND OTHER CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL INVENZZIA GROUP BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.