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

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

composer を再インストール

はじめに

ちょっとした備忘録です。

本題

ローカルで使用していた composerで何故か変なエラーが多発しておりました。

symfonyのcreate-projectを行おうとするとこんなエラー

$ composer create-project symfony/website-skeleton my_project_name
Could not find package symfony/website-skeleton with stability stable.

laravelの create-projectを行うとするとこんなエラー

$ composer create-project --prefer-dist laravel/laravel blog
Could not find package laravel/laravel with stability stable.

cakephpの create-project を行うとするとこんなエラー

$ composer create-project --prefer-dist cakephp/app:~4.0 my_app_name
Could not find package cakephp/app with stability stable.

既存のプロジェクトで composer updateやinstallは実行できる。

phpmacのものを、composerはphpenvのものを使用していたので、一旦 anyenv uninstall phpenv でphpenvを消して、composerも再インストールしたのですが、変わらず同じエラー。

うーん。と思って調べているとそれっぽいの見つけました。

akamist.com

一旦ざっくりと ~/.composer を削除して、再度composerを再インストールしたところ、問題なくcreate-projectできました。

キャッシュなんですかね?

削除前に composer cc を実行したのですが、変わらなかったので単純にキャッシュというわけではなさそうです。

とりあえず治ったので、様子見てみます。

現場からは以上です。

symfony consoleでバージョンエラーが出た対応

はじめに

こんばんは。

備忘録です。

symfony cliをインストールして起こったこと & 対処法です。

本題

まず、symfony cliをインストール後コマンドを実行すると以下のようなエラーが出ました。

$ symfony console

Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 7.4.0". You are running 7.3.11. in /path/to/vendor/composer/platform_check.php on line 24
exit status 255

????????

$ php -v
PHP 7.4.16 (cli) (built: Apr 25 2021 08:08:01) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.16, Copyright (c), by Zend Technologies

????????????????????

phpのバージョンは 7.4.16なのに 7.3.11になってるってどういうことでしょう?

調べてみると、現状ローカルに phpenvを入れて、 7.4にしていて、macに入っていたもともとのバージョンはまさに 7.3.11でした。

もともとのバージョンの方を見られている??

phpenvでバージョン切り替えている意味は??

まぁじゃあいいや。

ということで、macphpバージョン自体をphpenvと同じ7.4.16にあげました。

その後同じく symfony console を実行しても、同じエラーが出ました。

ますます謎は深まります。

色々調べていたら、見つけました。

github.com

どうやら、 ~/.symfony/php_versions_2.json ってのが怪しそうです。

$ cat ~/.symfony/php_versions_2.json
[
    {
        "version": "7.3.11",
        "path": "/usr",
        "php_path": "/usr/bin/php",
        "fpm_path": "/usr/sbin/php-fpm",
        "cgi_path": "",
        "php_config_path": "/usr/bin/php-config",
        "phpize_path": "/usr/bin/phpize",
        "phpdbg_path": "",
        "is_system": false
    }
]%                                                                                         

これが犯人っぽいです。

symfonyコマンドでも発見できました。

$ symfony php -v
PHP 7.3.11 (cli) (built: Jun  5 2020 23:50:40) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies

これを変更するにはどうするの?と思って調べてたら、いいコマンドがありました。

$ symfony local:php:refresh

phpenvもcomposer 入れたときに rehashたたくしこれで行けそうです。

symfony local:php:refresh を実行後再度、 php_versions_2.json を確認すると

$ cat ~/.symfony/php_versions_2.json
[
    {
        "version": "7.3.11",
        "path": "/usr",
        "php_path": "/usr/bin/php",
        "fpm_path": "/usr/sbin/php-fpm",
        "cgi_path": "",
        "php_config_path": "/usr/bin/php-config",
        "phpize_path": "/usr/bin/phpize",
        "phpdbg_path": "",
        "is_system": false
    },
    {
        "version": "7.4.16",
        "path": "/usr/local/Cellar/php@7.4/7.4.16_1",
        "php_path": "/usr/local/Cellar/php@7.4/7.4.16_1/bin/php",
        "fpm_path": "/usr/local/Cellar/php@7.4/7.4.16_1/sbin/php-fpm",
        "cgi_path": "/usr/local/Cellar/php@7.4/7.4.16_1/bin/php-cgi",
        "php_config_path": "/usr/local/Cellar/php@7.4/7.4.16_1/bin/php-config",
        "phpize_path": "/usr/local/Cellar/php@7.4/7.4.16_1/bin/phpize",
        "phpdbg_path": "/usr/local/Cellar/php@7.4/7.4.16_1/bin/phpdbg",
        "is_system": true
    }
]                                                                             
$ symfony console -V
Symfony 5.2.6 (env: dev, debug: true)

エラーが出ず、実行することができました!

終わりに

インストールしたときによしなに見てくれるもんだと思って、無駄にハマってしまいました。

現場からは以上です。

人を変える・人が変わるのは難しい

こんばんは。 最近悩んでいることをブログに殴り書きします。

まとまりないですが、お許しを。

昨今の情勢的に。というわけではないですが、普段からチャットのやり取りが多いんですよね。

その際に僕が心がけていることは、相手に不快感を与えない伝え方・見え方にするってところなんです。

しょうもないですが、具体的には

  1. 絵文字を使う。

  2. 極力柔和な言い方にする。

この2点です。

堅い文章を送る必要があるときは、極力通話します。

チャットってどうしても堅い文章だと、怒っている印象与えちゃうので。

なんで、こんなことを心がけているかというと、以前お仕事いただいていたところで、「チャットでの発言が強い(心ではそう思っていないのに)」ということで、一緒に働いていた方を一時期傷つけた事があるのが原因です。

それ以降は本当に短期で単細胞な僕でも、極力気をつけて送るようにしています。

ただ、これを続けていたところ、妙なことに、他の方のチャットでの発言や、自分が送ったチャットの書き方で、ビクビクするようになってしまいました。

「あ、なんか怒ってるかな...?」

「あ、さっきの伝え方、少し強かったかな...?」

って感じで、少しだけストレス感じるようになりました。

正直そんなに気にする必要ないんですが、小心者の自分なので、しょうがないですね。

まぁ、上の話は、自分が耐えれば全然いいんですが、最近感じているのは他の人の発言です。

普段から強い言い方する人っていますよね。

今お仕事頂いているところでも、います。

どこにでもいますよねw

僕は、その人から直接強い言葉をもらってはいないので、関係ないといえば関係ないんですが、他の人が言われているところをよく見るんですよね。

slackなので、スレッド開いて、消し忘れるとずっと右にスレッドが残っていて、毎回見ちゃうんですよね。

決してその人は、本当に強く言っているわけではないと思うんですよ。

ただただ、素で強くなっているだけだと思うんです。

こういうのって難しいですよね。

だって、僕だけが気にしている、ただの独り相撲なだけかもしれないしw

それに、当人に自覚がなければ、変に責めるのは意味分かんないし。

本当にまとまりがない文章になった。。。。

うーーーーーん。

言いたいことを言い合える環境って本当に大事だと思います。

でも、それって言い方を考えずにズバズバ言うってことではないと思うんですよね。

うーーーーーーん。

まぁつまり、そういうのみてると、あんまりモチベーション上がらないよね。

って話でした。すいません。

現場からは以上です。

php-mysql-engine貢献中...

はじめに

こんばんは。

今回も前回に続き、php-mysql-engineへの貢献を続けております。

なんとかしてcakephp3でも使えるようにという感じで、頑張っております。

本題

今回は、NoticeErrorが起きたのでそちらの対応を行いました。

例えば以下のようなSQLの場合

CREATE TABLE `orders`
(
    `id` INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `user_id` INTEGER(11) UNSIGNED,
    `price` INTEGER(11) UNSIGNED NOT NULL DEFAULT 0,
    `created_on`  timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

このようなNoticeErrorが発生します。

Notice Error: Uninitialized string offset: 0 in [/path/to/vendor/vimeo/php-mysql-engine/src/Parser/CreateTableParser.php, line 936]

問題は以下のコード

github.com

\substr($sql, $source_map[$i][0], $source_map[$i][1]) で、 デフォルトの値が 数値の 0 の場合 三項演算子false 側に倒れるようになっていました。

つまり最初のSQLでも、 DEFAULT 0 ではなく、 DEFAULT '0' であれば通りました。

今回こちらを修正するPRを作成しました。

github.com

終わりに

前回のPRは問題なくマージされましたので、今回も調子に乗って第二弾あげました。

本来は show column用のPRを作る予定だったのですが、たまたま見つかったので、こちらを先にあげました。

マージされたらいいなぁ。

現場からは以上です。

cakephp3とphp-mysql-engineの組み合わせはうまく行かない

はじめに

こんばんは。

php-mysql-engineの話を聞いて、ワクワクしながら試してみました。

結果、cakephp3だとうまく行かないことがわかりました。

検証した環境は、 3.9.8 でした。

本題

結果的にうまく行かなかったのですが、ハマった箇所は色んな場所でした。

最初は ドライバを用意して、FakePDOに書き換えるだけでイケルと思いましたが、甘かったです。

最初に用意したドライバは、以下です。

<?php
namespace App\Test\Driver;

use Cake\Database\Driver\Mysql;
use Vimeo\MysqlEngine\Php7\FakePdo;

class TestMysqlDriver extends Mysql
{
    /**
     * Establishes a connection to the database server
     *
     * @param  string $dsn    A Driver-specific PDO-DSN
     * @param  array  $config configuration to be used for creating connection
     * @return bool   true on success
     */
    protected function _connect($dsn, array $config)
    {
        $connection = new FakePdo(
            $dsn,
            $config['username'],
            $config['password'],
            $config['flags']
        );
        $this->setConnection($connection);

        return true;
    }
}

無理やりクリアしたエラー

1. Exception: The SQL code SET foreign_key_checks = 0 could not be parsed: Unexpected SET in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]

これは FixtureManager.phpload() を実行したした際に内部で実行される _runOperation() の中の以下の

disableConstraints() が実行しているSQLです。

    /**
     * {@inheritDoc}
     *
     * ### Example:
     *
     * $connection->disableConstraints(function ($connection) {
     *   $connection->newQuery()->delete('users')->execute();
     * });
     */
    public function disableConstraints(callable $operation)
    {
        return $this->getDisconnectRetry()->run(function () use ($operation) {
            $this->disableForeignKeys();

            try {
                $result = $operation($this);
            } catch (Exception $e) {
                $this->enableForeignKeys();
                throw $e;
            }

            $this->enableForeignKeys();

            return $result;
        });
    }

外部キー制約のチェックを外すというSQLです。

幸い実行しているのが MysqlDriverが use している MysqlDialectTrait というTraitクラスだったので、 MysqlDriverをextendsしている今回の独自ドライバで上書き可能でした。

なので一旦、用意したドライバで、通りそうなSQLを用意してお茶を濁しました。

    /**
     * {@inheritDoc}
     */
    public function disableForeignKeySQL()
    {
        return 'SELECT "1"';
    }

2. Exception: The SQL code SET foreign_key_checks = 1 could not be parsed: Unexpected SET in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]

これも同じく、 disableConstraints() が内部で実行しているSQLです。

さっきの逆ですね。

これも同じようななんでもないSQLをドライバで用意しました。

    /**
     * {@inheritDoc}
     */
    public function enableForeignKeySQL()
    {
        return 'SELECT "1"';
    }

