Maison  >  Article  >  développement back-end  >  Spécifications des tableaux et collections personnalisées en PHP

Spécifications des tableaux et collections personnalisées en PHP

Guanhui
Guanhuiavant
2020-06-17 17:41:012883parcourir

Spécifications des tableaux et collections personnalisées en PHP

Il s'agit à peu près d'un guide de style pour la conception de tableaux, mais son ajout au Guide de style de conception d'objets ne le fait pas. je me sens bien Oui, car tous les langages orientés objet n'ont pas de tableaux dynamiques. Les exemples de cet article sont écrits en PHP car PHP ressemble beaucoup à Java (que vous connaissez peut-être), mais utilise des tableaux dynamiques au lieu de classes et d'interfaces de collection intégrées.

Utiliser un tableau comme liste

Tous les éléments doivent être du même type

Lors de l'utilisation d'un array as Lorsqu'il s'agit d'une liste (une collection de valeurs dans un ordre spécifique), chaque valeur doit être de type z :

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

$badList = [
    'a',
    1
];

Un style généralement accepté pour annoter les types de liste est : @var array<TypeOfElement>. Assurez-vous de ne pas ajouter le type de l'index (c'est toujours int).

L'index de chaque élément doit être ignoré

PHP créera automatiquement un nouvel index pour chaque élément de la liste (0, 1, 2, etc.). Cependant, vous ne devez pas vous fier à ces index ni les utiliser directement. Les seules propriétés des listes sur lesquelles les clients doivent s'appuyer sont iterable et countable.

Alors, n'hésitez pas à utiliser foreach et count(), mais n'utilisez pas for pour parcourir les éléments de la liste :

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

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

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

(En PHP, for le bouclage est même possible Ne fonctionne pas car il peut manquer des index dans la liste et il peut y avoir plus d'index qu'il n'y a d'éléments dans la liste)

Utilisez un filtre au lieu de supprimer des éléments

Vous souhaiterez peut-être supprimer des éléments d'une liste par index (unset()), cependant, vous devez utiliser array_filter() pour créer une nouvelle liste (sans éléments indésirables) plutôt que de supprimer des éléments.

De même, vous ne devez pas vous fier à l'index d'un élément, donc lorsque vous utilisez array_filter(), vous ne devez pas utiliser le paramètre flag pour filtrer les éléments en fonction de l'index, ni même filtrer les éléments en fonction des deux éléments. et index.

// 好的过滤:
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
);

utilise un tableau comme carte

lorsque la clé est liée, pas l'index (0,1,2, etc.) . Vous êtes libre d'utiliser des tableaux comme cartes (à partir desquelles les valeurs peuvent être récupérées par leurs clés uniques).

Toutes les clés doivent être du même type

La première règle de l'utilisation de tableaux comme cartes est que toutes les clés du tableau doivent être du même type (le plus courant celles-ci sont des touches de type string).

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

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

Toutes les valeurs doivent être du même type

Il en va de même pour les valeurs de la carte : elles doivent être du même type.

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

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

Un style d'annotation généralement accepté pour les types de mappage est : @var array<TypeOfKey, TypeOfValue>.

Les cartes doivent rester privées.

Les listes peuvent être transmises en toute sécurité entre les objets en raison de leurs caractéristiques simples. N'importe quel client peut l'utiliser pour parcourir ses éléments ou pour compter ses éléments, même si la liste est vide. Les mappages sont plus difficiles à gérer car les clients peuvent s'appuyer sur des clés qui n'ont pas de valeurs correspondantes. Cela signifie qu'en général, ils doivent rester privés de l'objet qui les gère. Les clients ne sont pas autorisés à accéder directement au mappage interne, mais des getters (et éventuellement des setters) sont fournis pour récupérer les valeurs. Si une valeur n'existe pas pour la clé demandée, une exception est levée. Cependant, si vous pouvez garder la carte et ses valeurs complètement privées, faites-le.

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

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

// 公开地图可能很麻烦

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

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

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

Utiliser l'objet

pour les mappages avec plusieurs types de valeurs Lorsque vous souhaitez stocker des valeurs de différents types dans un seul mappage, veuillez utiliser un objet. Définissez une classe et ajoutez-y des propriétés typées publiques, ou ajoutez des constructeurs et des getters. Des exemples d'objets comme celui-ci sont des objets de configuration ou des objets de commande :

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

Exceptions à ces règles

Parfois, une bibliothèque ou un framework doit utiliser des tableaux de manière plus dynamique chemin . Dans ces cas-là, il n’est pas possible (ni souhaitable) de suivre les règles précédentes. Par exemple, les données d'un tableau, qui seront stockées dans une table de base de données, ou la configuration d'un formulaire 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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer