blob: 0cb4ac6921c0bb2442e4ff08865a1a104c494a31 [file] [log] [blame]
<?php
/**
* File containing the ezcBase class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Base
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Base class implements the methods needed to use the eZ components.
*
* @package Base
* @version //autogentag//
* @mainclass
*/
class ezcBase
{
/**
* Used for dependency checking, to check for a PHP extension.
*/
const DEP_PHP_EXTENSION = "extension";
/**
* Used for dependency checking, to check for a PHP version.
*/
const DEP_PHP_VERSION = "version";
/**
* Denotes the production mode
*/
const MODE_PRODUCTION = 0;
/**
* Denotes the development mode
*/
const MODE_DEVELOPMENT = 1;
/**
* Indirectly it determines the path where the autoloads are stored.
*
* @var string
*/
private static $libraryMode = "devel";
/**
* Contains the current working directory, which is used when the
* $libraryMode is set to "custom".
*
* @var string
*/
private static $currentWorkingDirectory = null;
/**
* The full path to the autoload directory.
*
* @var string
*/
protected static $packageDir = null;
/**
* Contains which development mode is used. It's "development" by default,
* because of backwards compatibility reasons.
*/
private static $runMode = self::MODE_DEVELOPMENT;
/**
* Stores info with additional paths where autoload files and classes for
* autoloading could be found. Each item of $repositoryDirs looks like
* array( autoloadFileDir, baseDir ). The array key is the prefix belonging
* to classes within that repository - if provided when calling
* addClassRepository(), or an autoincrement integer otherwise.
*
* @var array(string=>array)
*/
protected static $repositoryDirs = array();
/**
* This variable stores all the elements from the autoload arrays. When a
* new autoload file is loaded, their files are added to this array.
*
* @var array(string=>string)
*/
protected static $autoloadArray = array();
/**
* This variable stores all the elements from the autoload arrays for
* external repositories. When a new autoload file is loaded, their files
* are added to this array.
*
* @var array(string=>string)
*/
protected static $externalAutoloadArray = array();
/**
* Options for the ezcBase class.
*
* @var ezcBaseOptions
*/
static private $options;
/**
* Associates an option object with this static class.
*
* @param ezcBaseAutoloadOptions $options
*/
static public function setOptions( ezcBaseAutoloadOptions $options )
{
self::$options = $options;
}
/**
* Tries to autoload the given className. If the className could be found
* this method returns true, otherwise false.
*
* This class caches the requested class names (including the ones who
* failed to load).
*
* @param string $className The name of the class that should be loaded.
*
* @return bool
*/
public static function autoload( $className )
{
ezcBase::setPackageDir();
// Check whether the classname is already in the cached autoloadArray.
if ( array_key_exists( $className, ezcBase::$autoloadArray ) )
{
// Is it registered as 'unloadable'?
if ( ezcBase::$autoloadArray[$className] == false )
{
return false;
}
ezcBase::loadFile( ezcBase::$autoloadArray[$className] );
return true;
}
// Check whether the classname is already in the cached autoloadArray
// for external repositories.
if ( array_key_exists( $className, ezcBase::$externalAutoloadArray ) )
{
// Is it registered as 'unloadable'?
if ( ezcBase::$externalAutoloadArray[$className] == false )
{
return false;
}
ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] );
return true;
}
// Not cached, so load the autoload from the package.
// Matches the first and optionally the second 'word' from the classname.
$fileNames = array();
if ( preg_match( "/^([a-z0-9]*)([A-Z][a-z0-9]*)?([A-Z][a-z0-9]*)?/", $className, $matches ) !== false )
{
$autoloadFile = "";
// Try to match with both names, if available.
switch ( sizeof( $matches ) )
{
case 4:
// check for x_y_autoload.php
$autoloadFile = strtolower( "{$matches[2]}_{$matches[3]}_autoload.php" );
$fileNames[] = $autoloadFile;
if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) )
{
return true;
}
// break intentionally missing.
case 3:
// check for x_autoload.php
$autoloadFile = strtolower( "{$matches[2]}_autoload.php" );
$fileNames[] = $autoloadFile;
if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) )
{
return true;
}
// break intentionally missing.
case 2:
// check for autoload.php
$autoloadFile = 'autoload.php';
$fileNames[] = $autoloadFile;
if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) )
{
return true;
}
break;
}
// Maybe there is another autoload available.
// Register this classname as false.
ezcBase::$autoloadArray[$className] = false;
}
$path = ezcBase::$packageDir . 'autoload/';
$realPath = realpath( $path );
if ( $realPath == '' )
{
// Can not be tested, because if this happens, then the autoload
// environment has not been set-up correctly.
trigger_error( "Couldn't find autoload directory '$path'", E_USER_ERROR );
}
$dirs = self::getRepositoryDirectories();
if ( ezcBase::$options && ezcBase::$options->debug )
{
throw new ezcBaseAutoloadException( $className, $fileNames, $dirs );
}
return false;
}
/**
* Sets the current working directory to $directory.
*
* @param string $directory
*/
public static function setWorkingDirectory( $directory )
{
self::$libraryMode = 'custom';
self::$currentWorkingDirectory = $directory;
}
/**
* Figures out the base path of the Zeta Components installation.
*
* It stores the path that it finds in a static member variable. The path
* depends on the installation method of the Zeta Components. The SVN version
* has a different path than the PEAR installed version.
*/
protected static function setPackageDir()
{
if ( ezcBase::$packageDir !== null )
{
return;
}
// Get the path to the components.
$baseDir = dirname( __FILE__ );
switch ( ezcBase::$libraryMode )
{
case "custom":
ezcBase::$packageDir = self::$currentWorkingDirectory . '/';
break;
case "devel":
case "tarball":
ezcBase::$packageDir = $baseDir. "/../../";
break;
case "pear";
ezcBase::$packageDir = $baseDir. "/../";
break;
}
}
/**
* Tries to load the autoload array and, if loaded correctly, includes the class.
*
* @param string $fileName Name of the autoload file.
* @param string $className Name of the class that should be autoloaded.
* @param string $prefix The prefix of the class repository.
*
* @return bool True is returned when the file is correctly loaded.
* Otherwise false is returned.
*/
protected static function requireFile( $fileName, $className, $prefix )
{
$autoloadDir = ezcBase::$packageDir . "autoload/";
// We need the full path to the fileName. The method file_exists() doesn't
// automatically check the (php.ini) library paths. Therefore:
// file_exists( "ezc/autoload/$fileName" ) doesn't work.
if ( $prefix === 'ezc' && file_exists( "$autoloadDir$fileName" ) )
{
$array = require( "$autoloadDir$fileName" );
if ( is_array( $array) && array_key_exists( $className, $array ) )
{
// Add the array to the cache, and include the requested file.
ezcBase::$autoloadArray = array_merge( ezcBase::$autoloadArray, $array );
if ( ezcBase::$options !== null && ezcBase::$options->preload && !preg_match( '/Exception$/', $className ) )
{
foreach ( $array as $loadClassName => $file )
{
if ( $loadClassName !== 'ezcBase' && !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false ) && !preg_match( '/Exception$/', $loadClassName ) /*&& !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false )*/ )
{
ezcBase::loadFile( ezcBase::$autoloadArray[$loadClassName] );
}
}
}
else
{
ezcBase::loadFile( ezcBase::$autoloadArray[$className] );
}
return true;
}
}
// It is not in components autoload/ dir.
// try to search in additional dirs.
foreach ( ezcBase::$repositoryDirs as $repositoryPrefix => $extraDir )
{
if ( gettype( $repositoryPrefix ) === 'string' && $repositoryPrefix !== $prefix )
{
continue;
}
if ( file_exists( $extraDir['autoloadDirPath'] . '/' . $fileName ) )
{
$array = array();
$originalArray = require( $extraDir['autoloadDirPath'] . '/' . $fileName );
// Building paths.
// Resulting path to class definition file consists of:
// path to extra directory with autoload file +
// basePath provided for current extra directory +
// path to class definition file stored in autoload file.
foreach ( $originalArray as $class => $classPath )
{
$array[$class] = $extraDir['basePath'] . '/' . $classPath;
}
if ( is_array( $array ) && array_key_exists( $className, $array ) )
{
// Add the array to the cache, and include the requested file.
ezcBase::$externalAutoloadArray = array_merge( ezcBase::$externalAutoloadArray, $array );
ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] );
return true;
}
}
}
// Nothing found :-(.
return false;
}
/**
* Loads, require(), the given file name. If we are in development mode,
* "/src/" is inserted into the path.
*
* @param string $file The name of the file that should be loaded.
*/
protected static function loadFile( $file )
{
switch ( ezcBase::$libraryMode )
{
case "devel":
case "tarball":
list( $first, $second ) = explode( '/', $file, 2 );
$file = $first . "/src/" . $second;
break;
case "custom":
list( $first, $second ) = explode( '/', $file, 2 );
// Add the "src/" after the package name.
if ( $first == 'Base' || $first == 'UnitTest' )
{
list( $first, $second ) = explode( '/', $file, 2 );
$file = $first . "/src/" . $second;
}
else
{
list( $first, $second, $third ) = explode( '/', $file, 3 );
$file = $first . '/' . $second . "/src/" . $third;
}
break;
case "pear":
/* do nothing, it's already correct */
break;
}
if ( file_exists( ezcBase::$packageDir . $file ) )
{
require( ezcBase::$packageDir . $file );
}
else
{
// Can not be tested, because if this happens, then one of the
// components has a broken autoload file.
throw new ezcBaseFileNotFoundException( ezcBase::$packageDir.$file );
}
}
/**
* Loads, require(), the given file name from an external package.
*
* @param string $file The name of the file that should be loaded.
*/
protected static function loadExternalFile( $file )
{
if ( file_exists( $file ) )
{
require( $file );
}
else
{
throw new ezcBaseFileNotFoundException( $file );
}
}
/**
* Checks for dependencies on PHP versions or extensions
*
* The function as called by the $component component checks for the $type
* dependency. The dependency $type is compared against the $value. The
* function aborts the script if the dependency is not matched.
*
* @param string $component
* @param int $type
* @param mixed $value
*/
public static function checkDependency( $component, $type, $value )
{
switch ( $type )
{
case self::DEP_PHP_EXTENSION:
if ( extension_loaded( $value ) )
{
return;
}
else
{
// Can not be tested as it would abort the PHP script.
die( "\nThe {$component} component depends on the default PHP extension '{$value}', which is not loaded.\n" );
}
break;
case self::DEP_PHP_VERSION:
$phpVersion = phpversion();
if ( version_compare( $phpVersion, $value, '>=' ) )
{
return;
}
else
{
// Can not be tested as it would abort the PHP script.
die( "\nThe {$component} component depends on the PHP version '{$value}', but the current version is '{$phpVersion}'.\n" );
}
break;
}
}
/**
* Return the list of directories that contain class repositories.
*
* The path to the eZ components directory is always included in the result
* array. Each element in the returned array has the format of:
* packageDirectory => ezcBaseRepositoryDirectory
*
* @return array(string=>ezcBaseRepositoryDirectory)
*/
public static function getRepositoryDirectories()
{
$autoloadDirs = array();
ezcBase::setPackageDir();
$repositoryDir = self::$currentWorkingDirectory ? self::$currentWorkingDirectory : ( realpath( dirname( __FILE__ ) . '/../../' ) );
$autoloadDirs['ezc'] = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_INTERNAL, $repositoryDir, $repositoryDir . "/autoload" );
foreach ( ezcBase::$repositoryDirs as $extraDirKey => $extraDirArray )
{
$repositoryDirectory = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_EXTERNAL, realpath( $extraDirArray['basePath'] ), realpath( $extraDirArray['autoloadDirPath'] ) );
$autoloadDirs[$extraDirKey] = $repositoryDirectory;
}
return $autoloadDirs;
}
/**
* Adds an additional class repository.
*
* Used for adding class repositoryies outside the eZ components to be
* loaded by the autoload system.
*
* This function takes two arguments: $basePath is the base path for the
* whole class repository and $autoloadDirPath the path where autoload
* files for this repository are found. The paths in the autoload files are
* relative to the package directory as specified by the $basePath
* argument. I.e. class definition file will be searched at location
* $basePath + path to the class definition file as stored in the autoload
* file.
*
* addClassRepository() should be called somewhere in code before external classes
* are used.
*
* Example:
* Take the following facts:
* <ul>
* <li>there is a class repository stored in the directory "./repos"</li>
* <li>autoload files for that repository are stored in "./repos/autoloads"</li>
* <li>there are two components in this repository: "Me" and "You"</li>
* <li>the "Me" component has the classes "erMyClass1" and "erMyClass2"</li>
* <li>the "You" component has the classes "erYourClass1" and "erYourClass2"</li>
* </ul>
*
* In this case you would need to create the following files in
* "./repos/autoloads". Please note that the part before _autoload.php in
* the filename is the first part of the <b>classname</b>, not considering
* the all lower-case letter prefix.
*
* "my_autoload.php":
* <code>
* <?php
* return array (
* 'erMyClass1' => 'Me/myclass1.php',
* 'erMyClass2' => 'Me/myclass2.php',
* );
* ?>
* </code>
*
* "your_autoload.php":
* <code>
* <?php
* return array (
* 'erYourClass1' => 'You/yourclass1.php',
* 'erYourClass2' => 'You/yourclass2.php',
* );
* ?>
* </code>
*
* The directory structure for the external repository is then:
* <code>
* ./repos/autoloads/my_autoload.php
* ./repos/autoloads/you_autoload.php
* ./repos/Me/myclass1.php
* ./repos/Me/myclass2.php
* ./repos/You/yourclass1.php
* ./repos/You/yourclass2.php
* </code>
*
* To use this repository with the autoload mechanism you have to use the
* following code:
* <code>
* <?php
* ezcBase::addClassRepository( './repos', './repos/autoloads' );
* $myVar = new erMyClass2();
* ?>
* </code>
*
* @throws ezcBaseFileNotFoundException if $autoloadDirPath or $basePath do not exist.
* @param string $basePath
* @param string $autoloadDirPath
* @param string $prefix
*/
public static function addClassRepository( $basePath, $autoloadDirPath = null, $prefix = null )
{
// check if base path exists
if ( !is_dir( $basePath ) )
{
throw new ezcBaseFileNotFoundException( $basePath, 'base directory' );
}
// calculate autoload path if it wasn't given
if ( is_null( $autoloadDirPath ) )
{
$autoloadDirPath = $basePath . '/autoload';
}
// check if autoload dir exists
if ( !is_dir( $autoloadDirPath ) )
{
throw new ezcBaseFileNotFoundException( $autoloadDirPath, 'autoload directory' );
}
// add info to $repositoryDirs
if ( $prefix === null )
{
$array = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath );
// add info to the list of extra dirs
ezcBase::$repositoryDirs[] = $array;
}
else
{
if ( array_key_exists( $prefix, ezcBase::$repositoryDirs ) )
{
throw new ezcBaseDoubleClassRepositoryPrefixException( $prefix, $basePath, $autoloadDirPath );
}
// add info to the list of extra dirs, and use the prefix to identify the new repository.
ezcBase::$repositoryDirs[$prefix] = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath );
}
}
/**
* Returns the base path of the Zeta Components installation
*
* This method returns the base path, including a trailing directory
* separator.
*
* @return string
*/
public static function getInstallationPath()
{
self::setPackageDir();
$path = realpath( self::$packageDir );
if ( substr( $path, -1 ) !== DIRECTORY_SEPARATOR )
{
$path .= DIRECTORY_SEPARATOR;
}
return $path;
}
/**
* Sets the development mode to the one specified.
*
* @param int $runMode
*/
public static function setRunMode( $runMode )
{
if ( !in_array( $runMode, array( ezcBase::MODE_PRODUCTION, ezcBase::MODE_DEVELOPMENT ) ) )
{
throw new ezcBaseValueException( 'runMode', $runMode, 'ezcBase::MODE_PRODUCTION or ezcBase::MODE_DEVELOPMENT' );
}
self::$runMode = $runMode;
}
/**
* Returns the current development mode.
*
* @return int
*/
public static function getRunMode()
{
return self::$runMode;
}
/**
* Returns true when we are in development mode.
*
* @return bool
*/
public static function inDevMode()
{
return self::$runMode == ezcBase::MODE_DEVELOPMENT;
}
/**
* Returns the installation method
*
* Possible return values are 'custom', 'devel', 'tarball' and 'pear'. Only
* 'tarball' and 'pear' are returned for user-installed versions.
*
* @return string
*/
public static function getInstallMethod()
{
return self::$libraryMode;
}
}
?>