3. Exception: The SQL code SHOW TABLES FROM example_database could not be parsed: Parser error: expected SHOW TABLES LIKE in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]

php-mysql-engineでは SWOW TABLESで全テーブル取得を許可していないようです。 必ずLIKEで絞ることが条件になっていました。

しかし、 FixtureManager.phpload() で呼ばれている listTables() が SHOW TABLES を実行していました...

これは、 実際には vendor/cakephp/cakephp/src/Database/Schema/Collection.php が実行しています。

そしてMysqlの場合は MysqlSchema が該当のSQLを持っています。

    /**
     * {@inheritDoc}
     */
    public function listTablesSql($config)
    {
        return ['SHOW TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']), []];
    }

MysqlSchemaMysqlDialectTraitschemaDialect() というメソッドで _schemaDialect というプロパティが何もセットされてない場合、初期値としてセットされ、以降それが使用されます。

なので、作成した新たなドライバのコンストラクタで 事前に _schemaDialect プロパティに独自のSchemaクラスをセットすることで回避できました。

    /**
     * Constructor
     *
     * @param  array                     $config The configuration for the driver.
     * @throws \InvalidArgumentException
     */
    public function __construct($config = [])
    {
        parent::__construct($config);
        $this->_schemaDialect = new TestMysqlSchema($this);
    }
<?php
namespace App\Test\Driver;

use Cake\Database\Schema\MysqlSchema;
use Cake\Database\Schema\TableSchema;

class TestMysqlSchema extends MysqlSchema
{
    /**
     * {@inheritDoc}
     */
    public function listTablesSql($config)
    {
        return ['SHOW TABLES LIKE "%%"', []];
    }
}

4. Warning Error: PDO::getAttribute(): SQLSTATE[00000]: No error: PDO constructor was not called in [/path/to/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php, line 174]

PDO::ATTR_SERVER_VERSION を実行する部分で、エラーが警告が起きていました。

        if ($this->_version === null) {
            $this->_version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
        }

ここも versionを設定しておけばとりあえず回避できると思い、 先ほど作成した コンストラクタで $this->_version = 5.7 という感じで決め打ちで指定しました。

5. Warning Error: Fixture creation for "users" failed "No default collation or character set given" in [path/to/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php, line 312]

collationやcharacter setの指定が create table文にないことで警告が出ており、その後のテーブル作成が失敗していました。

charsetのオプションがfixtureクラスにあれば、このエラーは出ないと思いますが、試したプロジェクトの場合は全fixtureになかったので、全部のテストで同じエラーがでました。

今回の場合は、とりあえず utf8 に設定できればいいので、TestMysqlSchemaで createTableSql() をオーバライドして対応しました。

    /**
     * {@inheritDoc}
     */
    public function createTableSql(TableSchema $schema, $columns, $constraints, $indexes)
    {
        $contents = parent::createTableSql($schema, $columns, $constraints, $indexes);
        $contents[0] .= ' DEFAULT CHARACTER SET = utf8';

        return $contents;
    }

無理矢理は無理だったけど、なんとかなりそうなエラー

1. Warning Error: Fixture creation for "users" failed "Column type INTEGER not recognized" in [/path/to/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php, line 312]

create table文でint型のカラムがあるばあい、intは通るけどintegerは通らないという感じでした。

これに関しては、PRだしてみました。

github.com

もし通らなかったら、先程オーバライドした createTableSql 内で置換する感じで対応はできそうです。

どうにもならなかったエラー

1. UnexpectedValueException: The SQL code SHOW FULL COLUMNS FROM users could not be parsed: Parser error: expected SHOW TABLES

cakephpでは、find()やselect()を実行したときに、 内部でgetSchemaが呼ばれて、上記の SHOW FULL COLUMNSが実行されます。

php-mysql-engineは SHOW FULL COLUMNS も SHOW COLUMNSも非対応なので、これはどうにもなりませんでした...

終わりに

