I will post a first technique that i consider good & useful
It's a smart Autoload class that scans the framework folder and other folders you include by calling the ::AddWatchFolder static method
(due to it's nature the methods are static )
Advantages:
1. Forget about include & require
2. Freedom to move objects and reorganize them
3. Very fast, the location of the files is cached in a PHP file that APC will cache in memory
4. Zero maintenance required ! you will only need the ::AddWatchFolder method to add your new project's folder in the swarm
Techniques:
1. The object will "know" from the start where the files are as it generates a PHP cache file
2. The object will refresh the list only if it does not find a class (either moved or was not existent before)
3. If a class is not found even after the refresh an error will be triggered
Code: Select all
<?php
/**
* @package qBase
* @subpackage base
*/
/**
* Automate the load of classes
* Lowercase Files WILL BE SKIPPED !
* The Autoload pattern is : ClassName.php
*/
function __autoload($class_name)
{
// automate load of classes
QAutoload::AutoloadClass($class_name);
}
/**
* Helper class for the autoload of classes
* @package qBase
* @subpackage base
*/
final class QAutoload extends QObject
{
/**
* The folders to consider for Autoload
*
* @var array
*/
private static $WatchFolders = array();
/**
* The list of classes in the framework (class_name => path)
*
* @var array
*/
private static $Classes = array();
/**
* The list of classes in the framework with link to their watch folder (class_name => WatchFolder)
*
* @var array
*/
private static $ClassesWatchFolders = array();
/**
* Automate the load of classes
* Will always skip the "temp" and "res" folders in the root of the watch folder
*
* @param string $class_name the name of the class to be loaded
*/
public static function AutoloadClass($class_name)
{
if (isset(self::$Classes[$class_name]))
{
if (file_exists(self::$Classes[$class_name]))
{
require_once(self::$Classes[$class_name]);
return;
}
else
unset(self::$Classes[$class_name]);
}
self::RefreshObjectsTree();
if (isset(self::$Classes[$class_name]))
require_once(self::$Classes[$class_name]);
else
throw new Exception("Class [{$class_name}] was not found");
}
/**
* Sets the path of a class
*
* @param string $class_name
* @param string $path
*/
public static function SetClass($class_name, $path, $watch_folder = null)
{
self::$Classes[$class_name] = $path;
if ($watch_folder)
self::$ClassesWatchFolders[$class_name] = $watch_folder;
}
/**
* Loads all classes in the watch folders
*
*/
public static function LoadAllClasses()
{
foreach (self::$Classes as $class => $path)
self::AutoloadClass($class);
}
/**
* Gets the list of classes that can be autoloaded (class_name => path)
*
* @return array
*/
public static function GetClasses()
{
return self::$Classes;
}
/**
* Loads the list of classes and their path from "/res/misc/objectsTree.txt"
* @param string $only_folder will only load one Watch folder
* @param boolean $append will append to the list instead of rebuilding it
*
*/
public static function LoadClassesInfo($only_folder = null, $append = true)
{
$use_list = null;
if ($only_folder)
$use_list = array($only_folder);
else
{
if (!in_array(__QBasePath, self::$WatchFolders))
self::$WatchFolders[] = __QBasePath;
$use_list = self::$WatchFolders;
}
if (!$append)
self::$Classes = array();
foreach ($use_list as $Path)
{
$autoload_file = $Path . "temp/phpcache/autoload.php";
if (!file_exists($autoload_file))
{
$dir = $Path . "temp/phpcache/";
if (!is_dir($dir))
mkdir($dir, 0755, true);
file_put_contents($autoload_file, "<?php\n\n?>");
}
include($Path . "temp/phpcache/autoload.php");
}
}
/**
* Refreshes the list of classes
* Will always skip the "temp" and "res" folders in the root of the watch folder
*
* @param string $only_folder will only refresh one Watch folder
* @param boolean $append will append to the list instead of rebuilding it
*
*/
public static function RefreshObjectsTree($only_folder = null, $append = false)
{
$use_list = null;
if ($only_folder)
$use_list = array($only_folder);
else
{
if (!in_array(__QBasePath, self::$WatchFolders))
self::$WatchFolders[] = __QBasePath;
$use_list = self::$WatchFolders;
}
if ($append)
{
$full_list = self::$Classes;
$full_list_watch = self::$ClassesWatchFolders;
}
else
{
$full_list = array();
$full_list_watch = array();
}
foreach ($use_list as $Path)
{
$list = array();
$list_watch = array();
self::RefreshObjectsTreeRecurse($Path, $list, $list_watch, $Path);
// public static function SetClass($class_name, $path)
$text = "<?php\n";
foreach ($list as $k => $v)
{
$text .= "QAutoload::SetClass(\"".addslashes($k)."\", \"".addslashes($v)."\", \"".addslashes($Path)."\");\n";
}
$text .= "?>";
file_put_contents($Path . "temp/phpcache/autoload.php", $text);
$full_list = array_merge($full_list, $list);
$full_list_watch = array_merge($full_list_watch, $list_watch);
}
self::$Classes = $full_list;
self::$ClassesWatchFolders = $full_list_watch;
}
/**
* Recursive function for RefreshObjectsTree
*
* @param string $path
* @param array $list
* @param array $list_watch the list of watch folder per class
* @param string $watch_folder the current watch folder
*
*/
private static function RefreshObjectsTreeRecurse($path, &$list, &$list_watch, $watch_folder)
{
$items = scandir($path);
foreach ($items as $itm)
{
if (strlen(trim($itm, ". \t\r\n")) == 0)
continue;
if (($path == $watch_folder) && (($itm == "res") || ($itm == "temp")))
continue;
$p = rtrim($path, "/") . "/" . ltrim($itm, "/");
if (is_file($p))
{
$dot_pos = strrpos($itm, ".");
$ext = ($dot_pos === false) ? null : substr($itm, $dot_pos + 1);
if ($ext == "php")
{
$class_name = substr($itm, 0, $dot_pos);
// skip lowercase files
if ($class_name != strtolower($class_name))
{
$list[$class_name] = $p;
$list_watch[$class_name] = $watch_folder;
}
}
}
else
{
self::RefreshObjectsTreeRecurse($p, $list, $list_watch, $watch_folder);
}
}
}
/**
* Ads a folder that contains classes to be autoloaded
* Will always skip the "temp" and "res" folders in the root of the watch folder
*
* @param string $folder
* @param boolean $refresh_tree if true will refresh the tree from the filesystem
*/
public static function AddWatchFolder($folder, $set_as_runtime_folder = false, $refresh_tree = false)
{
if (!in_array($folder, self::$WatchFolders))
self::$WatchFolders[] = $folder;
if ($refresh_tree)
self::RefreshObjectsTree($folder);
else
self::LoadClassesInfo($folder, true);
if ($set_as_runtime_folder)
{
QBaseInit::SetRuntimeFile(rtrim($folder, "\\/") . "/temp/phpcache/runtime.php");
}
}
/**
* Not implemented yet
*
* @param string $folder
*/
public static function RemoveWatchFolder($folder)
{
throw new Exception("RemoveWatchFolder is not implemented yet");
}
/**
* Gets the full path to the file where the object is defined
*
* @param mixed $class The $class parameter may be an instance of an object or a string with the name of the object
* @return string
*/
public static function GetClassPath($class)
{
$class_name = null;
if (is_object($class))
$class_name = get_class($class);
else if (is_string($class))
$class_name = $class;
else
throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
if (isset(self::$Classes[$class_name]))
return self::$Classes[$class_name];
else
return null;
}
/**
* Gets the name of the folder where the class is stored
* The dir name is not buffered only the full path is
* the `dirname` functino will be called on each call to this function
*
* @param string $class
* @return string
*/
public static function GetClassDir($class)
{
$path = self::GetClassPath($class);
if ($path)
return dirname($path) . "/";
else
return null;
}
/**
* Gets the watch path for the specified class
*
* @param string $class
* @return string
*/
public static function GetClassWatchPath($class)
{
$class_name = null;
if (is_object($class))
$class_name = get_class($class);
else if (is_string($class))
$class_name = $class;
else
throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
if (isset(self::$ClassesWatchFolders[$class_name]))
return self::$ClassesWatchFolders[$class_name];
else
return null;
}
/**
* Gets the framework relative path to the file where the object is defined
*
* @param mixed $class The $class parameter may be an instance of an object or a string with the name of the object
* @return string
*/
public static function GetRelativeClassPath($class)
{
$class_name = null;
if (is_object($class))
$class_name = get_class($class);
else if (is_string($class))
$class_name = $class;
else
throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
if (isset(self::$Classes[$class_name]))
{
$path = self::$Classes[$class_name];
return substr($path, strlen(self::$ClassesWatchFolders[$class_name]));
}
else
return null;
}
/**
* Prints the objects that can be autoloaded and their definition file
*
*/
public static function PrintLoadableClasses()
{
echo "<h3>Classes that can be autoloaded: </h3>";
$classes = self::GetClasses();
foreach ($classes as $k => $v)
{
echo "$k: ".QAutoload::GetClassPath($k)."<br/>";
}
}
}
?>
Alex