ホームページ  >  記事  >  バックエンド開発  >  PHP の配列仕様とカスタム コレクション

PHP の配列仕様とカスタム コレクション

Guanhui
Guanhui転載
2020-06-17 17:41:012828ブラウズ

PHP の配列仕様とカスタム コレクション

これはほぼ 配列設計 のスタイル ガイドですが、これを オブジェクト デザイン スタイル ガイド に追加するのは違和感があります。そう、すべてのオブジェクト指向言語が動的配列を持っているわけではないからです。 PHP は Java (よく知られているかもしれません) によく似ているため、この記事の例は PHP で書かれていますが、組み込みのコレクション クラスやインターフェイスの代わりに動的配列を使用します。

配列をリストとして使用する

すべての要素は同じ型である必要があります

リストとしての配列 リスト (特定の順序での値のコレクション) の場合、各値は z:

$goodList = [
    'a',
    'b'
];

$badList = [
    'a',
    1
];

型である必要があります。リスト型に注釈を付けるための一般的に受け入れられているスタイルは次のとおりです:@var配列<TypeOfElement> 。インデックスのタイプを追加しないように注意してください (常に int です)。

各要素のインデックスは無視してください。

PHP は、リスト内の各要素 (0、1、2 など) に新しいインデックスを自動的に作成します。ただし、これらのインデックスに依存したり、直接使用したりしないでください。クライアントが依存する必要があるリストのプロパティは、iterablecountable だけです。

したがって、foreachcount() を自由に使用できますが、リスト内の要素をループするために for を使用しないでください。

// 好的循环:
foreach ($list as $element) {
}

// 不好的循环 (公开每个元素的索引):
foreach ($list as $index => $element) {
}

// 也是不好的循环 (不应该使用每个元素的索引):
for ($i = 0; $i < count($list); $i++) {
}

(PHP では、リスト内のインデックスが欠落していたり​​、リスト内の要素よりも多くのインデックスが存在したりする可能性があるため、for ループは機能しない場合もあります。)

要素を削除する代わりにフィルターを使用する

インデックス (unset()) によってリストから要素を削除することもできますが、次のように使用する必要があります。 array_filter( ) 要素を削除する代わりに、(不要な要素を除いた) 新しいリストを作成します。

同様に、要素のインデックスに依存すべきではないため、array_filter() を使用する場合は、要素をフィルタリングするために flag パラメータを使用しないでください。インデックス、さらに要素とインデックスに基づいて要素をフィルタリングします。

// 好的过滤:
array_filter(
    $list, 
    function (string $element): bool { 
        return strlen($element) > 2; 
    }
);

// 不好的过滤器(也使用索引来过滤元素)
array_filter(
    $list, 
    function (int $index): bool { 
        return $index > 3;
    },
    ARRAY_FILTER_USE_KEY
);

// 不好的过滤器(同时使用索引和元素来过滤元素)
array_filter(
    $list, 
    function (string $element, int $index): bool { 
        return $index > 3 || $element === 'Include';
    },
    ARRAY_FILTER_USE_BOTH
);

キー がインデックス (0、1、2 など) ではなく

に関連している場合、配列をマップとして使用します

。配列をマップとして自由に使用できます (マップから値を一意のキーで取得できます)。

すべてのキーは同じ型である必要があります

配列をマップとして使用する最初のルールは、配列内のすべてのキーが同じ型である必要があります (最も一般的なもの) 1 つはタイプ string のキーです)。

$goodMap = [
    'foo' => 'bar',
    'bar' => 'baz'
];

// 不好(使用不同类型的键)
$badMap = [
    'foo' => 'bar',
    1 => 'baz'
];

すべての値は同じ型でなければなりません

マップ内の値についても同様であり、同じ型である必要があります。

$goodMap = [
    'foo' => 'bar',
    'bar' => 'baz'
];

// 不好(使用不同类型的值)
$badMap = [
    'foo' => 'bar',
    'bar' => 1
];

一般的に受け入れられているマッピング タイプの注釈スタイルは、@var array<typeofkey typeofvalue></typeofkey> です。

マップはプライベートに保つ必要があります。

リストはその単純な特性により、オブジェクト間で安全に受け渡すことができます。すべてのクライアントは、リストが空であっても、これを使用して要素をループしたり、要素をカウントしたりできます。クライアントは対応する値を持たないキーに依存する可能性があるため、マッピングの処理はさらに困難になります。これは、一般に、それらを管理するオブジェクトに対してプライベートにしておく必要があることを意味します。クライアントは内部マッピングに直接アクセスすることはできません。代わりに、値を取得するためにゲッター (場合によってはセッター) が提供されます。要求されたキーの値が存在しない場合、例外がスローされます。ただし、マップとその値を完全に非公開にできる場合は、そうしてください。

// 公开一个列表是可以的

/**
 * @return array<User>
 */
public function allUsers(): array
{
    // ...
}

// 公开地图可能很麻烦

/**
 * @return array<string, User>
 */
public function usersById(): array
{ 
    // ...
}

// 相反,提供一种方法来根据其键检索值

/**
 * @throws UserNotFound
 */ 
public function userById(string $id): User
{ 
    // ...
}

複数の値タイプのマッピングにはオブジェクトを使用する

異なる タイプの 値を 1 つのマッピングに格納したい場合は、を使用してくださいオブジェクト。クラスを定義し、そのクラスに型指定されたパブリック プロパティを追加するか、コンストラクターとゲッターを追加します。このようなオブジェクトの例は、構成オブジェクトやコマンド オブジェクトです。

