Skip to content

Modules - Classpath meta information & initializers #220

@thekid

Description

@thekid

Scope of Change

Every class path element will be able to provide a way to provide meta information. When it does so, we will call this class path element a module. Modules are named, may have a version, are accessible by reflection, annotatable and may contain static initializer blocks.

Rationale

Prerequisite for XP Framework Modularization - see RFC #204.

Functionality

If a classpath element (a path or a XAR file) contains a file named module.xp, then it is loaded and initialized. For each loaded module, a lang.Module instance is created to access the module's meta data and wrap around the class loader for the specific classpath element.

The "core" module

The XP Framework's core will be represented by a module named core.

Reflection

Here's the lang.Module class.

public class lang.Module extends lang.Object implements lang.IClassLoader {
  protected var lang.Module::$loader
  protected var lang.Module::$definition
  protected var lang.Module::$name
  protected var lang.Module::$version

  public lang.Module __construct(lang.IClassLoader $loader, lang.XPClass $definition, [string $name= null], [string $version= null])

  public static lang.reflect.Module forName(string $name) throws lang.ElementNotFoundException
  public static lang.reflect.Module[] getModules()

  public bool providesClass(string $class)
  public bool providesResource(string $filename)
  public bool providesPackage(string $package)
  public string[] packageContents(string $package)
  public lang.XPClass loadClass(string $class) throws lang.ClassNotFoundException
  public string loadClass0(string $class) throws lang.ClassNotFoundException
  public string getResource(string $string) throws lang.ElementNotFoundException
  public io.Stream getResourceAsStream(string $string) throws lang.ElementNotFoundException
  public string getName()
  public string getVersion()
  public string getComment()
  public bool hasAnnotation(string $name, [string $key= null])
  public var getAnnotation(string $name, [string $key= null]) throws lang.ElementNotFoundException
  public bool hasAnnotations()
  public var[] getAnnotations()
  public lang.IClassLoader getClassLoader()
  public bool equals(var $cmp)
  public string toString([string $cwd= null])
  public string hashCode()
  public string getClassName()
  public lang.XPClass getClass()
}

Example module.xp

<?php
/* This file is part of the XP framework
 *
 * $Id$
 */

  uses(...);

  /**
   * The example module
   *
   * @see http://example.com
   */
  module example(1.0.0) {
    // Class body here
  }
?>

Restrictions

  • Module names must match the following regular expression: /[a-z][a-z0-9_\/\.-]*/ (all-lowercase, start with an alpha char, and continue with any alphanumeric, underscore, forward slash, dot, or dash). It is recommended to use the pattern [vendor] "/" [name] in order to easily integrate with Composer
  • Module versions may contain anything except these special chars: (, ), *, +, -, <, >, [, ], , and the combination .., although it is highly recommended to stick to what PHP's version_compare() understands.

Usecase: Database drivers

Database drivers can be modularized as follows when this RFC is implemented:

/path/to/sybase_ct/src/main/php/module.xp:

<?php
  uses('rdbms.DriverManager');

  module sybase_ct(1.0.3) {
    public static function initialize(IClassLoader $l) {
      DriverManager::register('sybase', $l->loadClass('rdbms.sybase.SybaseConnection'));
    }
  }
?>

/path/to/sybase_ct/src/main/php/rdbms/sybase/:

* SybaseConnection.class.php
* SybaseResultSet.class.php
* SybaseDialect.class.php

In the application's class path, we can include /path/to/sybase_ct/src/main/php/ and will automatically have the sybase driver registered.

Usecase: IoC

IoC containers need a place where the wiring takes place. This can now be done in the static initializer of a module:

/path/to/app/src/main/php/module.xp:

<?php
  uses('ioc.Binder');

  module company/db-app {
    static function __static() {
      with ($binder= new Binder()); {
        $binder->bind('rdbms.DBConnection')->named('lsd')->to(DriverManager::getConnection('...'));
      }
    }
  }
?>

/path/to/app/src/main/php/com/example/scriptlet/state:

* HomeState.class.php
* ...

Later on, we can use:

<?php
  class HomeState extends AbstractState {

    #[@inject, @named('lsd')]
    public function setConnection(DBConnection $conn) { ... }

    public function process($request, $response, $context) { ... }
  }
?>

...and have the database connection injected.

Security considerations

n/a

Speed impact

Slightly slower because loading a class path will add an addition stat() call.

Class loading optimization

Not all modules need to be searched for all classes, resources or packages. Modules can optionally declare packages they provide, making the class loading mechanism skip them during class lookup based on string comparisons rather than filesystem lookups.

<?php
  module imaging.test(3.4.1) provides imaging.tests.unittest, imaging.tests.integration {

  }
?>

Dependencies

None.

Related documents

RFC #204
http://effbot.org/pyfaq/what-is-init-py-used-for.htm

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions