もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

今年の振り返り

はじめに

こんにちは。

今年もあと僅かになりましたので、今年の活動の振り返りを行いたいと思います。

本題

年始に立てた目標

kojirooooocks.hatenablog.com

立てた目標としては以下。

  • ブログ数は年間76件を目指す
  • アドベントカレンダー参加数は4件を目指す
  • OSSの貢献数12件を目指す
  • 積本を12冊読破する
  • LTを2回以上行う
  • 積ゲームを12本クリアする
  • 10kgの減量を行う

それぞれ目標達成したか見てみます。

ブログ数は年間76件を目指す

f:id:kojirooooocks:20211218011609p:plain

合計51件。

この記事足して52件。

目標未達!

しかも、積本消化のログとかが多かったからそんな中身のある内容じゃなかったなぁ。

アドベントカレンダー参加数は4件を目指す

アドベントカレンダー参加数は0件で目標未達

OSSの貢献数12件を目指す

このあたりのPRしか出せず合計2件で目標未達

kojirooooocks.hatenablog.com

積本を12冊読破する

この他にも読んでますが合計6冊で目標未達...

LTを2回以上行う

0件で目標未達。

積ゲームを12本クリアする

3, 4本くらいしかクリアできず目標未達

10kgの減量を行う

減ったり増えたりを繰り返して、結局72kg

目標未達。

終わりに

というわけで、全て未達という最低な結果になりました...

9月10月くらいから仕事とプライベートがすごく忙しくなったので、だめでした。。

うーん。

来年はもう少しやる気出してやりたい。

現場からは以上です。

MacOS Montereyでは5000番ポートは使われている

こんばんは。

ちょっとだけハマったので備忘録です。

ついこの間 OSをMontereyにあげたのですが、今までハマらないところでハマってしまいました。

現在とある案件でfirebase emulator を使っているのですが、そのさいHostingしているポートが5000番でして、 Big Surのときは問題なかったのですが、Montereyにあげたあとからエラーが出始めました。

⚠  hosting: Port 5000 is not open on localhost, could not start Hosting Emulator.
⚠  hosting: To select a different host/port, specify that host/port in a firebase.json config file:

使ってないだろ?と思って調べてみました。

$ lsof -i:5000
COMMAND   PID         USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ControlCe 508 xxxxxxx   38u  IPv4 xxxxxxxxxxxx      0t0  TCP *:commplex-main (LISTEN)
ControlCe 508 xxxxxxx   39u  IPv6 xxxxxxxxxxxx      0t0  TCP *:commplex-main (LISTEN)

むむ。確かに使われている。

OS上げる前は問題なかったからOSあげたからだめなんか?ということで調べたら出てきました。

developer.apple.com

AirPlayの機能が5000番使ってるらしいです。

以下のチェックを外せば問題なく起動できます。

f:id:kojirooooocks:20211212005626p:plain

幸い僕は使ってないので、これのチェックを外して問題なく動くようになりました。

よかったよかった。

cakephpのバージョンアップ実行

はじめに

こんにちは。

現在複数案件を抱えておりまして、かつ、プライベートもとても忙しく、状況で若干疲弊気味です。。。

今回は現在お世話になっている案件のcakephpのバージョンアップを行いました。

長期間運用中のサービスのため、若干辛めなコードが点在しており、FWのバージョンアップには、若干消極的ぎみだったのですが、重い腰を上げてバージョンアップしました。

マイナーバージョンのバージョンアップですけどね...w

今回は3系の最新バージョンである 3.10.1 にあげることにしました。

ちなみに現在のバージョンは以下のとおりです。

$ ./bin/cake version
3.6.15

本題

基本的に問題なく動きました。

ただ、一点、バージョン 3.7 にあった以下の振る舞いの変更に引っかかりました。

Cake\Database\Type\IntegerType は SQL を生成しデータベースの結果を PHP の型に変換するときに 値が数値ではない場合に例外を発生させるようになります。

