>  기사  >  백엔드 개발  >  PHP의 배열 사양 및 사용자 정의 컬렉션

PHP의 배열 사양 및 사용자 정의 컬렉션

Guanhui
Guanhui앞으로
2020-06-17 17:41:012829검색

PHP의 배열 사양 및 사용자 정의 컬렉션

이것은 대략적인 스타일 가이드입니다. 배열 디자인이지만 객체 디자인 스타일 가이드에 추가하는 것은 모든 객체 지향 언어에 동적 배열이 있는 것은 아니기 때문에 옳지 않다고 생각됩니다. PHP는 Java(익숙할 수 있음)와 매우 유사하지만 내장 컬렉션 클래스 및 인터페이스 대신 동적 배열을 사용하기 때문에 이 기사의 예제는 PHP로 작성되었습니다.

배열을 목록으로 사용하기

모든 요소는 동일한 유형이어야 합니다

배열을 목록(특정 순서의 값 모음)으로 사용할 때 각 값은 다음과 같아야 합니다. z 유형:

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

$badList = [
    'a',
    1
];

목록 유형에 주석을 달 때 일반적으로 허용되는 스타일은 @var array<TypeOfElement>입니다. 인덱스 유형을 추가하지 않도록 하세요(항상 int임). @var array<TypeOfElement>。 确保不添加索引的类型(它总是int)。

应该忽略每个元素的索引

PHP 将自动为列表中的每个元素(0、1、2等)创建新索引。然而,你不应该依赖这些索引,也不应该直接使用它们。客户端应该依赖的列表的唯一属性是可迭代的可计数的

因此,可以随意使用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,等等)。你可以随意使用数组作为映射(可以通过其唯一的键从其中检索值)。

所有的键应该是相同的类型

使用数组作为映射的第一个规则是,数组中的所有键都应该具有相同的类型(最常见的是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>

각 요소의 인덱스는 무시되어야 합니다.

PHP는 목록의 각 요소(0, 1, 2 등)에 대해 자동으로 새 인덱스를 생성합니다. 그러나 이러한 인덱스에 의존하거나 직접 사용해서는 안 됩니다. 클라이언트가 의존해야 하는 목록의 유일한 속성은

Iterable

Countable입니다.

그러므로 foreachcount()를 자유롭게 사용해도 되지만, 목록의 요소를 반복하는 데 for를 사용하지 마세요.

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

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

// 公开地图可能很麻烦

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

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

/**
 * @throws UserNotFound
 */ 
public function userById(string $id): User
{ 
    // ...
}
(PHP에서는 목록에 누락된 인덱스가 있고 목록에 있는 요소보다 더 많은 인덱스가 있을 수 있기 때문에 for 루프가 작동하지 않을 수도 있습니다.)

필터 사용 요소를 제거하는 대신인덱스(unset())로 목록에서 요소를 제거하고 싶을 수도 있지만, array_filter()를 사용하여 요소를 제거하는 대신 원치 않는 요소 없이 새 목록을 만듭니다.

또한 요소의 인덱스에 의존해서는 안 되므로 array_filter()를 사용할 때 flag 매개변수를 사용하여 인덱스를 기준으로 요소를 필터링하면 안 됩니다. 또는 요소 및 인덱스 필터 요소를 기반으로 할 수도 있습니다.

final class SillyRegisterUserCommand
{
    public string $username;
    public string $plainTextPassword;
    public bool $wantsToReceiveSpam;
    public int $answerToIAmNotARobotQuestion;
}
🎜🎜 키 🎜가 🎜 관련되어 있고 인덱스(0,1,2 등)가 아닌 배열을 맵 🎜🎜🎜으로 사용하세요. 배열을 맵으로 자유롭게 사용할 수 있습니다(고유 키로 값을 검색할 수 있음). 🎜🎜🎜모든 키는 동일한 유형이어야 합니다🎜🎜🎜배열을 맵으로 사용하는 첫 번째 규칙은 배열의 모든 키가 동일한 유형이어야 한다는 것입니다(가장 일반적인 것은 string 유형입니다) 열쇠). 🎜
$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();
🎜🎜모든 값은 동일한 유형이어야 합니다. 🎜🎜🎜맵의 값도 마찬가지입니다. 동일한 유형이어야 합니다. 🎜
$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;
    }
}
🎜일반적으로 허용되는 매핑 유형 주석 스타일은 @var array<TypeOfKey, TypeOfValue>입니다. 🎜🎜🎜지도는 비공개로 유지되어야 합니다. 🎜🎜🎜목록은 단순한 특성으로 인해 객체 간에 안전하게 전달될 수 있습니다. 모든 클라이언트는 목록이 비어 있는 경우에도 해당 요소를 반복하거나 요소 수를 계산하는 데 이를 사용할 수 있습니다. 클라이언트가 해당 값이 없는 키에 의존할 수 있으므로 매핑을 처리하기가 더 어렵습니다. 이는 일반적으로 이를 관리하는 개체에 대해 비공개로 유지되어야 함을 의미합니다. 클라이언트는 내부 매핑에 직접 액세스할 수 없으며 대신 값을 검색하기 위해 getter(및 setter도 가능)가 제공됩니다. 요청한 키에 대한 값이 없으면 예외가 발생합니다. 그러나 지도와 그 값을 완전히 비공개로 유지할 수 있다면 그렇게 하십시오. 🎜
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(
            /* ... */
        );
    }
}
🎜🎜다양한 값 유형을 가진 맵에 객체 사용🎜🎜🎜하나의 맵에 🎜다양한 유형의 값🎜을 저장하고 싶을 때 객체를 사용하세요. 클래스를 정의하고 여기에 공개 유형의 속성을 추가하거나 생성자와 getter를 추가합니다. 이와 같은 개체의 예로는 구성 개체 또는 명령 개체가 있습니다. 🎜
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) {
    // ...
}
🎜🎜이러한 규칙에 대한 예외🎜🎜🎜 때로는 라이브러리나 프레임워크가 배열을 보다 동적인 방식으로 사용해야 하는 경우가 있습니다. 이러한 경우 이전 규칙을 따르는 것은 불가능하거나 바람직하지 않습니다. 예를 들어, 데이터베이스 테이블에 저장될 배열 데이터 또는 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제