このエントリーをはてなブックマークに追加

PHP Advent Calendar 2021 5日目の記事です。
昨日は@EbiTTさんの Laravelのキューで並列処理をするには でした。

PHP8.1のEnumの挙動を色々試したので、サンプルコードとともにご紹介しようと思います。

ちなみに昨日、Symfonyのアドベントカレンダーに、SymfonyのEnumTypeで遊んだ記事を書いたのも私です。
Enumをどれだけ待望していたかわかると思います(笑)。
というのも、弊社でメインの開発領域として扱っているリスティング広告やSNS広告にはめちゃくちゃ「○○オプションリスト」みたいなのが多くて、今まで自作のEnumTraitを何度も作っては自作ゆえの不具合に泣く数年を過ごしていました。 今年PHP8.0で開発し始めた新システムをサクッとPHP8.1に上げられそうなので、自作のEnumTraitを一日も早く捨てたいです!

基本のEnum

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;
}
<?php

class LanguageTest extends TestCase
{
    public function test()
    {
        $this->assertEquals('PHP', Language::PHP->name);
        try {
            $v = Language::PHP->value;
        } catch (\Throwable $e) {
            $this->assertTrue(str_contains($e->getMessage(), 'Undefined property'));
        }
        $this->assertTrue(Language::PHP !== 'PHP');
        $this->assertTrue(Language::PHP instanceof Language);
        $cases = Language::cases();
        $this->assertCount(4, $cases);
        $this->assertSame($cases[1], Language::PHP);
    }
}

Enumで取りうる値は case と呼びます。
数字から始まるcaseは定義できませんでした(PHPでは数字から変数名もダメなので、同じルールみたいです)
定義した case PHPPHP 部分を取るときは Language::PHP->name で文字列として取り出せます。
基本のEnumではvalueを利用することはできません。( Language::PHP->value で1が取れると思ったら取れませんでした…)
Enum内に定義されている全てのcaseを配列で取り出すときは Language::cases() で取れます。
コードの1行目に書いた Language::PHP と2行目に書いた Language::PHP=== でtrue判定でした。
Enumを使うfunctionやmethodやプロパティでの型宣言はEnumのクラス名で指定できます。( function foo(Language $lang) {...} と書けます)

Backed Enum

<?php

enum Language: string
{
    case Java = 'java';
    case PHP = 'php';
    case TypeScript = 'ts';
    case JavaScript = 'js';
}
<?php

class LanguageTest extends TestCase
{
    public function test()
    {
        $this->assertEquals('PHP', Language::PHP->name);
        $this->assertEquals('php', Language::PHP->value);
        $this->assertTrue(Language::PHP !== 'PHP');
        $this->assertTrue(Language::PHP instanceof Language);
        $cases = Language::cases();
        $this->assertCount(4, $cases);
        $this->assertSame($cases[1], Language::PHP);
        $this->assertSame(Language::PHP, Language::from('php'));
    }
}

enum Language :string のように各caseの値の型を決めたenumはBacked Enumと呼ばれます。
case TypeScript = 'ts'; と定義したとき、Language::TypeScript->nameTypeScript が、Language::TypeScript->valuets が取れます。
Language::from('php') のようにcaseの値をfromに渡すとEnumのインスタンスを得ることができます。
その他の挙動はBasic Enumとは同じです。

Enumのメソッド

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;

    public function getTargetDomain(): string
    {
        return match($this) {
            self::PHP, self::Java => 'backend',
            self::JavaScript, self::TypeScript => 'frontend',
        };
    }
}

Enumにもメソッドを生やすことができます。
メソッド内からはEnumインスタンス自体を $this で通常のクラスと同様に参照できました。
ただし function __toString(): string だけは定義できないようです :sweat_drops: (PhpStormに怒られました…)

スクリーンショット 2021-12-04 23 17 00

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;

    public static function getBackendLanguages(): array
    {
        return [
            self::Java,
            self::PHP,
        ];
    }

    public static function getFrontendLanguages(): array
    {
        return [
            self::TypeScript,
            self::JavaScript,
        ];
    }
}

もちろんstatic methodも生やすことができました。