ワクワクしながら頑張ったのですが、結局ダメでした。。。

参考サイトのスライドでは cakephp4.2.4を使っているとのことなので、4系ではうまくいくのかもしれません 。

laravelとかはどうなんだろう?試してみようかな。

現場からは以上です。

Vue3超入門 vol.1

はじめに

こんばんは。

vue3の入門を頑張ってやってみます。

参考書籍は Web+DB Press vol. 120の 「最新Vue.js3入門」です。

今回は Composition APIです。

参考サイト

本題

さっそくやってみた

Composition APIはざっくり、関数ベースでコンポーネント定義ができる機能のようです。

今までのコンポーネント定義(Options API)とは違い、 setup()というメソッド内に機能を記述していく形です。

Composition APIで作った場合
<template>
  <div>
    <label>名字:</label>
    <input type="text" v-model="state.lastName" />
  </div>

  <div>
    <label>名前:</label>
    <input type="text" v-model="state.firstName" />
  </div>

  <div>
    <label>注文個数(単価100円): </label>
    <input type="number" v-model="state.amount" />
  </div>

  <div>
    <label>合計: </label>
    <span>{{ totalFormat }}</span>
  </div>

  <div>
    <button type="button" @click="onClick">submit!!</button>
  </div>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  name: "CompositionExample",
  setup() {
    const state = reactive({
      lastName: '',
      firstName: '',
      amount: 0
    })
    const totalFormat = computed(() => (state.amount * 100).toString().replace(
        /(\d+?)(?=(?:\d{3})+$)/g,
        function (x) {
          return x + ',';
        }))
    const onClick = () => {
      alert(`${state.lastName} ${state.firstName}さん。購入ありがとうございました。`)
    }
    
    return {
      state,
      totalFormat,
      onClick,
    }
  }
}
</script>

<style scoped>

</style>
Options APIで作った場合
<template>
  <div>
    <label>名字:</label>
    <input type="text" v-model="lastName" />
  </div>

  <div>
    <label>名前:</label>
    <input type="text" v-model="firstName" />
  </div>

  <div>
    <label>注文個数(単価100円): </label>
    <input type="number" v-model="amount" />
  </div>

  <div>
    <label>合計: </label>
    <span>{{ totalFormat }}</span>
  </div>

  <div>
    <button type="button" @click="onClick">submit!!</button>
  </div>
</template>

<script>
export default {
  name: "OptionsApiExample",
  data() {
    return {
      lastName: '',
      firstName: '',
      amount: 0
    }
  },
  methods: {
    onClick() {
      alert(`${this.lastName} ${this.firstName}さん。購入ありがとうございました。`)
    }
  },
  computed: {
    totalFormat() {
      return (this.amount * 100).toString().replace(
          /(\d+?)(?=(?:\d{3})+$)/g,
          function (x) {
            return x + ',';
          }
      );
    }
  }
}
</script>

<style scoped>

</style>

ざっくり作ったんですが、こんな感じの違いっぽいです。

setup()内に全部集約されている感じですね。

reactive の他に ref もあるみたいです。

どちらもリアクティブな値に出来るようですが、どちらを使うのが良いのでしょうか?

本読んだ感じ、ざっくり、 reactive が複数 ref が単数という感じでした。

その他にも、 refで作成した値は、 script 内では、 ○○.value という感じで呼び出して、 template 側では ○○ という感じでアクセスできます。

これはなにが嬉しいの?

例えば

「こっちのコンポーネントの、このデータ、もしくは機能をこっちのコンポーネントでも使いたい!!!」

みたいなニーズって、大規模になればなるほどでてくると思います。

これ、従来だと、なんとか親子関係にしたり、vuexとかつかって、表現したりするのでしょうか?

これが、関数ベースのComposition APIの場合は、注入しやすくなるみたいです!

リアクティブなデータをパーツパーツで細かく分けて、必要とするコンポーネントに注入できるという感じでした。

