Просмотр исходного кода

Zend_Loader_Autoloader multi-version support

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@17982 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew 16 лет назад
Родитель
Сommit
af0971c63d

+ 165 - 0
documentation/manual/en/module_specs/Zend_Loader-Autoloader.xml

@@ -128,6 +128,171 @@ $autoloader->setFallbackAutoloader(true);
 ]]></programlisting>
     </sect2>
 
+    <sect2 id="zend.loader.autoloader.zf-version">
+        <title>Selecting a Zend Framework version</title>
+
+        <para>
+            Typically, you will use the version of Zend Framework that the autoloader you
+            instantiate came with. However, when developing a project, it's often useful to track
+            specific versions, major or minor branches, or just the latest version.
+            <classname>Zend_Loader_Autoloader</classname>, as of version 1.10, offers some features
+            to help manage this task.
+        </para>
+
+        <para>
+            Imagine the following scenario:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    During <emphasis>development</emphasis>, you want to track the latest version of
+                    Zend Framework you have installed, so that you can ensure the application works
+                    when you upgrade between versions.
+                </para>
+
+                <para>
+                    When pushing to <emphasis>Quality Assurance</emphasis>, however, you need to
+                    have slightly more stability, so you want to use the latest installed revision
+                    of a specific minor version.
+                </para>
+
+                <para>
+                    Finally, when you push to <emphasis>production</emphasis>, you want to pin to a
+                    specific installed version, to ensure no breakage occurs if or when you add new
+                    versions of Zend Framework to you server.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            The autoloader allows you to do this with the method
+            <methodname>setZfPath()</methodname>. This method takes two arguments, a
+            <emphasis>path</emphasis> to a set of Zend Framework installations, and a
+            <emphasis>version</emphasis> to use. Once invoked, it prepends a path to the
+            <constant>include_path</constant> pointing to the appropriate Zend Framework
+            installation library.
+        </para>
+
+        <para>
+            The directory you specify as your <emphasis>path</emphasis> should have a tree such as
+            the following:
+        </para>
+
+        <programlisting language="text"><![CDATA[
+ZendFramework/
+|-- 1.9.2/
+|   |-- library/
+|-- ZendFramework-1.9.1-minimal/
+|   |-- library/
+|-- 1.8.4PL1/
+|   |-- library/
+|-- 1.8.4/
+|   |-- library/
+|-- ZendFramework-1.8.3/
+|   |-- library/
+|-- 1.7.8/
+|   |-- library/
+|-- 1.7.7/
+|   |-- library/
+|-- 1.7.6/
+|   |-- library/
+]]></programlisting>
+
+        <para>
+            (where <emphasis>path</emphasis> points to the directory "ZendFramework" in the above
+            example)
+        </para>
+
+        <para>
+            Note that each subdirectory should contain the directory <filename>library</filename>,
+            which contains the actual Zend Framework library code. The individual subdirectory names
+            may be version numbers, or simply be the untarred contents of a standard Zend Framework
+            distribution tarball/zipfile.
+        </para>
+
+        <para>
+            Now, let's address the use cases. In the first use case, in
+            <emphasis>development</emphasis>, we want to track the latest source install. We can do
+            that by passing "latest" as the version:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$autoloader->setZfPath($path, 'latest');
+]]></programlisting>
+
+        <para>
+            In the example from above, this will map to the directory
+            <filename>ZendFramework/1.9.2/library/</filename>; you can verify this by checking the
+            return value of <methodname>getZfPath()</methodname>.
+        </para>
+
+        <para>
+            In the second situation, for <emphasis>quality assurance</emphasis>, let's say we want
+            to pin to the 1.8 minor release, using the latest install you have for that release. You
+            can do so as follows:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$autoloader->setZfPath($path, '1.8');
+]]></programlisting>
+
+        <para>
+            In this case, it will find the directory
+            <filename>ZendFramework/1.8.4PL1/library/</filename>.
+        </para>
+
+        <para>
+            In the final case, for <emphasis>production</emphasis>, we'll pin to a specific version
+            -- 1.7.7, since that was what was available when Quality Assurance tested prior to our
+            release.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$autoloader->setZfPath($path, '1.7.7');
+]]></programlisting>
+
+        <para>
+            Predictably, it finds the directory <filename>ZendFramework/1.7.7/library/</filename>.
+        </para>
+
+        <para>
+            You can also specify these values in the configuration file you use with
+            <filename>Zend_Application</filename>. To do so, you'd specify the following
+            information:
+        </para>
+
+        <programlisting language="ini"><![CDATA[
+[production]
+autoloaderZfPath = "path/to/ZendFramework"
+autoloaderZfVersion = "1.7.7"
+
+[qa]
+autoloaderZfVersion = "1.8"
+
+[development]
+autoloaderZfVersion = "latest"
+]]></programlisting>
+
+        <para>
+            Note the different environment sections, and the different version specified in each
+            environment; these factors will allow <classname>Zend_Application</classname> to
+            configure the autoloader appropriately.
+        </para>
+
+        <warning>
+            <title>Performance implications</title>
+
+            <para>
+                For best performance, either do not use this feature, or specify a specific Zend
+                Framework version (i.e., not "latest", a major revision such as "1", or a minor
+                revision such as "1.8").  Otherwise, the autoloader will need to scan the provided
+                path for directories matching the criteria -- a somewhat expensive operation to
+                perform on each request.
+            </para>
+        </warning>
+    </sect2>
+
     <sect2 id="zend.loader.autoloader.interface">
         <title>The Autoloader Interface</title>
 

