Ubuntuにfish shellをいれる

導入環境

  • Ubuntu: 20.04.1 LTS
  • fish: 3.1.0

導入手順

  • fishをインストール
    • sudo apt install fish
  • fishをデフォルトシェルに設定
    ~$ cat /etc/shells 
    # /etc/shells: valid login shells
    /bin/sh
    /bin/bash
    /usr/bin/bash
    /bin/rbash
    /usr/bin/rbash
    /bin/dash
    /usr/bin/dash
    /usr/bin/fish
    ~$ chsh
    パスワード: 
    ------ のログインシェルを変更中
    新しい値を入力してください。標準設定値を使うならリターンを押してください
    ログインシェル [/bin/bash]: /usr/bin/fish
  • ログインし直すとfish shellがデフォルトshellに
    Welcome to fish, the friendly interactive shell
    Type `help` for instructions on how to use fish
    ~> 
  • fish_configでカスタマイズ
    • fish_config とshellで叩くとブラウザで設定を変更できる
  • fontをインストール
    • sudo apt install fonts-powerline
    • font を DejaVu Sans Mono for Powerline Book に変更 ※重要
  • fishermanを入れる
    • プラグイン管理ツール 公式
    • curl https://git.io/fisher --create-dirs -sLo ~/.config/fish/functions/fisher.fish
    • curlしようとしたらcurlがなかったので sudo apt install curl
    • 今回は特にプラグインいれてNothing
  • せっかくなのでターミナルも変更してみる

    • tilixなるターミナルが良いと聞いた
    • sudo apt install tilix
    • これはいいかもしんないわね!!!!!!
  • 最終的にこんな感じ f:id:matsup8:20200912011454p:plain

エラー

  • PhpStormのターミナルでエラー発生
    • error: Unable to open universal variable file '/snap/phpstorm/178/plugins/terminal/fish/fish_variables'
    • fish shellにしてからPhpStormのターミナルにめちゃくちゃ出ている
    • snapでinstallしたPhpStormの場合出現する…?
    • Fish user functions not loaded
    • PhpStormの Setting > Tools > Terminal で Shell Integration を外せ!
      とのことだったので外したらエラーは消えた
    • 問題ありそうならまた対応しようそうしよう…

Ubuntuのタイトルバー邪魔問題

これの上部 Development -g gnome.md の部分 ↓ f:id:matsup8:20200911204805p:plain

導入環境

導入手順

GNOMEの拡張でタイトルバーを制御すると良いとか
Ubuntu(GNOME)でウィンドウのタイトルバーが場所を取ってしまうことへの対処

  • GNOME Tweaksを入れる

    • sudo apt install gnome-tweak-tool
    • GNOME Tweaksが使えるようになるがまだタイトルバーは消せない(この時点で消させてくれ)
  • 拡張機能の導入

  • FireFox拡張機能を導入し No Title Bar なる拡張を導入…が、動かない(なんで?)

    • Ubuntu 19.10以上は上記拡張では動かず No Title Bar - Forked なるものなら動くよう
    • ありがとう先人たち
  • 動作を確認 f:id:matsup8:20200911204812p:plain

  • やったぜ
  • ※細かいボタン設定などは GNOME Tweaksから変更できる模様

UbuntuにAnsible導入

導入環境

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible
  • エラー
E: リポジトリ http://ppa.launchpad.net/ansible/ansible/ubuntu focal Release には Release ファイルがありません。
$ ansible --version
ansible 2.9.6

ThinkPad X250にUbuntuを導入する

導入環境

  • マシン: ThinkPad X250
    • プロセッサ: Intel Core i5-5200U CPU @ 2.20GHz*4
    • メモリ: 7.5GiB
    • OS: Windows10 Pro
    • OS識別: 64bit
    • ディスク: SSD 512.1GB
  • Ubuntu: ubuntu 20.04.1 LTS

導入手順

USBをUbuntuのLiveUSBにする

USBからUbuntuを起動

  • ThinkPadBIOSを起動
  • Bootの優先順位を変更(UbuntuのUSBを上に)
  • Windows Boot Managerは削除(デュアルブートはしない)
  • 優先順位を変更後F10(Save and Exit)
  • Install Ubuntuを選択
    • Install Ubuntuを選択するとSetUpウィザードがでるのでこれで
  • ディスクを削除してUbuntuをインストール
    • ※ 再起動時にUSBを抜いておく(またBootが始まっちゃうので)
  • 再起動後Ubuntuのアップデート 再起動