終わりに

Compositon API なかなかおもしろいですね。

ただ、設計が柔軟にできるようになる代わりに、設計が大事になってくる感じですね。。。

次は、teleport調べてみます。

現場からは以上です。

cakephp3でeventを使っている際のテストコード

はじめに

こんばんは。 またまたcakephp3記事です。

もう cakephpver4.2.4とかなのですが、現場ではまだcakephp3を使用しているため致し方なし。という感じです。

今回は cakephpのevent機能を使用している際のテストコードです。

前回 eventの使用方法などは書いたのですが、使用している箇所のテストコードを今回書いたので、備忘録として残しておきます。

kojirooooocks.hatenablog.com

本題

前回使用した ExampleControllerのテストコードを書きます。

今回はわかりやすく UserContollerにして、actionも registerにします。

使用されているeventは NotificationEventListener です。

今回は少しだけ具体的に NotificationEventListener の処理を書きます。

ExampleController

<?php
namespace App\Controller;

use App\Repository\UserRepository;
use App\EventListener\NotificationEventListener;
use App\EventDispatcher;
use Kojirock5260\Component\Adapter\SlackAdapter;
use Kojirock5260\User\UseCase\RegisterUser;
use Kojirock5260\User\Exception\RegisterUserException;

class UserController extends AppController
{
    public function registerAction()
    {
        try {
            $slackConfig = Configure::read('Slack');
            $this->getEventManager()->on(new NotificationEventListener(new SlackAdapter($slackConfig)));
            $userId = (new RegisterUser(
                new UserRepository($this->UsersTable),
                new EventDispatcher($this->getEventManager())
            ))($this->request->getData('userData'));
            $this->Flash->error('新規登録ありがとうございます。');
        } catch (RegisterUserException $e) {
            $this->Flash->error('ユーザー登録が失敗しました。');
        }
    }
}

NotificationEventListener

<?php
namespace App\EventListener;

use Cake\Event\EventListenerInterface;

class NotificationEventListener implements EventListenerInterface
{
    private $slackAdapter;

    public function __construct(SlackAdapter $slackAdapter)
    {
        $this->slackAdapter = $slackAdapter;
    }

    public function implementedEvents(): array
    {
        return [
            'Notification.Slack' => 'notificationSlack',
        ];
    }

    public function notificationSlack($event, array $data): void
    {
        $this->slackAdapter->send('ユーザー新規登録!', $data['params']);
    }
}

本来はもっと複雑な処理があると思いますが、一旦はこんな感じにします。

この ExampleControllerをテストしたい場合、slackに通知するeventをmockしたくなります。

その場合は、以下のように書きます。

ExampleControllerTest.php

<?php
namespace App\Test\TestCase\Controller;

use Cake\Event\Event;
use Cake\Event\EventManager;
use Cake\TestSuite\IntegrationTestCase;

class UserControllerTest extends IntegrationTestCase
{
    /**
     * @test
     */
    public function register()
    {
        EventManager::instance()->on('Notification.Slack', function (Event $event) {
            $slackAdapterMock = $this->createPartialMock(SlackAdapter::class, ['send']);
            $slackAdapterMock->method('send')->willReturn(true);
            $event->getSubject()->slackAdapter = $slackAdapterMock;
        });

        $this->enableCsrfToken();
        $this->post('/user/register', [
            'email' => 'hogehoge@fuga.com',
                'name'  => 'kojirock'
        ]);

        $flashData = $this->_requestSession->read('Flash.flash.0');
        self::assertSame('Flash/success', $flashData['element']);
    }
}

これで、slackAdapterのsend()メソッドを実行せずそれ以外のコードが実行されるようになります。

終わりに

cakephp4にあげて、早くDIの恩恵を受けたいです。。 このテストコード関連でも、やっぱり外から注入できないとmockがとてもつらいことになってしまってて、大変です。

現場からは以上です。