+ 11 - 0
library/Zend/Application.php

@@ -145,6 +145,17 @@ class Zend_Application
             $this->setAutoloaderNamespaces($options['autoloadernamespaces']);
         }
 
+        if (!empty($options['autoloaderzfpath'])) {
+            $autoloader = $this->getAutoloader();
+            if (method_exists($autoloader, 'setZfPath')) {
+                $zfPath    = $options['autoloaderzfpath'];
+                $zfVersion = !empty($options['autoloaderzfversion']) 
+                           ? $options['autoloaderzfversion'] 
+                           : 'latest';
+                $autoloader->setZfPath($zfPath, $zfVersion);
+            }
+        }
+
         if (!empty($options['bootstrap'])) {
             $bootstrap = $options['bootstrap'];
 

+ 120 - 0
library/Zend/Loader/Autoloader.php

@@ -78,6 +78,11 @@ class Zend_Loader_Autoloader
     protected $_suppressNotFoundWarnings = false;
 
     /**
+     * @var null|string
+     */
+    protected $_zfPath;
+
+    /**
      * Retrieve singleton instance
      *
      * @return Zend_Loader_Autoloader
@@ -248,6 +253,32 @@ class Zend_Loader_Autoloader
         return array_keys($this->_namespaces);
     }
 
+    public function setZfPath($spec, $version = 'latest')
+    {
+        $path = $spec;
+        if (is_array($spec)) {
+            if (!isset($spec['path'])) {
+                throw new Zend_Loader_Exception('No path specified for ZF');
+            }
+            $path = $spec['path'];
+            if (isset($spec['version'])) {
+                $version = $spec['version'];
+            }
+        }
+
+        $this->_zfPath = $this->_getVersionPath($path, $version);
+        set_include_path(implode(PATH_SEPARATOR, array(
+            $this->_zfPath,
+            get_include_path(),
+        )));
+        return $this;
+    }
+
+    public function getZfPath()
+    {
+        return $this->_zfPath;
+    }
+
     /**
      * Get or set the value of the "suppress not found warnings" flag
      *
@@ -461,4 +492,93 @@ class Zend_Loader_Autoloader
         $this->_namespaceAutoloaders[$namespace] = $autoloaders;
         return $this;
     }
+
+    /**
+     * Retrieve the filesystem path for the requested ZF version
+     * 
+     * @param  string $path 
+     * @param  string $version 
+     * @return void
+     */
+    protected function _getVersionPath($path, $version)
+    {
+        $type = $this->_getVersionType($version);
+        
+        if ($type == 'latest') {
+            $version = 'latest';
+        }
+
+        $availableVersions = $this->_getAvailableVersions($path, $version);
+        if (empty($availableVersions)) {
+            throw new Zend_Loader_Exception('No valid ZF installations discovered');
+        }
+
+        $matchedVersion = array_pop($availableVersions);
+        return $matchedVersion;
+    }
+
+    /**
+     * Retrieve the ZF version type
+     * 
+     * @param  string $version 
+     * @return string "latest", "major", "minor", or "specific"
+     * @throws Zend_Loader_Exception if version string contains too many dots
+     */
+    protected function _getVersionType($version)
+    {
+        if (strtolower($version) == 'latest') {
+            return 'latest';
+        }
+
+        $parts = explode('.', $version);
+        $count = count($parts);
+        if (1 == $count) {
+            return 'major';
+        }
+        if (2 == $count) {
+            return 'minor';
+        }
+        if (3 < $count) {
+            throw new Zend_Loader_Exception('Invalid version string provided');
+        }
+        return 'specific';
+    }
+
+    /**
+     * Get available versions for the version type requested
+     * 
+     * @param  string $path 
+     * @param  string $version 
+     * @return array
+     */
+    protected function _getAvailableVersions($path, $version)
+    {
+        if (!is_dir($path)) {
+            throw new Zend_Loader_Exception('Invalid ZF path provided');
+        }
+
+        $path       = rtrim($path, '/');
+        $path       = rtrim($path, '\\');
+        $versionLen = strlen($version);
+        $versions   = array();
+        $dirs       = glob("$path/*", GLOB_ONLYDIR);
+        foreach ($dirs as $dir) {
+            $dirName = substr($dir, strlen($path) + 1);
+            if (!preg_match('/^(?:ZendFramework-)?(\d+\.\d+\.\d+((a|b|pl|pr|p|rc)\d+)?)(?:-minimal)?$/i', $dirName, $matches)) {
+                continue;
+            }
+
+            $matchedVersion = $matches[1];
+
+            if (('latest' == $version)
+                || ((strlen($matchedVersion) >= $versionLen)
+                    && (0 === strpos($matchedVersion, $version)))
+            ) {
+                $versions[$matchedVersion] = $dir . '/library';
+            }
+        }
+
+        uksort($versions, 'version_compare');
+        return $versions;
+    }
 }

