将项目升级到 PHP 命名空间:一个简单的解决方案





5.00/5 (1投票)
如何为您的项目添加可选的 PHP 命名空间
引言
在本文中,我们将解释为什么以及如何在我们自己的 Web 应用程序项目中实现 PHP 命名空间。由于几乎所有项目都基于此,因此实现是在 Scavix Web 开发框架中完成的,您可以在那里找到完整的代码。如果您不熟悉它,您可能需要先阅读我们的其他文章。
目标读者
具有扎实 PHP 知识的经验丰富的软件开发人员。
问题所在
您可能自己也经历过:您将一个第三方库包含到项目中,它使用了您在项目中的其他地方已经使用过的一些类名(例如“Logger”或“Model”)。
如果您无法更改类名,这很糟糕,因为...
- ...向后兼容性。
- ...数据库中的外部类引用。
- ...
如果您无法更改第三方库,情况会变得更糟,因为它例如是闭源的(或者出于任何原因)。在这种情况下,您将无法使用此库。
解决方案
简而言之:在您的代码中使用 命名空间。更详细地说:如果您将代码封装到命名空间中,您可以使用任何不使用相同命名空间的库。当然,这种冲突的可能性要小得多,如果您明智地选择您的命名空间(例如 CompanyName\ProductName),它永远不会发生。
解决方案的问题
像往常一样,在您拥有干净的命名空间之前,存在一些障碍需要克服。如果您从零开始一个新项目,则没有任何问题,但请记住:如果您与第三方库发生冲突,那么您正在处理一个现有项目。如果您将命名空间添加到您的文件中,您需要为整个项目执行此操作。在您完成之前,它会崩溃。这当然不好,您可能会忘记某些地方,因此命名空间重新设计将花费您很长时间。但它真的值得。
解决解决方案中的问题
简而言之:向后兼容。当然,这并不那么简单。哦,等等:事实上在这种情况下是!哦,再等等:只有当您使用 自动加载时才是这样,但我们确信您这样做,所以我们将继续:)
基本过程是如果请求的类是普通的,但找到的类有一个限定名,则创建一个类别名。听起来很酷,是吧?让我们在下面的示例中看看
//file: myclass.php
namespace Scavix\SomeCoolProject;
class MyClass { /* some magic happens here */ }
这是类名的标准自动加载器机制// file: autoloader.php
function system_spl_autoload($class_name)
{
// use some function to find the file that declares the class requested
$file = __search_file_for_class($class_name);
require_once($file);
}
spl_autoload_register("system_spl_autoload",true,true);
参考资料:__search_file_for_class,spl_autoload_register。
// file: index.php
require_once("autoloader.php");
$mc = new MyClass(); // Throws an exception: Class not found
$mc = new Scavix\SomeCoolProject\MyClass(); // OK
当然,在此示例中,会遇到“类未找到”异常。因此,为了使该代码正常工作,我们现在将扩展自动加载器以自动实现上述规则:如果请求的类是普通的,但找到的类具有限定名,则创建一个类别名。
// file: autoloader_ex.php
function system_spl_autoload($class_name)
{
if( strpos($class_name, '\\') !== false )
{
$orig = $class_name;
$class_name = array_pop(explode('\\',$class_name));
}
// use some function to find the file that declares the class requested
$file = __search_file_for_class($class_name);
// remember the defined classes, include the $file and detect newly declared classes
$pre = get_declared_classes();
require_once($file);
$post = array_unique(array_diff(get_declared_classes(), $pre));
// loop through the new class definitions and create weak aliases if they are given with qualified names
foreach( $post as $cd )
{
$d = explode('\\',$cd);
if( count($d) > 1 )
{
// Aliasing full qualified classnames to their simple ones. Note: weak alias!
create_class_alias($cd,array_pop($d));
}
}
// get the class definition. note: we assume that there's only one class/interface in each file!
$def = array_pop($post);
if( !isset($orig) && !$def )
// plain class requested AND file was already included, so search up the declared classes and alias
{
foreach( array_reverse($pre) as $c )
{
if( !ends_with($c,$class_name) )
continue;
// Aliasing previously included class
create_class_alias($c,$class_name,true);
break;
}
}
else
{
$class_name = isset($orig)?$orig:$class_name;
if( strtolower($def) != strtolower($class_name) && ends_iwith($def,$class_name) )
// no qualified classname requested but class was defined with namespace
{
// Aliasing class
create_class_alias($def,$class_name,true);
}
}
}
spl_autoload_register("system_spl_autoload",true,true);
function create_class_alias($original,$alias,$strong=false)
{
// if strong create a real alias known to PHP
if( $strong )
class_alias($original,$alias);
// In any case store the alias in a global variable
$alias = strtolower($alias);
if( isset($GLOBALS['system_class_alias'][$alias]) )
{
if( $GLOBALS['system_class_alias'][$alias] == $original )
return;
if( !is_array($GLOBALS['system_class_alias'][$alias]) )
$GLOBALS['system_class_alias'][$alias] = array($GLOBALS['system_class_alias'][$alias]);
$GLOBALS['system_class_alias'][$alias][] = $original;
}
else
$GLOBALS['system_class_alias'][$alias] = $original;
}
参考资料:__search_file_for_class,spl_autoload_register,get_declared_classes,ends_with,ends_iwith,class_alias。
好的:比原来的多几行,但不太复杂,对吧? ;)。现在这会很好地工作
// file: index.php
require_once("autoloader_ex.php");
$mc = new MyClass(); // OK
$mc = new Scavix\SomeCoolProject\MyClass(); // OK
因此,现在您可以逐步将项目迁移到使用命名空间,而无需担心由于缺少命名空间声明而导致的崩溃。很抱歉放弃了您跳过命名空间的最后一个借口 :)
关于命名空间的一些说明
您可能已经注意到:我们喜欢命名空间!但是您将不得不以正确的方式进行操作,并考虑它在哪里有用。所以我们在我们的项目中遵循这些规则
- 使用一个唯一的命名空间根目录
- 每个文件只使用一个命名空间
- 确保每个类/接口都在一个命名空间中
- 将每个类/接口放在其自己的文件中,并且不要在其中放置任何其他内容
- 不要向命名空间添加函数
更改日志
- 2013/08/26:初始发布