From Fedora Project Wiki

< SIGs‎ | PHP

Line 126: Line 126:
cat <<'AUTOLOAD' | tee src/autoload.php
cat <<'AUTOLOAD' | tee src/autoload.php
<?php
<?php
 
require_once '/usr/share/php/Zend//Loader/AutoloaderFactory.php';
require_once ('%{_datadir}/php/Zend/Loader/StandardAutoloader.php');
Zend\Loader\AutoloaderFactory::factory(array(
 
    'Zend\Loader\StandardAutoloader' => array(
$option = array(
        'fallback_autoloader' => true, // for other dep, if needed
  Zend\Loader\StandardAutoloader::LOAD_NS => array('Foo\\Bar' => __DIR__),
        'autoregister_zf' => true,     // for ZF, if needed
  Zend\Loader\StandardAutoloader::AUTOREGISTER_ZF => true,
        'namespaces' => array(
);
          'Foo\\Bar' => __DIR__
 
))));
$loader = new Zend\Loader\StandardAutoloader($option);
$loader->register();
AUTOLOAD
AUTOLOAD
</pre>
</pre>
Using AutoloaderFactory ensure a single instance of StandardAutoloader is used.


=== More autoloader tips ===
=== More autoloader tips ===

Revision as of 06:24, 10 August 2015

PHP Packaging Tips

Dependencies

PEAR/PECL

package.xml provided in pear/pecl channel archive describe the dependencies of the package, e.g.

 <dependencies>
  <required>
   <php>
    <min>5.3.0</min>
   </php>
   <package>
    <name>Foo</name>
    <channel>pear.php.net</channel>
    <min>2.0.0</min>
   </package>
  </required>
  <optional>
   <extension>
    <name>curl</name>
   </extension>
  </optional>
 </dependencies>

In your spec

# From package.xml - required
Requires: php(language) >= 5.3.0
Requires: php-pear(Foo) >= 2.0.0
# From package.xml - optional
Requires: php-curl

composer

composer.json provided in lot of libraries/applications describes the dependencies.

See The composer.json schema

  • "require" becomes "Requires", using the virtual php-composer(foo)
  • "require-dev" usually becomes "BuildRequires"
  • "suggest" are optional dependencies, which can becomes (packager choice) "Requires"
  • "conflict" becomes "Conflicts"
  • "replace" becomes "Provides" (and "Obsoletes" if needed)

Autoloader

Explanation

Composer: most library and lot of applications are now "composer" aware. Which means composer is used to install dependencies and create a suitable autoloader. Composer is mostly a "Bundled every library in every project", not something we want in Fedora.

Consumer Autoloader: in some case, we can add an autoloader for an application and all its dependencies. It seems this is not the best solution, if the dependency tree change, the autoloader need to be fixed.

Provider Autoloader: if each library provides an autoloader for its classes and its dependencies, a consumer just have to include this autoloader. This seems the best solution, so recommended here.

Providing an autoloader is not mandatory (so not in the Guidelines), but seems a good practice.

Implementation

There are various way to create an autoloader

upstream: when upstream provides an autoloader, no need to use another one ;)

classmap: when "composer.json" describe a library to autoload a classmap, e.g.

"autoload": {
      "classmap": ["src/"]
}

the simple solution is to generate an simple classmap autoloader, using the phpab command, e.g.

BuildRequires: %{_bindir}/phpab

%build
%{_bindir}/phpab --output src/autoload.php src

Symfony Framework: recommended when your package already pull some symfony components and is PSR-0/PSR-4 compliant, e.g.

"require": {
   "php": ">=5.3.2",
   "symfony/console": "~2.5",
   "foo/baz": "~1.0"
},
"autoload": {
    "psr-0": { "Foo\\Bar\\": "src/" }
},

In your spec (autoload.php can also be a separate source file):

BuildRequires: php-composer(symfony/class-loader)
Requires:      php-composer(symfony/class-loader)

cat <<'AUTOLOAD' | tee src/autoload.php
<?php

if (!isset($fedoraClassLoader) || !($fedoraClassLoader instanceof \Symfony\Component\ClassLoader\ClassLoader)) {
    if (!class_exists('Symfony\\Component\\ClassLoader\\ClassLoader', false)) {
        require_once '%{_datadir}/php/Symfony/Component/ClassLoader/ClassLoader.php';
    }

    $fedoraClassLoader = new \Symfony\Component\ClassLoader\ClassLoader();
    $fedoraClassLoader->register();
}

// This library
$fedoraClassLoader->addPrefix('Foo\\Bar\\', dirname(dirname(__DIR__)));

// Another library (dependency)
require_once '%{_datadir}/php/Foo/Baz/autoload.php';
AUTOLOAD

Notice: in this implementation proposal, $fedoraClassLoader can be shared between libraries when various autoloader are stacked.

Zend Framework: recommended when your package already pull some ZF components.

In your spec (autoload.php can also be a separate source file):

BuildRequires: php-composer(zendframework/zend-loader) 
Requires:      php-composer(zendframework/zend-loader) 

cat <<'AUTOLOAD' | tee src/autoload.php
<?php
require_once '/usr/share/php/Zend//Loader/AutoloaderFactory.php';
Zend\Loader\AutoloaderFactory::factory(array(
    'Zend\Loader\StandardAutoloader' => array(
        'fallback_autoloader' => true, // for other dep, if needed
        'autoregister_zf' => true,     // for ZF, if needed
        'namespaces' => array(
           'Foo\\Bar' => __DIR__
))));
AUTOLOAD

Using AutoloaderFactory ensure a single instance of StandardAutoloader is used.

More autoloader tips

Test your autoloader: usually by using it to run the test suite, and better to run the installed copy, e.g.

%check
phpunit --bootstrap=%{buildroot}/%{_datadir}/php/Foo/Bar/autoload.php

Use full path to dependencies autoloader, to ensure you use the expected one (don't rely on include_path to avoid /usr/share/pear which can provide an old version, or SCL which doesn't includes /usr/share/php, or any user altered env.)

Use relative path for the library itself, so the autoloader can be included from /usr/share/php or %{buildroot}