+ 35 - 0
tests/TestConfiguration.php.dist

@@ -316,6 +316,41 @@ define('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_USER', '');
 define('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_PASS', '');
 
 /**
+ * Zend_Loader_Autoloader multi-version support tests
+ *
+ * ENABLED:      whether or not to run the multi-version tests
+ * PATH:         path to a directory containing multiple ZF version installs
+ * LATEST:       most recent ZF version in the PATH
+ *               e.g., "1.9.2"
+ * LATEST_MAJOR: most recent ZF major version in the PATH to test against
+ *               e.g., "1.9.2"
+ * LATEST_MINOR: most recent ZF minor version in the PATH to test against
+ *               e.g., "1.8.4PL1"
+ * SPECIFIC:     specific ZF version in the PATH to test against
+ *               e.g., "1.7.6"
+ * As an example, consider the following tree:
+ *     ZendFramework/
+ *     |-- 1.9.2
+ *     |-- ZendFramework-1.9.1-minimal
+ *     |-- 1.8.4PL1
+ *     |-- 1.8.4
+ *     |-- ZendFramework-1.8.3
+ *     |-- 1.7.8
+ *     |-- 1.7.7
+ *     |-- 1.7.6
+ * You would then set the value of "LATEST" and "LATEST_MAJOR" to "1.9.2", and
+ * could choose between "1.9.2", "1.8.4PL1", and "1.7.8" for "LATEST_MINOR",
+ * and any version number for "SPECIFIC". "PATH" would point to the parent
+ * "ZendFramework" directory.
+ */
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_ENABLED', false);
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_PATH', false);
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST', false);
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST_MAJOR', false);
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST_MINOR', false);
+define('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_SPECIFIC', false);
+
+/**
  * Zend_Ldap online tests
  */
 define('TESTS_ZEND_LDAP_ONLINE_ENABLED', false);

+ 20 - 0
tests/Zend/Application/ApplicationTest.php

@@ -394,6 +394,26 @@ class Zend_Application_ApplicationTest extends PHPUnit_Framework_TestCase
         $options = $application->getOptions();
         $this->assertEquals(array('config', 'includePaths'), array_keys($options));
     }
