PSR-4:自动加载器(组合器)和扩展命名空间确保回退 php

PSR-4: Autoloader (composer) and extending namespaces ensuring fallback php

我的命名空间回退和在 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 - 它可以使用 site/src/ACME/app/View/Example.php.

抱歉,我是命名空间的新手,所以我的措辞可能不太好。

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

编辑:原来我错了,你的例子可以用 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. Class 名称匹配命名空间 \ACME\App\Site(您的第一个 PSR-4 条目)。
  4. 加载文件app/View.php
  5. 文件不存在。错误。

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

那我该如何解决呢?

您似乎想将可重用库代码与站点代码分开。如果是这样的话,我会使用单独的名称空间。例如,使用 ACME\Site 命名空间来保存您的可重用代码,并使用 ACME\MySiteName 来保存您的站点特定代码。这样就不会有歧义,作曲家加载你的 类.

也不会有问题

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

好的,没关系,但您必须使用 hack 来解决您的问题。 Composer 有一个 classmap 加载器,您必须使用它来代替首选的 PSR-4 加载器。

{
    "autoload": {
        "classmap": ["app/", "src/"]
    }
}

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

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

您可能想为不同的目的创建几个子 classes,因此您需要在某个地方保存要使用的逻辑。处理此问题的组件称为 "service locator" 或有时称为 "DI container".

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

让我们稍微分开一下,因为它们现在都混在一起了。

What I am trying to do is this:

  1. Have a core which can overwritten / extended.
  2. The core is based off an interface.

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

让我们以 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 {}

您明确命名了三个 class。您需要三个与名称空间和 class 名称相匹配的文件。自动加载不需要做任何检测 class 是否存在——因为你不能选择继承不存在的 class ,否则忽略它。

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

So what I really want to do is make it so I don't have to have site/app/View/View.php, and I don't have to have site/app/View/Example.php - it can use site/src/ACME/app/View/Example.php.

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

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

Sorry I'm new to namespaces so I may not of phrased it very well.

命名空间没什么特别的,如果你使用 PSR-0 风格class带下划线的名字也会出现同样的问题:

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 的文件中以第二个名称导入一个 class。但这只是该文件范围内的别名(即不影响其他文件或全局范围)。