github.com

database的にリレーション制約をつけていないけど、cake側でリレーション関係を表しているような場合、join後nullになるようなパターンがあったりします。

たとえば、area_id というカラムには area.idが入るのですが、使われない場合に0に登録しておくみたいな感じです。

バッドパターンではありますが、何箇所かそのような部分があります。

このような場合、文字列の null が来るようになり、上記の is_numericに引っかかりエラーになりました。

3.6以前は 下記のように is_scalar でチェックしていたため問題なかったようです

github.com

こちらは、OldIntegerTypeを作り、各カラムのタイプを変更しました。

<?php

namespace App\Model\Type;

use Cake\Database\Type\IntegerType;

class OldIntegerType extends IntegerType
{
    protected function checkNumeric($value)
    {
        if (!is_scalar($value)) {
            throw new \InvalidArgumentException(sprintf(
                'Cannot convert value of type `%s` to integer',
                getTypeName($value)
            ));
        }
    }
}

これだけで動くようになりました。

ドキュメント見る限りは破壊的な変更は特になく、その他の緩やかな変更も問題ありませんでした。

終わりに

今回はPHPのちょっとした記事になりました。

ちょっとプライベートが忙しすぎてアドベントカレンダーをキャンセルするという悪行をしてしまいました。。。 まじで反省しています。

現場からは以上です。

psalmを試す

はじめに。

こんばんは。

今回は以下の本で紹介されていた psalm を試してみます。

本題

勢いでsymfonyアドベントカレンダーに登録してしまったんで、symfonyプロジェクトでちょろっとやってみようと思います。 (symfonyは特に使いませんが...)

1. install

$ symfony new --full psalm_example --version lts
$ cd psalm_example
$ ./bin/console -V
Symfony 4.4.33 (env: dev, debug: true)

$ composer require --dev vimeo/psalm

$ ./vendor/bin/psalm --init

2. 設定ファイル作成

<?xml version="1.0"?>
<psalm
    errorLevel="1" ← 1に変更
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
            <file name="src/Kernel.php" /> ← 追加
        </ignoreFiles>
    </projectFiles>
</psalm>

3. わざとエラー出してみる

<?php

namespace Kojirock\PsalmExample;

class Example
{
    /**
     * 数値を文字列に変換する
     * @param int $num
     * @return string
     */
    public static function example1(int $num): string
    {
        return (string)$num;
    }
}

$example1Result = Example::example1(100);
Example::example1($example1Result);
$  ./vendor/bin/psalm 
Scanning files...
Analyzing files...

