2

我的命名空间回退和在 Composer 中使用 PSR-4 加载程序时遇到问题。

我想做的是:

  1. 有一个可以覆盖/扩展的核心。
  2. 核心基于接口。

目录结构是这样的:

site/app/View/Example.php
site/src/ACME/app/View/Example.php
site/src/ACME/app/Interface/View.php

我没有设置这个配置,所以如果你有更好的建议,那就去吧。

我的作曲家 json 对于 psr-4 是这样的:

 "autoload": {
    "psr-4": {
         "ACME\\App\\Site\\" : "app/",
         "ACME\\App\\" : "src/AMCE/app/"
    }
}

我认为如果找不到站点,这将使 ACME\App\Site\View 回退到 ACME\App\View (注意我还没有完成界面部分......)。

我的 site/app/View/Example.php 代码如下:

namespace ACME\App\Site\View;

class ViewExample extends View {

当我也有 site/app/View/View.php 时,哪个有效。看起来像:

namespace ACME\App\Site\View;

class View extends \ACME\App\View\View {

site/src/app/View/View.php 如下所示:

namespace ACME\APP\View;

class View {

这个应该使用接口(我还没试过)。

所以我真正想做的就是做到这一点,这样我就不必拥有site/app/View/View.php,也不必拥有site/app/View/Example.php——它可以使用网站/src/ACME/app/View/Example.php。

对不起,我是命名空间的新手,所以我可能不会很好地表达它。

我得到的是我认为 ACME\App\Site 会回退到 ACME\App - 它没有?还是我做错了?目前它需要所有文件到位。

4

3 回答 3

2

编辑:原来我最初是错的,有可能让你的例子与 PSR-4 一起工作!您只需要为可以从不同位置加载的命名空间指定一个目录数组。

简单的解决方案

{
    "autoload": {
        "psr-4": {
            "ACME\\App\\Site\\": ["app/", "src/ACME/app"],
            "ACME\\App\\": "src/ACME/app/"
        }
    }
}

就个人而言,我宁愿更明确地命名我的命名空间,见下文。

原始答案

Composer PSR-4 加载器在尝试加载不存在的文件时不会后退。它只是立即失败。它的流程看起来像:

  1. \ACME\App\Site\View未加载
  2. 扫描 PSR-4 条目以查找匹配的命名空间
  3. 类名匹配命名空间\ACME\App\Site(您的第一个 PSR-4 条目)。
  4. 加载文件app/View.php
  5. 文件不存在。错误。

它永远不会回到第 3 步并尝试下一个命名空间。

那么我该如何解决呢?

看起来您想将可重用的库代码与站点代码分开。如果是这种情况,我会使用单独的命名空间。例如,使用ACME\Site命名空间来保存可重用的代码,并ACME\MySiteName用于特定于站点的代码。这样就不会有歧义了,作曲家在加载你的类时也不会遇到任何问题。

但我不想重新排列我的命名空间!

好的,这很好,但是您必须使用 hack 来解决您的问题。Composer 有一个classmap加载器,您必须使用它而不是首选的 PSR-4 加载器。

{
    "autoload": {
        "classmap": ["app/", "src/"]
    }
}
于 2016-02-19T16:50:20.377 回答
2

让我们把事情分开一点,因为现在它们都混在一起了。

我想做的是:

  1. 有一个可以覆盖/扩展的核心。
  2. 核心基于接口。

这听起来像是基本的面向对象继承。一个接口定义了提议的公共行为,核心实现了所需的基础,细节实现改变了一些部分,并重用了其他部分。

让我们以 PHP 使用绝对命名空间名称的方式编写示例代码:

class \ACME\App\Site\View\ViewExample extends \ACME\App\Site\View\View {}

class \ACME\App\Site\View\View extends \ACME\App\View\View {}

class \ACME\App\View\View {}

您有三个明确命名的类。您需要三个与命名空间和类名匹配的文件。自动加载不需要对是否存在类进行任何检测 - 因为您不能选择性地从不存在的类继承,或者以其他方式忽略它。

另一方面,默认实现三级继承很可能是太多了。对我来说,这看起来像是糟糕的设计,并且会使维护代码变得比必要更难。根据您想要实现的目标,有很多替代方案可以让您更轻松地获得想要的目标。例如,要改变一些行为细节,有装饰器模式或策略模式。

所以我真正想做的就是做到这一点,这样我就不必拥有site/app/View/View.php,也不必拥有site/app/View/Example.php——它可以使用网站/src/ACME/app/View/Example.php。

你不能拥有这个。您的代码明确声明它继承自\ACME\App\Site\View\View,因此此类必须存在于某处。

这与任何自动加载无关。为了进行实验,您可以将所有代码添加到一个文件中,然后运行它。这将使 PHP 立即知道所有类,问题将变得显而易见:当其他类同时继承一个类时,您不能删除它。

对不起,我是命名空间的新手,所以我可能不会很好地表达它。

命名空间并不是什么花哨的东西,如果你使用带有下划线的 PSR-0 风格的类名,也会出现同样的问题:

class ACME_App_Site_View_ViewExample extends ACME_App_Site_View_View {}

// This class MUST be present for the above class to work
class ACME_App_Site_View_View extends ACME_App_View_View {}

class ACME_App_View_View {}

命名空间的主要新特性是您可以在一个文件中以另一个名称导入一个类use OtherNamespace\Classname。但这只是该文件范围内的别名(即不影响其他文件或全局范围)。

于 2016-02-23T00:32:49.200 回答
1

命名空间和自动加载不是这项工作的正确工具。命名空间只是一种确保两个人(或代码的一部分)不会使用相同名称来表示不同事物的方法。自动加载只是一种避免列出要从中加载代码的每个源文件的方法。

当您在另一个类中覆盖一个类的行为时,它们不是同一个类;通常,您会希望继承默认操作并重用其中的一部分。

您可能希望为不同的目的创建多个子类,因此您需要在某个地方保存要使用的逻辑。处理此问题的组件称为“服务定位器”或有时称为“DI 容器”。

命名空间允许您将短名称映射到更长的、唯一的类名;自动加载让您将特定的唯一类名映射到源文件;服务位置是您在特定情况下选择要使用的独特类的方式。

于 2016-02-20T00:19:16.037 回答