final class SillyRegisterUserCommand
{
    public string $username;
    public string $plainTextPassword;
    public bool $wantsToReceiveSpam;
    public int $answerToIAmNotARobotQuestion;
}

これらの規則の例外

場合によっては、ライブラリまたはフレームワークは、より動的に配列を使用する必要があります。方法 。このような場合、以前のルールに従うことは不可能です (また望ましくない)。たとえば、データベーステーブルに保存される配列データや、Symfony フォーム設定などです。

自定义集合类

自定义集合类是一种非常酷的方法,最后可以和IteratorArrayAccess和其朋友一起使用,但是我发现大多数生成的代码令人很困惑。第一次查看代码的人必须在 PHP 手册中查找详细信息,即使他们是有经验的开发人员。另外,你需要编写更多的代码,你必须维护这些代码(测试、调试等)。所以在大多数情况下,我发现一个简单的数组,加上一些适当的类型注释,就足够了。到底什么是需要将数组封装到自定义集合对象中的强信号?

  • 如果你发现与那个数组相关的逻辑被复制了。
  • 如果你发现客户端必须处理太多关于数组内部内容的细节。

使用自定义集合类来防止重复逻辑

如果使用相同数组的多个客户端执行相同的任务(例如过滤、映射、减少、计数),则可以通过引入自定义集合类来消除重复。将重复的逻辑移到集合类的一个方法上,允许任何客户端使用对集合的简单方法调用来执行相同的任务:

$names = [/* ... */];

// 在几个地方发现:
$shortNames = array_filter(
    $names, 
    function (string $element): bool { 
        return strlen($element) < 5; 
    }
);

// 变成一个自定义集合类:
use Assert\Assert;

final class Names
{
    /**
     * @var array<string>
     */
    private array $names;

    public function __construct(array $names)
    {
        Assert::that()->allIsString($names);
        $this->names = $names;
    }

    public function shortNames(): self
    {
        return new self(
            array_filter(
                $this->names, 
                function (string $element): bool { 
                    return strlen($element) < 5; 
                }
            )
        );
    }
}

$names = new Names([/* ... */]);
$shortNames = $names->shortNames();

在集合的转换上使用方法的好处就是获得了一个名称。这使你能够向看起来相当复杂的array_filter()调用添加一个简短而有意义的标签。

使用自定义集合类来解耦客户端

如果一个客户端使用特定的数组并循环,从选定的元素中取出一段数据,并对该数据进行处理,那么该客户端就与所有涉及的类型紧密耦合: 数组本身、数组中元素的类型、它从所选元素中检索的值的类型、选择器方法的类型,等等。这种深度耦合的问题是,在不破坏依赖于它们的客户端的情况下,很难更改所涉及类型的任何内容。因此,在这种情况下,你也可以将数组包装在一个自定义 的集合类中,让它一次性给出正确的答案,在内部进行必要的计算,让客户端与集合更加松散地耦合。

$lines = [];

$sum = 0;
foreach ($lines as $line) {
    if ($line->isComment()) {
        continue;
    }

    $sum += $line->quantity();
}

// Turned into a custom collection class:

final class Lines
{
    public function totalQuantity(): int
    {
        $sum = 0;

        foreach ($lines as $line) {
            if ($line->isComment()) {
                continue;
            }

            $sum += $line->quantity();
        }

        return $sum;
    }
}

自定义集合类的一些规则

让我们看看在使用自定义集合类时应用的一些规则。

让它们不可变

对集合实例的现有引用在运行某种转换时不应受到影响。因此,任何执行转换的方法都应该返回类的一个新实例,就像我们在上面的例子中看到的那样:

final class Names
{
    /**
     * @var array<string>
     */
    private array $names;

    public function __construct(array $names)
    {
        Assert::that()->allIsString($names);
        $this->names = $names;
    }

    public function shortNames(): self
    {
        return new self(
            /* ... */
        );
    }
}

当然,如果要映射内部数组,则可能要映射到另一种类型的集合或简单数组。与往常一样,请确保提供适当的返回类型。

只提供实际客户需要和使用的行为

你不必扩展泛型集合库类,也不必自己在每个自定义集合类上实现泛型筛选器、映射和缩减方法,只实现真正需要的。如果某个方法在某一时刻不被使用,那么就删除它。

使用 IteratorAggregate 和 ArrayIterator 来支持迭代

如果你使用 PHP,不用实现所有的Iterator接口的方法(并保持一个内部指针,等等),只是实现IteratorAggregate接口,让它返回一个ArrayIterator实例基于内部数组:

final class Names implements IteratorAggregate
{
    /**
     * @var array<string>
     */
    private array $names;

    public function __construct(array $names)
    {
        Assert::that()->allIsString($names);
        $this->names = $names;
    }

    public function getIterator(): Iterator
    {
        return new ArrayIterator($this->names);
    }
}

$names = new Names([/* ... */]);

foreach ($names as $name) {
    // ...
}

权衡考虑

为你的自定义集合类编写更多代码的好处是使客户端更容易地使用该集合(而不是仅仅使用一个数组)。如果客户端代码变得更容易理解,如果集合提供了有用的行为,那么维护自定义集合类的额外成本就是合理的。但是,因为使用动态数组非常容易(主要是因为你不必写出所涉及的类型),所以我不经常介绍自己的集合类。尽管如此,我知道有些人是它们的伟大支持者,所以我将确保继续寻找潜在的用例。

推荐教程:《PHP

以上がPHP の配列仕様とカスタム コレクションの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。