各種設定

LaravelでAPIを作る際のエラーハンドリングメモ

めちゃくちゃに悩んでいるので頭の整理を兼ねてメモする。

経緯

現在アサインしている案件でLaravelを使用してAPIを作成することになっており、
機能実装に関してはさておき、エラーハンドリングをどうしようかという壁に当たった。

やったこと

Exceptionを定義してHttpExceptionをAPI用のエラーに入れ替える

まずはErrorHandler(app/Exceptions/Handler.php)の中身をいじり
404などのエラーでもJsonResponseを返すようにする。

<?php
    public function render($request, \Exception $exception)
    {
        // レスポンスをJSON形式に変更
        if ($request->is('api/*')) {
            $status = $this->isHttpException($exception) ? $exception->getStatusCode() : 500;
            return new JsonResponse([
                'message' => $exception->getMessage(),
            ], $status);
        }

        return parent::render($request, $exception);
    }

しかし、このままだとgetMessageに何も入ってこないので、
API用のエラーメッセージを用意するかExceptionを用意するかしないといけない気がしている自分。
ここで書いているときにそういうことだよな(?)って思った。
ダックタイピング大事だな〜〜〜。

ということで、HttpExceptionをApi用のExceptionに置き換えるメソッドを作成した。
Api用のExceptionは基底抽象クラスのApiExceptionを継承している。

<?php
    public function render($request, \Exception $exception)
    {
        // レスポンスをJSON形式に変更
        if ($request->is('api/*')) {
            $e = $this->prepareApiException($exception);
            return new JsonResponse($e->toArray(), $e->getStatusCode());
        }

        return parent::render($request, $exception);
    }

    /**
     * API用のExceptionに変換する
     *
     * @param Exception $exception
     * @return ApiException
     */
    protected function prepareApiException(Exception $exception)
    {
        switch (true) {
            case $exception instanceof ApiException:
                return $exception;
            case $exception instanceof AuthenticationException:
                return new UnauthorizedApiException('', $exception->getPrevious());
            case $exception instanceof MethodNotAllowedHttpException:
                return new MethodNotAllowedApiException('', $exception);
            case $exception instanceof ModelNotFoundException:
            case $exception instanceof NotFoundHttpException:
                return new NotFoundApiException('', $exception->getPrevious());
            case $exception instanceof ValidationException:
                return new ValidationFailedApiException($exception->errors(), '', $exception->getPrevious());
                break;
            default:
                return new InternalServerErrorApiException('', $exception->getPrevious());
        }
    }

Exceptionの中身はこんな感じ。

<?php

namespace App\Exceptions\Api;

use Exception;

abstract class ApiException extends Exception
{
    protected $headers = [];

    /**
     * @param int $statusCode
     * @param string $message
     * @param Exception $previous
     * @param array $headers
     */
    public function __construct(
        $statusCode = 0,
        $message = '',
        Exception $previous = null,
        array $headers = []
    ) {
        $this->headers = $headers;

        parent::__construct($message, $statusCode, $previous);
    }

    /**
     * Convert exception to array.
     *
     * @return array
     */
    public function toArray()
    {
        $return = [];
        $return['message'] = $this->getMessage();

        $errors = $this->getErrors();
        if (!empty($errors)) {
            $return['errors'] = $errors;
        }

        return $return;
    }

    /**
     * Add extra info to the output.
     *
     * @return array
     */
    public function getErrors(): array
    {
        return [];
    }
} 
<?php

namespace App\Exceptions\Api;

use Exception;

class ValidationFailedApiException extends ApiException
{
    /**
     * @var array
     */
    protected $errors = [];

    /**
     * Create a new ValidationFailedApiException.
     *
     * @param array $errors
     * @param string $message
     * @param Exception $previous
     */
    public function __construct(array $errors, $message = '', Exception $previous = null)
    {
        $this->errors = $errors;

        if (empty($message)) {
            $message = 'Validation failed.';
        }

        parent::__construct(422, $message, $previous);
    }