まとめ

ここまでで実際のプロダクトコードでPHPのEnumを使っていくための良い準備運動ができたと思います!
個人的には__toString()が生やせないのはちょっと衝撃でした。

今回試したコードは https://github.com/77web/my-php8.1-enum-playground に公開してあるので、実際にお手元で動かしてみたい方はどうぞ。

明日は @rana_kualu さんの記事です!


このエントリーをはてなブックマークに追加

Symfony Advent Calendar 2021 4日目の記事です。
昨日は @ttskch さんの UniqueEntity制約はpersistしただけの既存エンティティとの重複は検出してくれないので工夫が必要 でした。

Symfony6は "php": ">=8.0.2" という攻めた必須要件で、PHP8.1を使っているとEnumの恩恵を受けることもできるようになりました!
というわけで、早速PHP8.1.0とSymfony6の環境でEnumフォームタイプで遊んでみようと思います。(EnumFormType自体はSymfony5.4から利用可能です)
※ 以下、コードの詳細は遊んだプルリク https://github.com/77web/php8.1-Symfony6-playground/pull/1 のコミットログでも確認できます。

EnumType

EnumTypeはChoiceTypeを継承しているのでmultiple, expanded等ChoiceTypeでおなじみのオプションも使えます。 https://github.com/77web/php8.1-Symfony6-playground/blob/c125e99ac14ff4143e2446c6a48ad3d9b136f13b/src/Form/ContactType.php

<?php
class ContactType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name')
            ->add('age', EnumType::class, [
                'class' => Age::class,
            ])
            ->add('interests', EnumType::class, [
                'class' => Interest::class,
                'multiple' => true,
                'expanded' => true,
            ])
            ->add('opinion')
        ;
    }
    // ...
}

EnumTypeを含むフォームへのsubmit

https://github.com/77web/php8.1-Symfony6-playground/blob/c125e99ac14ff4143e2446c6a48ad3d9b136f13b/tests/Form/ContactTypeTest.php

BackedでないEnumに対してはEnumに定義されたcase上の順番(0index)の番号、BackedEnumに対してはBackedな値(Enumのインスタンスのvalueに当たるもの)をsubmitします。

<?php

$form->submit([
    'name' => '77web',
    'age' => 3, // Age::AGE_40_TO_49はAgeの4番目のcase
    'interests' => ['php', 'angular', 'aws'], // BackedEnumなのでvalue
    'opinion' => 'I love Symfony',
]);

$form->getData() で実際にセットされた値を確認すると、ちゃんと各Enumのインスタンスがセットされていることがわかります。

<?php

$this->assertEquals(Age::AGE_40_TO_49, $data->getAge());
$this->assertEquals([Interest::PHP, Interest::FRONTEND, Interest::INFRA], $data->getInterests());

EnumTypeを使ったフォームのレンダリング

HTMLになると通常のChoiceTypeと同じです。

multiple => true, expanded => falseにしたときはselect multiple multiple => true, expanded => trueにしたときはcheckbox
スクリーンショット 2021-12-04 0 15 10 スクリーンショット 2021-12-04 0 15 23

先ほど見たようにselect,radio,inputの値はsubmitしたい値なので、BackedでないEnumでは連番、BackedEnumではvalueとなります。

BackedでないEnumのselectのHTML出力

スクリーンショット 2021-12-04 0 40 50

BackedEnumのcheckboxのHTML出力

スクリーンショット 2021-12-04 0 45 22

まとめ

PHP8.1のEnum+Symfony6のEnumType、 いままで自作の EnumTrait で色々むちゃしていたところがスッキリできそうです。遊びでコードを書いているだけでも、とても良い開発者体験を味わうことができました。 ☺️ 早くプロダクションコードをPHP8.1にして、自作のEnumTraitを駆逐したいです :grin:

明日も @ttskch さんの記事です!お楽しみに!


このエントリーをはてなブックマークに追加

CodePolaris 2021アドベントカレンダー 2日目の記事です!