ERROR: InvalidScalarArgument - src/Controller/SampleController.php:18:27 - Argument 1 of Kojirock\PsalmExample\Example::example1 expects int, string provided (see https://psalm.dev/012)
        Example::example1($example1Result);

int型を期待している引数に文字列型を渡してるのでエラーになりました。

// 連番の添字の配列として指定
/** @var non-empty-list<int> $example3 */
$example3 = Example::example3();
if (!$example3['example']) {
    $example3['example'] = 'aaa';
}


ERROR: InvalidArrayOffset - src/Controller/SampleController.php:20:14 - Cannot access value on variable $example3 using offset value of 'example', expecting int (see https://psalm.dev/115)
        if (!$example3['example']) {

連番の配列として指定しているのに連想配列的に扱っていることでエラーになりました。

/** @var array{0: int, 1: string} $example3 */
$example3 = Example::example3();
$example3[0] = 1;
$example3[1] += 2;
$ ./vendor/bin/psalm 
Scanning files...
Analyzing files...

E

ERROR: InvalidOperand - src/Controller/SampleController.php:20:9 - Cannot perform a numeric operation with a non-numeric type string (see https://psalm.dev/058)
        $example3[1] += 2;

配列の1番目は文字列型に指定しているのに、足し算をしようとしてエラーになりました。

$this->plus(1, 9);

/**
 * @param 1|2|3 $a
 * @param 4|5|6 $b
 * @return int
 */
private function plus(int $a, int $b): int
{
    return $a + $b;
}
$ ./vendor/bin/psalm 
Scanning files...
Analyzing files...

E

ERROR: InvalidArgument - src/Controller/SampleController.php:21:24 - Argument 2 of App\Controller\SampleController::plus expects 4|5|6, 9 provided (see https://psalm.dev/004)
        $this->plus(1, 9);

4〜6までの数値を期待していたのに、9が渡されたためエラーになりました。

終わりに

クラスとかメソッドとかも色々できるみたいですが、そのあたりはphpStormで開発してたらなんとかなりそうな気がしました。

ただ、比較的レガシーなコードや、色んな人が触ってジェンガみたいになっているコードなど、そういったきつめのコードに対して、 psalm を使って少しずつ紐を解きほぐすようなことができるかもと思いました。

やっぱりそういうコードは配列が多用されてたりするから、そこでは結構旨味ありそうだと感じました。

現場からは以上です。

phpenvでphp8をMacにインストール

はじめに

以下の本で紹介されている psalm を試すために php8をインストールしました。 なんか変なところにつまったので、一旦備忘録残しておきます。

参考サイト

anyenv + phpenvでPHP8をインストールした作業メモ

本題

まずは必要なライブラリをbrewで入れます。

$ brew install pkg-config bzip2 libjpeg libiconv tidy-html5 libzip autoconf

これでOKっぽいので、以下のコマンドでinstallします。

$ PHP_BUILD_CONFIGURE_OPTS="--with-bz2=/usr/local/opt/bzip2 --with-iconv=/usr/local/opt/libiconv" phpenv install 8.0.9

[Preparing]: /var/tmp/php-build/source/8.0.9
[Compiling]: /var/tmp/php-build/source/8.0.9

-----------------
|  BUILD ERROR  |
-----------------

Here are the last 10 lines from the log:

-----------------------------------------
/var/tmp/php-build/source/8.0.9/ext/openssl/openssl.c:6393:6: warning: passing 'const struct rsa_st *' to parameter of type 'RSA *' (aka 'struct rsa_st *') discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
                                        EVP_PKEY_get0_RSA(pkey),
                                        ^~~~~~~~~~~~~~~~~~~~~~~
/usr/local/Cellar/openssl@3/3.0.0_1/include/openssl/rsa.h:289:29: note: passing argument to parameter 'rsa' here
                       RSA *rsa, int padding);
                            ^
108 warnings and 1 error generated.
make: *** [ext/openssl/openssl.lo] Error 1
make: *** Waiting for unfinished jobs....
3 warnings generated.
-----------------------------------------

opensslで系でなんかエラーが出ました。

brew list してみると、 openssl@1.1 openssl@3 がありました。

そういえば、以前の記事で openssl@3がインストールしたのですが、それが原因なのかもしれません。。。

kojirooooocks.hatenablog.com

これがだめなのかな...?

なので、一旦 openssl@3 をアンインストールしてみます。

brew uninstall openssl@3

ただし、これだけだとまだ上記のエラーが発生していました。

続けて以下の記事を試しました。

qiita.com

これできれいにインストールすることができました!

無駄にハマったー。

zipcloakでパスワードかけたzipファイルをfinderから解凍しようとしたらできない...!

こんばんは。

最近 macクリーンインストールしました。

ずっとタイムマシーンから復活してばっかりしていたので、完全に新規でインストールしたかったのです。

そのさい ~/.aws/ とか ~/.ssh/ 系を一旦zipに固めて iCloudとかに移動させておきました。

なんとなく、本当になんとなくそのまま移行させるの嫌だったので、zipcloakでパスワードかけて iCloudに持っていてクリーンインストールをしました。

インストール後、環境を復活させようと思って、諸々復活させて、最後にパスワードかけたzipたちを持ってきて、finderからダブルクリックで解凍しようとしたら、、、、

なんと解凍できなかったんです....!!!!!

「パスワード間違ったのか...?」

と思って、いつも使うパスワードの、よく間違えそうなミスを何個も試しましたが、全然通らない。

多分30回くらい試したんですが全然通らないので、諦めようと思ったのですが、こうなったらパスワード突破してやると思い、以下とかを参考にツールを入れて頑張ってみました。

qiita.com

www.iestudy.work

結論として、この方法はうまくいきませんでした....

まず ./configure 時にopensslでのエラーが発生しました。

ただし、こちらの対処は openssl@3brew installして無事コンパイルすることができました。

次に コンパイルzip2john を使ってhash値を作成しようと思ったのですが、、、

実行すると、自分が使用している ESET Cyber Security Proが coinminer を検出しました。

github.com

よくわかんないのですがこれがそうなのかな??

ちょっと怖いので使うの断念しました....

諦めようかな。。と思ったのですが最後の最後に unzip で解凍したら、、なんと解凍できました...

3〜4時間くらいハマってしまったので、辛かったです....

未来の自分に一言。

zipcloak 信用するな

Next.js 入門

はじめに

こんばんは。

今日の積本はこちら。

その中から Next.jsの勉強をしました。

こレに習って、自分が勉強した部分を備忘録で残しておきます。

本題

1. Next.jsとは

Reactベースのwebフレームワーク。

主要な技術はこちら

  • React
  • TypeScript
  • webpack

SPAだけではなくマルチページにも対応している。

対応出来るニーズが広いため、その分設計ノウハウが必要になる。

HTMLの作成方法

Next.jsは大きく分けて以下のHTML作成手段を提供している。

それぞれ長所短所あるが、短所はキャッシュ等である程度カバーできる (逆にキャッシュで長所を消してしまう可能性もある)

1. Server Side Rendering(SSR)

リクエストのたびにサーバー側でHTMLを構築する

長所

常に最新の情報を返せる

短所

毎回レンダリングするのでサーバに負荷を与える

2. Static Site Generation(SSG)

事前にHTMLをビルドしておき、ビルド済みのHTMLを返す

長所

すでにHTMLが作られているのでサーバー負荷が低い

短所

HTMLを更新したい場合は再ビルドの必要があるので、更新頻度が高いサービスは不向き

3. Client Side Rendering(CSR)

必要最小限のHTMLのみかえして、その後APIでコンテンツを取得してHTML要素をレンダリングして構築する

長所

SSR同様最新の情報を取得できる

短所

APIで情報を取得後HTMLが構築されるまで何も表示されない

JSファイルが大きくなる傾向がある

2. セットアップ

A. Vercelで実行環境を構築する

サインアップ後 スターターキットで Next.js を選ぶ。

f:id:kojirooooocks:20211102024950p:plain

レポジトリの名前を決めて create

f:id:kojirooooocks:20211102025002p:plain

Create Teamは自分ひとりなので skip

f:id:kojirooooocks:20211102025011p:plain

Buildを待つと出来上がるのでその後ダッシューボードへ

f:id:kojirooooocks:20211102025023p:plain

その後 Visit を押下するとデプロイされたアプリケーションを表示できる。

f:id:kojirooooocks:20211102025034p:plain

B. ローカル環境構築

作成された git repositoryを cloneしてnodeのバージョンを合わせる。

本書ではnodeのバージョンが 14.17.0 だった。

自分のローカルのバージョンは以下だったので、nodeenvでバージョンを合わせるようにした。

$ node -v
v16.11.0


$ nodenv install 14.17.0
Downloading node-v14.17.0-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v14.17.0/node-v14.17.0-darwin-x64.tar.gz
Installing node-v14.17.0-darwin-x64...
Installed node-v14.17.0-darwin-x64 to /path/to/.anyenv/envs/nodenv/versions/14.17.0


$ nodenv versions  
  14.17.0
* 16.11.0 (set by /path/to/.anyenv/envs/nodenv/version)


$ nodenv local 14.17.0


$ node -v
v14.17.0

その後 npm i で各種ライブラリをインストール後 npm run dev で開発サーバを立ち上げて、ブラウザアクセスが可能になる

f:id:kojirooooocks:20211102025049p:plain

C. Next.jsのファイル構成

$ tree -L 3 .
.
├── README.md
├── next.config.js
├── node_modules
│   ├── @babel
│   ├── ...
│   ├── ...
│   ├── ...
│   └── yocto-queue
├── package-lock.json
├── package.json
├── pages
│   ├── _app.js
│   ├── api
│   │   └── hello.js
│   └── index.js
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── yarn.lock
ルーティング

pages/ 以下にファイルを配置していくことでルーティングが決定する。

例)

  • pages/users/index.js => /users
  • pages/users/profile.js => /users/profile
  • pages/users/[id].js => /users/{id}
API

pages/api/ 配下にリクエストハンドラーをセットすることでAPIが提供できる。

const handler = (req, res) => {
  const body = { message: "プロフィールだよ"}
  res.statusCode = 200;
  res.json(body)
};

export default handler

f:id:kojirooooocks:20211102025103p:plain

D. srcディレクトリは勝手に識別してくれる

特に設定を書かなくても src は勝手に認識してくれる。

また、 pages ディレクトリは src/ 以下に配置しても認識してくれる。

E. TypeScript化が容易である

TypeScriptも簡単に導入可能。

以下のコマンドを実行し、TSをインストールする。

$ npm i -D typescript @types/react

pages/index.js pages/_app.js の拡張子を .tsx に変更する。

その後ビルドし直すと next-env.d.ts tsconfig.json が作成される。

これで導入が完了。

3. 静的サイトの生成

Next.jsはページの特性に合わせてSSG or SSR or CSR などを切り替えることが出来る。

本書のP53ページに記載されているフローチャートが、とても理解しやすく、最適解の助けになるものだった。

ぜひ一読してもらいたい。

A. 今までの課題

今までの SSG の課題は2つ

  • ページ量にSSGのビルド時間が比例して大きくなる
  • データ更新の都度、ビルドするという燃費の悪さ

Next.jsはこの弱点を Incremental Static Regeneration(ISR)というしくみで克服している。

B. 静的生成の際に使用されるAPI

Next.jsのページ生成の静的生成において、非常に重要なAPIが以下。

  • getStaticProps

ページの静的生成に外部からデータを取得して、page componentにpropsとして渡すことが出来る。

ページ生成に外部からデータを取る必要がない場合は定義の必要なし。

/**
 * 静的生成時にデータの取得・出力をする
 * @param context 
 * @returns 
 */
export async function getStaticProps(context) {
  const res = await fetch('https://.../users')
  const users = await res.json();
  return { props: { users }}
}
  • getStaticPaths

ページの静的生成時に動的ルートを取得・出力できる

users/{id} のように動的なURLでも 静的生成可能。

つまり事前に全ルートのファイルを事前作成する。

/**
 * params.idに1 or 2を渡す
 * これで、getStaticPropsがそれを扱えるようになる
 * @returns 
 */
export async function getStaticPaths() {
  return {
    paths: [
      { 
        params: { id: 1 }
      },
      { 
        params: { id: 2 }
      }
    ],
    fallback: false
  }
}
export async function getStaticProps({ parmas }) {
  // params.idに1 or 2がはいる
  const res = await fetch(`https://.../users/${params.id}`)
  const user = await res.json();
  return { props: { user }}
}

決め打ちの数値だけではなく getStaticPaths() でもfetchを使って、対象のID群を抜き出して渡すことも可能。

export async function getStaticPaths() {
  const res = await fetch(`https://.../items`)
  const items = await res.json()
  return {
    paths: items.map((item) => ({ params: { id: item.id }})),
    fallback: false
  }
}

ただ、上記のような取得方法だと id が何万件もあるようなものでは、更新されるたびに作るという作業が非効率で時間もかかりすぎる。

Next.jsはこの解決法に「段階的に静的生成する」というアプローチを取る。

それが、 fallback: true

C. fallback: true

fallback: true を指定すると、事前生成されたページが見つからない場合、そのページを生成する。

事前に生成するのではなく、リクエストが来た段階でファイルの存在を判定し、なければ生成するという形。

当然あれば、それを返す。これで大量のページが存在するサイトでも、すべてをビルド時に生成する必要がなくなる。

D. fallback: 'blocking'

fallback: true のデメリットとして、ファイルが存在せずページを生成するフローの場合、バックグラウンドで該当ページの生成が終わるまでは、空のHTMLが返ることになる。

ユーザーの表示的にはよく見るローディングページなどになると思う。

生成が完了したらJSで表示の更新が行われる。これを起こさせないのが、 fallback: 'blocking'

fallback: 'blocking' を指定すると、ファイルが存在しないページへのリクエストの場合、最初にからのHTMLを返すことを許さない。

バックグラウンドのページ生成が終わるまでHTML表示を待たせることになる。

SEO対策が必要なページに適している。

この fallback に関しても本では図解でわかりやすく解説されていたので、ぜひ読んでほしい。

E. Incremental Static Regeneration

今までの中で、まだ解決されてない問題が以下

一度生成されたページをどうやって更新するか

これを解決するのは、 getStaticProps()revalidate オプション

export async function getStaticProps({ parmas }) {
  // params.idに1 or 2がはいる
  const res = await fetch(`https://.../users/${params.id}`)
  const user = await res.json();
  
  // revalidate = 再生成をする間隔(秒数 = 下記だと100秒)
  return { props: { user }, revalidate: 100 }
}

これによりページ更新のためにアプリケーション全体を再ビルドしなくて良くなった。

4. 静的生成に取り組む下準備

第5章以降でGithubAPIを題材に取り組む前の下準備が紹介されている。

  1. Githubのtoken取得
  2. GithubAPIを扱うための Octokit をインストール
  3. fetch用のロジックを作成
  4. 実際にAPIを実行して取得してみる
  5. Github OAuth Appのセッティング

実際にAPIを実行して取得してみる

import { octokit } from "@/utils/fetcher"
import { InferGetStaticPropsType } from "next"

export type PageProps = InferGetStaticPropsType<typeof getStaticProps>

export const getStaticProps = async () => {
  const repos = await octokit.request(
    "GET /users/{username}/repos", 
    { username: 'kojirock5260' }
  )
  return { props: { repos }}
}

export default function Page(props: PageProps) {
  console.log(props.repos.data.map(v => {
    const { name, full_name } = v
    return { name, full_name }
  }));
  return <div>Hellow Next.js</div>
}

f:id:kojirooooocks:20211103031227p:plain

Github OAuth Appのセッティング

f:id:kojirooooocks:20211103031242p:plain

5. 実際に作ってみる

本に記載されている githubのコードを写経して作成してみた。

トップページ

f:id:kojirooooocks:20211106020621p:plain

ユーザー検索結果

f:id:kojirooooocks:20211106020617p:plain

自分のページ(認証前)

f:id:kojirooooocks:20211106020607p:plain

自分のページ(認証後)

f:id:kojirooooocks:20211106020603p:plain

commit一覧

f:id:kojirooooocks:20211106020600p:plain

一部TSの型エラーが出ていたので、制約を緩めれば今でもこの本のコード通りに動くと思われる。

終わりに

初期設定はとても簡単なものでした。

また、 fallbackrevalidate なども実際本記載のサンプルアプリケーションを使って動きを確認できて、理解を深めることが出来ました。

react自体もあまりやったことないので、今度はreactの本も勉強してみようと思いました。

現場からは以上です。