    /**
     * Get array of errors
     *
     * @return array
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
} 

ここまでやって、
ここまでやる必要ある????
って思った僕。

f:id:matsup8:20200218025547p:plain

正直汎用的なエラーに関してはAPI用のエラーメッセージだけ用意しておいて、
独自エラーに関しては

return new JsonRespnse(['message' => 'hoge'], Response::HTTP_NOT_FOUND);  

とかなんかそんなふうにしてしまえば良いのではと思った。

f:id:matsup8:20200218025922p:plain

全然分かんね〜〜〜みんなどうやってるんだこれ〜〜〜〜〜〜

とりあえずここまで。

Ruby事始め

経緯

業務でRubyを使う案件があり、アサインするか…?しないか…?
するかしないかは、あなた次第です。
のような感じだったので触ってみることにした。

やったこと

最初はRailsチュートリアルでもやろうかと思っていたけれども、
弊社に居るRubyエンジニアの方曰く
「とりあえずは生のRubyに触る方が良いと思う」
との事だったので素直にRubyを触ることにした。
Rubyを触るだと変な感じがするけどそんな感じ)

とりあえず目指すところとして、
PHPでいえばPHPUnitだけcomposer installされていてテストができる
状態を目指すことにした。

src/
  Hoge.php
tests/
  HogeTest.php
vendor/
   // 割愛
composer.json
phpunit.xml

環境

OS : macOS Catalina
Ruby : 2.7.0 (anyenvが便利だった)

参考資料

qiita.com qiita.com

構築

ほぼほぼQiitaの丸写しをしていく。
作業ディレクトリで以下コマンドを叩く。

> bundle init

bundle initをするとGemfileが作成される。
bundleがcompsoerみたいなもん?
Gemfileがcomposer.json的な?

rspecをインストールするためGemfileに以下を記述。
gem "rspec", ">= 3.0.0"
何故rspecなのかと言うとTDD本で見たことがあったし有名っぽいから。

> bundle install

すかさずinstallする。ますますcomposerっぽい。
ディレクトリができるのか?と思ったけどできなかった。
もう一声必要らしい。

> bundle exec rspec --init

rspecディレクトリが生成された。

rspec/
  examples.txt
  spec_helper.rb
Gemfile
Gemfile.lock

spec_helper.rbのコメントアウトされている部分(- =begin,- =end)を外し、準備完了である。
よーし!

正直に言ってここまでのことはほぼほぼ理解していない。
……とにかくテストを書きたかった。

class Hello
    def message
        "Hello!"
    end    
end

defがなんか…function的な…?
messageがfunction名で…?

まいったな…
いったいどこに迷い込んでしまったんだ…

焦るんじゃない
俺はテストが書きたいだけなんだ

require_relative '../src/Hello'

RSpec.describe Hello do
    it "message return Hello!" do
        expect(Hello.new.message).to eq "Hello!"
    end
end

…?

あ…しまった
テストはここでおわりか…

ということでテストを起動してみる。

> bundle exec rspec

f:id:matsup8:20200206005322p:plain
テスト結果

ようやくテストが出来た。
正直に言ってほぼほぼ理解できていないが、気が向いたらまたやろう。
向かなければ、PHPに詰まって憤死しそうな時にでもやればいい。

そう思った。

俺は得体の知れない奇妙な満足感を味わっていた。

CakePHP2のClass読み込み

経緯

リファクタのためのクラス分割用にトップディレクトリにsrcなどのディレクトリを作成して、App::build()で登録、App::use()で読み込み使えるようにしたかった。

結論として

無駄にトップディレクトリにこだわっていたけれども、
素直にappディレクトリ以下を使った方が良い。

やったこと

とりあえずまずはトップディレクトリにsrc/Hoge/Fuga.phpを作成。

src/
  Hoge/
    Hoge.php 

適当にテストを書いてHoge.phpに記述したclass Hogeを読み込んでみる。

<?php
class HogeTest {
    
    public function testHoge ()
    {
        App::build(
            [
                'Sample' => [ROOT . DS . 'src' . DS],
            ],
            App::REGISTER
        );

        App::uses('Hoge','Sample');

        // Class Not Found...
        $hoge = new Hoge();
    }
}

Sample/Hoge/Hoge.phpまでは読み込んでくれない。

https://book.cakephp.org/2/ja/core-utility-libraries/app.html#vendor

サブディレクトリがある場合はサブディレクトリも登録してあげる必要があるらしい。

<?php
class HogeTest {
    
    public function testHoge ()
    {
        App::build(
            'Sample' => [
                ROOT . DS . 'src' . DS,
                ROOT . DS . 'src' . DS . 'Hoge' . DS,            
            ],
            App::REGISTER
        );

        App::uses('Hoge','Sample');

        // 読み込めたぞ!
        $hoge = new Hoge();
    }
}

実験は成功だ!!!!!

しかし、リファクタをするにあたりディレクトリが大量にできていくであろう中、
その度にパスを追加する必要があるのか…?
ということに気づいた。

そしてできたコードが以下。

<?php
// ディレクトリを取得する再帰関数
function _load_src($root, &$paths)
{
    $res = glob($root . '/*', GLOB_ONLYDIR | GLOB_MARK);
    if (empty($res)) {
        return;
    }
    $paths = array_merge($paths, $res);
    foreach ($res as $f) {
        _load_src($f, $paths);
    }
}

$_src_paths = [];
_load_src(ROOT . DS . 'src', $_src_paths);

App::build(
    [
        'Sample' => $_src_paths,
    ],
    App::REGISTER
);

これでいいじゃん(いいじゃん)
と思ってbootstrap.phpにこいつをぶち込んでやるぜと意気揚々としていたところ、
そういえばクラス名が競合した場合はどうなるのん?
とふと思ったので試した。

src/
  Hoge.php
  Hoge/
    Hoge.php 
<?php
class Hoge
{
    public function getHoge()
    {
        return 'Hoge';
    }
}
<?php
class Hoge
{
    public function getHoge()
    {
        return 'Fuga';
    }
}

これを出力する。

<?php
/** テストfunction内 **/

// パッケージを追加 ※略
App::uses('Hoge','Sample');
$hoge = new Hoge()
var_dump($hoge->getHoge());
string(4) "hoge"

実験は失敗だ。
考えてみれば当たり前だけれども先に登録されているパスのクラスが使用される。
そのため上記の方法ではsrc以下でクラス名の重複ができない。

つまり

結論に戻って、素直にappディレクトリ以下につくれば良かった。

CakePHP2のクラスロードの仕組みは

<?php

App::use('Hoge','Sample');

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/** classMapに ['Hoge' => 'Sample'] として登録される。 **/
public static function uses($className, $location) {
    self::$_classMap[$className] = $location;
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/** ロード処理 **/
public static function load($className) {
    if (!isset(self::$_classMap[$className])) {
        return false;
    }

    $parts = explode('.', self::$_classMap[$className], 2);
    list($plugin, $package) = count($parts) > 1 ? $parts : array(null, current($parts));

    if ($file = self::_mapped($className, $plugin)) {
        return include $file;
    }
    $paths = self::path($package, $plugin);

    if (empty($plugin)) {
        // ここに入ってきてCakeの設定した接頭辞がつけられる(Cake規約)
        $appLibs = empty(self::$_packages['Lib']) ? APPLIBS : current(self::$_packages['Lib']);
        $paths[] = $appLibs . $package . DS;
        $paths[] = APP . $package . DS;
        $paths[] = CAKE . $package . DS;
    } else {
        $pluginPath = self::pluginPath($plugin);
        $paths[] = $pluginPath . 'Lib' . DS . $package . DS;
        $paths[] = $pluginPath . $package . DS;
    }

    $normalizedClassName = str_replace('\\', DS, $className);
    foreach ($paths as $path) {
        $file = $path . $normalizedClassName . '.php';
        if (file_exists($file)) {
            self::_map($file, $className, $plugin);
            return include $file;
        }
    }
    return false;
}

/** $pathsの中身
array(3) {
  [0]=>
  string(29) "/ROOTHOGE/app/Lib/Sample/"
  [1]=>
  string(25) "/ROOTHOGE/app/Sample/"
  [2]=>
  string(30) "/ROOTHOGE/html/lib/Cake/Sample/"
}
**/

よって、appディレクトリ以下に作成すれば下記のような場合

- app
  - src
    - Hoge.php 
    -Hoge
      - Hoge.php 
<?php

App::uses('Hoge','src');
// または
App::uses('Hoge','src/Hoge');

でクラスを使い分けられるようになるのであった。

  • 完 -

補足

一応App.phpのloadに $paths[] = ROOT . DS . $package . DS;
と記述してあげればできるっちゃできるのだけど流石にコアライブラリいじくりたくなくてやめた。

補足 +

これ単純に先にパスを登録しておくか動的に追加するかでしかないなとか
そもそもnamespaceきってできるじゃんとか色々と気づいた。
元々のソースにnamespace入れようとして死んだ経験からnamespace自体無理じゃん〜
って思ってたけど独立してるソースなら普通に読み込めるんだった。