以前もPHPカンファレンス北海道の懇親会でお話しした のですが、私は文系の学部で大学を卒業しています。プログラミングの勉強を始めたのは、結婚して子供を生んでからです。大学の情報処理の授業で初歩的なHTMLだけ習っていましたが、1人ボッチで市販の書籍とインターネット上の情報だけでプログラミングを学びました。
フリーランスを経て、2人目の子供が小学校に入学するときに弊社にパートタイムで入社し、現在は社員になってマネージャー兼エンジニアをしています。

女性の独学エンジニアとしては成功ルートの部類だと自分でも思うので、「何をしたのがよかったのか?」を振り返ってお伝えしようと思います。

ポートフォリオを作るなかれ、オープンソースソフトウェアにコントリビュートしよう(自分のライブラリをオープンソースとして公開だとなお良し)

できるだけ安価にプログラミングを勉強しようとしたとき、オープンソースの言語・オープンソースのライブラリから始めることになるでしょう。
SymfonyもLaravelもCakePHPも、EC-CUBEもWordPressも、なんならPHP自体もオープンソースです。つまり、これら全て、ソースが公開されているのです。
大抵のライブラリやフレームワークはREADMEにissueリストへのリンクがついています。自分の学習しているライブラリやフレームワークのREADMEからissueリストに行ってみてください。
issueリストを眺めてみて、 good first issue というラベルがついたissueがあればしめたものです。(初心者向けのissueという意味です) 勇気を出して「これ、私やってみます!」と声を上げましょう。もし行き詰まってしまっても、issueで質問すれば、大抵のメンテナーはサポートしてくれます。(行き詰まって質問する前に、次の項の「エラーメッセージを読もう」を忘れずに!)

やや逆説的ですが、コントリビュートチャンスを見つけるために、各種ライブラリやフレームワークを使い倒して、なにかシステムを開発してみると良いでしょう。その際、システムを完成させることよりも、ライブラリの使い方に習熟すること・バグを見つけるためにやっていることを忘れないように!

いきなり英語でコメントのやりとりが発生する海外のオープンソースに突撃するのはハードルが高いかもしれません。基本的に日本語でやりとりされているEC-CUBEは個人的にオススメなコントリビュート対象です。

エラーメッセージを読もう

プログラム言語のエラーメッセージは英語で書かれていることが多いですが、文法的には単純です。そんなに難しい単語も使われていません。中学・高校の授業で習った程度の英語力があれば十分です。
英語がどうしても苦手な方は、最初はGoogle翻訳やDeepLに入れてみる、でも良いです。
「わかりません」「動きません」の前に目の前に表示されているエラーメッセージを英語として読み解いてみると、意外と自力で解決できることは多いのです。

working out loudを身に着けよう

Working out loudとは? https://workingoutloud.com/ という世界的に認識された仕事のやり方であり、スキルです。
(日本語の記事でわかりやすいのは、こちらのブログ記事 https://blog.studysapuri.jp/entry/2018/11/14/working-out-loud だと思います)
作業を進めながら、わかったこと・わからないこと・できたこと・詰まったこと等をどんどん表明しましょう。
プログラマ・エンジニアとして就職してない場合は、Twitterがオススメです。Twitterという公開の場で呟くのが怖ければCodePolarisのSlackで練習してみてもいいかもしれません。

ユーザーサポートや業務知識を進んで取りに行こう

最後に、エンジニアとして就職した後のことですが、よほど分業がしっかりした大企業でない限り、周囲の先輩エンジニアはユーザーサポートに苦しんでいるかもしれません。また、業務知識(作る対象のシステムで扱うビジネス領域)との折り合いにも苦しんでいることが多いです。
入社時点ではプログラミングの知識や能力ではおそらく周囲のエンジニアと差がある状態だと思います。これらの専門のエンジニアが苦手とする分野を積極的にカバーすることで、皆から一目置かれる存在になることはできます。
私自身、入社時点ではリスティング広告のことをほぼ全く知らなかったのですが、ユーザーサポートと業務知識の仕事を取りに行った結果、開発部の中で誰よりも詳しくなり、今では皆に教える側になっています。

いかがでしたか?私の体験談が駆け出しエンジニアの皆様に少しでもお役に立てば幸いです。
明日は @maiamea さんです!