1708882125
2024-02-25 15:15:26
私たちはすでに簡単なプレゼンテーションを行っていましたが、 PHP のコレクション 以前に修正されました。 念のために言っておきますが、コレクションは PHP でのテーブルの処理を標準化できるツールです。ネイティブで使用できる関数は、構成と操作 (パラメーターの順序、参照によるかどうかなど) の点で少し「異質」です。 コレクションには、JavaScript で行うのと同じように、メソッドを「チェーン」できる追加機能とオブジェクト構文も付属しています。
コレクションを使用すると、テーブルを操作できます。 しかし、この修正では、特にオブジェクトの配列を操作するためにそれを使用できることがわかります。 そのために私たちは挑戦に頼っていきます。」 リトルモンスター #3« 。
プログラムでは、一歩ずつ前進します。
コレクションを使用しない OOP でのチャレンジの解決
「リトル モンスター #3」チャレンジでは、モンスターをその特徴の 1 つである食べ物の種類 (4 種類) に従ってグループ化することが目的です。
4 つのグループを形成したら、別の特性、つまり重量に従ってモンスターを順序付けします。 それらは昇順または降順のいずれかに配置されます (これも食品の種類に応じて異なります)。
これらすべてをどのようにコーディングできるかを見てみましょう。
まず、食べ物の種類を保存する列挙型と、モンスターを管理し、その情報を解析するクラスがあります。
enum FoodType: string
{
case FRUITS = 'F';
case GRASS = 'G';
case ROCK = 'R';
case WOOD = 'W';
}
class Monster
{
public function __construct(
public readonly string $name,
public readonly FoodType $foodType,
public readonly int $weight,
) {}
public static function createFromText(string $informations): Monster
{
$data = explode(':', $informations);
return new self(
name: $data[0],
foodType: FoodType::from($data[1]),
weight: (int) $data[2]
);
}
}
ちょっとした説明:
- 列挙では、さまざまなケースを任意の順序で並べるのではなく、課題への応答として予想される順序を使用しました… (ネタバレ: 最終的には役立ちます)
- 他の修正と同様に、Monster には静的メソッドがあり、Monster のインスタンスを解析して返す役割を果たします。 分析を管理するのはメーカーではありません。 そして、私のプロパティは、プロパティのプロモーションを通じてコンストラクターで定義されます。
次に、これらの (あまりにも) かわいい小さなモンスターの行を表す MonstersLines クラスを作成しましょう。
class MonstersLines
{
/**
* @var Monster[]
*/
private array $monsters = [];
public function addMonsterFromText(string $informations): void
{
$this->monsters[] = Monster::createFromText($informations);
}
public function getMonstersFromFoodType(FoodType $foodType, int $nbMonsters = 3): string
{
// 1. Initialisation d'un tableau vide
$monsters = [];
// 2. Je filtre selon le type de nourriture
foreach ($this->monsters as $monster) {
if ($monster->foodType === $foodType) {
$monsters[] = $monster;
}
}
// 3. Je trie dans un sens ou dans l'autre, selon le type de nourriture
// grâce à la fonction usort, en comparant les poids
if ($foodType === FoodType::FRUITS || $foodType === FoodType::GRASS) {
usort($monsters, function (Monster $m1, Monster $m2) {
return $m1->weight <=> $m2->weight;
});
} else {
// Sens inverse
usort($monsters, function (Monster $m1, Monster $m2) {
return $m2->weight <=> $m1->weight;
});
}
// 4. Je garde les N premiers monstres
$monsters = array_slice($monsters, 0, $nbMonsters);
// 5. J'extraie les noms des monstres
$names = array_column($monsters, 'name');
// 6. Je lie les noms avec un "-"
return implode('-', $names);
}
public function getAllFirstThree(): string
{
// 0. Je précise l'ordre attendu
$foodTypes = [
FoodType::FRUITS,
FoodType::GRASS,
FoodType::ROCK,
FoodType::WOOD
];
// 0. J'initialise un tableau vide en vue de mon implode final
$result = [];
// 1. Je parcours chaque type pour récupérer les 3 premiers monstres
foreach ($foodTypes as $foodType) {
$result[] = $this->getMonstersFromFoodType($foodType, 3);
}
// 2. Je lie les éléments avec un "-"
return implode('-', $result);
}
}
ちょっとした説明:
- コンストラクターはなく、静的なMonsterメソッドを使用した専用メソッドでモンスターを1つずつ追加します。
- モンスターを分類するには2つの方法があり、番号を付けて手順を説明します。
- 複数の変数を同時に操作していることに気付きます。ループ、参照による関数 (usort)、値を返す関数 (implode) があります。 すべてがうまく機能しますが、構造的な観点から見ると、全体が少し「異なっています」。
コレクションを使用してこれらすべてを改善する方法を見ていきます…
PHP でのコレクションの使用
コレクションには、特定のプロパティに従って昇順または降順で並べ替えることができる「sortBy」および「sortByDesc」関数が用意されています。
変数関数の使用
これを利用して、使用する関数を変数に格納します。 そして、コレクションを「チェーン」するときに動的に呼び出します。
コレクションなし (上記と同じコード)
public function getMonstersFromFoodType(FoodType $foodType, int $nbMonsters = 3): string
{
$monsters = [];
foreach ($this->monsters as $monster) {
if ($monster->foodType === $foodType) {
$monsters[] = $monster;
}
}
if ($foodType === FoodType::FRUITS || $foodType === FoodType::GRASS) {
usort($monsters, function (Monster $m1, Monster $m2) {
return $m1->weight <=> $m2->weight;
});
} else {
usort($monsters, function (Monster $m1, Monster $m2) {
return $m2->weight <=> $m1->weight;
});
}
$monsters = array_slice($monsters, 0, $nbMonsters);
$names = array_column($monsters, 'name');
return implode('-', $names);
}
コレクションとともに
public function getMonstersFromFoodType(FoodType $foodType, int $number = 3): string
{
// Gestion du tri, par défaut, dans l'ordre croissant, décroissant pour ROCK et WOOD
$sort="sortBy";
if ($foodType === FoodType::ROCK || $foodType === FoodType::WOOD) {
$sort="sortByDesc";
}
return collect($this->monsters)
->filter(function(Monster $item) use ($foodType) {
return $item->foodType === $foodType;
})
->$sort('weight')
->slice(0, $number)
->implode('name', '-');
}
より簡潔で、よりよく整理されていますね?!
コレクション部分について少し説明します。
- まず、collect 関数を使用してコレクションを作成します。 その後、適用したいすべての関数を実行順に連鎖させることができます。
- まず、array_filter を作成する役割を持つ filter メソッドから始めます。 したがって、匿名関数を使用してフィルタリング条件、つまり食品の種類の一致を指定します。
- 次に、「重み」に基づいて一方向または別の方向に動的に並べ替えます。 もう if/else は必要ありません。
- スライスは array_slice を作成します
- implode はプロパティの名前をパラメータとして受け取り、名前を取得するために array_column を保存します。
列挙型のネイティブの「case」メソッドの使用
getAllFirstThree メソッドを再設計するには、列挙型のネイティブの「cases」メソッドを使用します。 これが、列挙型で定義された順序に注意を払った理由です。
コレクションなし (上記と同じコード)
public function getAllFirstThree(): string
{
$foodTypes = [
FoodType::FRUITS,
FoodType::GRASS,
FoodType::ROCK,
FoodType::WOOD
];
$result = [];
foreach ($foodTypes as $foodType) {
$result[] = $this->getMonstersFromFoodType($foodType, 3);
}
return implode('-', $result);
}
コレクションとともに
public function getAllFirstThree(): string
{
return collect(FoodType::cases())
->map( fn(FoodType $foodType) => $this->getMonstersFromFoodType($foodType, 3) )
->implode('-');
}
確信している ?!
ちょっとした説明:
- collect はパラメータとして配列を期待します。 enum の case() メソッドは、すべてのケースを含む配列を返します。 これこそまさに私たちが必要としているものなのです! 注意してください。ここでは、列挙型の順序がチャレンジ ステートメントで期待されている順序と同じであるため、機能します。 別の注文では機能しなくなります。
- map メソッドは array_map を作成します。 ここで使えます アロー関数 関数の内容が単一の命令で構成されているため、「fn () =>」となります。
- もう一度終了するために少し内破します。
Pest PHP を使用した最終コードと単体テスト
チャレンジを完了するには、次のコードを実行します。
$monsterLine = new MonstersLines;
foreach ($monsters as $monsterInformations) {
$monsterLine->addMonsterFromText($monsterInformations);
}
$solution = $monsterLine->getAllFirstThree();
したがって、この修正では、オブジェクト テーブルでのコレクションの使用が、特にプロパティによる並べ替え、プロパティの値の取得などにおいて、完全に機能的かつ実用的であることがわかりました。
コレクションには引き続き多くの機能があり、これらの機能については、 PHP のコレクションの公式ドキュメント (Laravel ドキュメント内)。
コード全体は次の場所にあります。 ギットハブ、 Pest PHP で実行される単体テストと同様に。 それらはまた、非常に実用的でした コードをリファクタリングする コレクションと一緒に。 私は次のプロセスに従いました。
- コレクションを使用しない最初のソリューション
- 適切な動作を保証する単体テストを作成する
- コレクションを使用したリファクタリング
- プロセス全体でテストを開始して、すべてがまだ問題がないことを確認します。
2 つのクラスをテストするために、データセットを使用してテストを作成しました。 各データセットにはクラスの名前が含まれているため、クラスを動的にインスタンス化できます。 以下にその様子を示します。
test('Tri des monstres ROCK', function(string $className) {
$monsters = ['Drex29:R:16', 'Traz65:W:74', 'Blit86:R:27', 'Brux18:F:83', 'Cobi82:R:72', 'Brux61:F:56', 'Draz68:G:97'];
$monsterLine = new $className;
foreach ($monsters as $monsterInformations) {
$monsterLine->addMonsterFromText($monsterInformations);
}
expect($monsterLine->getMonstersFromFoodType(FoodType::ROCK, 3))->toBe('Cobi82-Blit86-Drex29');
})->with([
[MonstersLinesWC::class],
[MonstersLines::class]
]);
これは必ずしも「完璧な」アプローチであるとは限りません。 それを行う前に、少なくとも 2 つのクラスが同じメソッドを持つことを確認するインターフェイスを作成できます。 あるいは、パターンに向かって進みます… デザインパターン 戦略 😉
#PHP #オブジェクトの配列でのコレクションの使用