+
+    public function testPassingZfVersionAutoloaderInformationConfiguresAutoloader()
+    {
+        if (!constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_ENABLED')) {
+            $this->markTestSkipped();
+        }
+        if (!constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST')) {
+            $this->markTestSkipped();
+        }
+        $path   = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_PATH');
+        $latest = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST');
+
+        $application = new Zend_Application('production', array(
+            'autoloaderZfPath'    => $path,
+            'autoloaderZfVersion' => 'latest',
+        ));
+        $autoloader = $application->getAutoloader();
+        $actual     = $autoloader->getZfPath();
+        $this->assertContains($latest, $actual);
+    }
 }
 
 if (PHPUnit_MAIN_METHOD == 'Zend_Application_ApplicationTest::main') {

+ 2 - 0
tests/Zend/Loader/AllTests.php

@@ -30,6 +30,7 @@ if (!defined('PHPUnit_MAIN_METHOD')) {
 }
 
 require_once 'Zend/Loader/AutoloaderTest.php';
+require_once 'Zend/Loader/AutoloaderMultiVersionTest.php';
 require_once 'Zend/Loader/Autoloader/ResourceTest.php';
 require_once 'Zend/Loader/PluginLoaderTest.php';
 
@@ -53,6 +54,7 @@ class Zend_Loader_AllTests
         $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Loader');
 
         $suite->addTestSuite('Zend_Loader_AutoloaderTest');
+        $suite->addTestSuite('Zend_Loader_AutoloaderMultiVersionTest');
         $suite->addTestSuite('Zend_Loader_Autoloader_ResourceTest');
         $suite->addTestSuite('Zend_Loader_PluginLoaderTest');
 

+ 229 - 0
tests/Zend/Loader/AutoloaderMultiVersionTest.php

@@ -0,0 +1,229 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Loader
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Loader_AutoloaderMultiVersionTest::main');
+}
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../TestHelper.php';
+
+/**
+ * @see Zend_Loader_Autoloader
+ */
+require_once 'Zend/Loader/Autoloader.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Loader
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Loader
+ */
+class Zend_Loader_AutoloaderMultiVersionTest extends PHPUnit_Framework_TestCase
+{
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function setUp()
+    {
+        if (!constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_ENABLED')) {
+            $this->markTestSkipped();
+        }
+
+        // Store original autoloaders
+        $this->loaders = spl_autoload_functions();
+        if (!is_array($this->loaders)) {
+            // spl_autoload_functions does not return empty array when no
+            // autoloaders registered...
+            $this->loaders = array();
+        }
+
+        // Store original include_path
+        $this->includePath = get_include_path();
+
+        Zend_Loader_Autoloader::resetInstance();
+        $this->path        = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_PATH');
+        $this->latest      = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST');
+        $this->latestMajor = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST_MAJOR');
+        $this->latestMinor = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_LATEST_MINOR');
+        $this->specific    = constant('TESTS_ZEND_LOADER_AUTOLOADER_MULTIVERSION_SPECIFIC');
+        $this->autoloader  = Zend_Loader_Autoloader::getInstance();
+    }
+
+    public function tearDown()
+    {
+        // Restore original autoloaders
+        $loaders = spl_autoload_functions();
+        foreach ($loaders as $loader) {
+            spl_autoload_unregister($loader);
+        }
+
+        foreach ($this->loaders as $loader) {
+            spl_autoload_register($loader);
+        }
+
+        // Retore original include_path
+        set_include_path($this->includePath);
+
+        // Reset autoloader instance so it doesn't affect other tests
+        Zend_Loader_Autoloader::resetInstance();
+    }
+
+    public function testZfPathIsNullByDefault()
+    {
+        $this->assertNull($this->autoloader->getZfPath());
+    }
+
+    /**
+     * @expectedException Zend_Loader_Exception
+     */
+    public function testSettingZfPathFailsOnInvalidVersionString()
+    {
+        $this->autoloader->setZfPath($this->path, 'foo.bar.baz.bat');
+    }
+
+    /**
+     * @expectedException Zend_Loader_Exception
+     */
+    public function testSettingZfPathFailsWhenBasePathDoesNotExist()
+    {
+        $this->autoloader->setZfPath('foo.bar.baz.bat', 'latest');
+    }
+
+    /**
+     * @expectedException Zend_Loader_Exception
+     */
+    public function testSettingZfVersionFailsWhenNoValidInstallsDiscovered()
+    {
+        $this->autoloader->setZfPath(dirname(__FILE__), 'latest');
+    }
+
+    public function testAutoloadLatestUsesLatestVersion()
+    {
+        $this->autoloader->setZfPath($this->path, 'latest');
+        $actual = $this->autoloader->getZfPath();
+        $this->assertContains($this->latest, $actual);
+    }
+
+    public function testAutoloadLatestIncludesLibraryInPath()
+    {
+        $this->autoloader->setZfPath($this->path, 'latest');
+        $actual = $this->autoloader->getZfPath();
+        $this->assertRegexp('#' . preg_quote($this->latest) . '[^/\\\]*/library#', $actual);
+    }
+
+    public function testAutoloadLatestAddsPathToIncludePath()
+    {
+        $this->autoloader->setZfPath($this->path, 'latest');
+        $incPath = get_include_path();
+        $this->assertRegexp('#' . preg_quote($this->latest) . '[^/\\\]*/library#', $incPath);
+    }
+
+    public function testAutoloadMajorRevisionShouldUseLatestFromMajorRevision()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMajor, 'major'));
+        $actual = $this->autoloader->getZfPath();
+        $this->assertContains($this->latestMajor, $actual);
+    }
+
+    public function testAutoloadMajorRevisionIncludesLibraryInPath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMajor, 'major'));
+        $actual = $this->autoloader->getZfPath();
+        $this->assertRegexp('#' . preg_quote($this->latestMajor) . '[^/\\\]*/library#', $actual);
+    }
+
+    public function testAutoloadMajorRevisionAddsPathToIncludePath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMajor, 'major'));
+        $incPath = get_include_path();
+        $this->assertRegexp('#' . preg_quote($this->latestMajor) . '[^/\\\]*/library#', $incPath);
+    }
+
+    public function testAutoloadMinorRevisionShouldUseLatestFromMinorRevision()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMinor, 'minor'));
+        $actual = $this->autoloader->getZfPath();
+        $this->assertContains($this->latestMinor, $actual);
+    }
+
+    public function testAutoloadMinorRevisionIncludesLibraryInPath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMinor, 'minor'));
+        $actual = $this->autoloader->getZfPath();
+        $this->assertRegexp('#' . preg_quote($this->latestMinor) . '[^/\\\]*/library#', $actual);
+    }
+
+    public function testAutoloadMinorRevisionAddsPathToIncludePath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->_getVersion($this->latestMinor, 'minor'));
+        $incPath = get_include_path();
+        $this->assertRegexp('#' . preg_quote($this->latestMinor) . '[^/\\\]*/library#', $incPath);
+    }
+
+    public function testAutoloadSpecificRevisionShouldUseThatVersion()
+    {
+        $this->autoloader->setZfPath($this->path, $this->specific);
+        $actual = $this->autoloader->getZfPath();
+        $this->assertContains($this->specific, $actual);
+    }
+
+    public function testAutoloadSpecificRevisionIncludesLibraryInPath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->specific);
+        $actual = $this->autoloader->getZfPath();
+        $this->assertRegexp('#' . preg_quote($this->specific) . '[^/\\\]*/library#', $actual);
+    }
+
+    public function testAutoloadSpecificRevisionAddsPathToIncludePath()
+    {
+        $this->autoloader->setZfPath($this->path, $this->specific);
+        $incPath = get_include_path();
+        $this->assertRegexp('#' . preg_quote($this->specific) . '[^/\\\]*/library#', $incPath);
+    }
+
+    protected function _getVersion($version, $type)
+    {
+        $parts = explode('.', $version);
+        switch ($type) {
+            case 'major': 
+                $value = array_shift($parts);
+                break;
+            case 'minor':
+                $value  = array_shift($parts);
+                $value .= '.' . array_shift($parts);
+                break;
+        }
+        return $value;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Loader_AutoloaderMultiVersionTest::main') {
+    Zend_Loader_AutoloaderMultiVersionTest::main();
+}