Ikeda's Blog

【PHP】可変変数に配列を使う

可変変数とは

可変変数を使用すると、以下のようなことができる。

$name = 'abc';
$$name = 'aaaaa';

var_dump($abc); // => string(5) "aaaaa"

配列に入れた値を変数名にする

では、この$nameを配列にしたい場合。

ダメな例

 

$test = [
    1 => 'abc'
    2 => 'def'
];

$$test[1] = 'aaaaa';

var_dump($abc); // => NULL

正しい例

 

$test = [
    1 => 'abc'
    2 => 'def'
];

${$test[1]} = 'aaaaa';

var_dump($abc); // => string(5) "aaaaa"

参考URL

https://www.php.net/manual/ja/language.variables.variable.php

【レビュー】GARMIN ForeAthlete 55

ざっくり結論

★★★☆☆(5点中、3点)
悪くはないんだけど、友人知人に勧めるかは微妙なライン。

製品情報

https://www.garmin.co.jp/products/wearables/foreathlete-55-black

前提情報

私は、腕時計の類があまり好きではなく、ずっと着けていなかった。
スマートウォッチも、今回はじめて購入したため、「他と比べて」という観点では見ていない。

使用感

まず最初に持った感想は、「軽い!」というところ。37グラムて。
その重さで様々な測定機能を持っているのは、技術の進歩ってすごいなぁ、という感想になってしまった。
これは、他社他機種のスマートウォッチも大きな差はない様子。スペックで言えばApple watchも38グラム程度だったはず。

では着け心地はというと、正直、微妙。
軽いから、長時間着けていても負担にはならないのは確か。
ただ、本体が肌に貼り付く感覚があって、これが結構気になる。
肌質にもよるところではあるのだろうけれど、良くない印象を持った部分。

機能

まず、大前提として、私がすべての機能を十全に使いこなせていない。
多機能の弊害というか、「その機能は使わないなぁ……」というものが存在するので、そういった点には触れない。

運動の記録

一番の目当てとしていた機能。これに関しては、非常に満足。
GPS関連商品をずっと扱っているGARMINだけあって、どこを通ったか、正確な記録ができている。

ただし、全てのデータが正確ではない。特に、ステップ数(歩数)が信頼できない。
何せ、寝て起きただけで、時折100歩以上が記録されているのだ。ちゃんと0の日も、ごく稀にある。
推測だが、GPSを歩数測定に使用しているのではないだろうか。
ポケモンGOをやったことのある人なら、自宅に居るはずなのに微妙に動き回っているような現象を見たことがあると思う。
まぁ、万歩計のように振るだけでカウントされても困るので、難しいところだが。

心拍・呼吸数の測定

心拍や呼吸数の測定を行ってくれる。
とはいえ、常日頃から測っていたわけではないので、これらのデータが正しいかどうかは、正直わからない。
少なくとも、運動した時にはしっかりと心拍数も呼吸数も上がっているので、デタラメではないだろう。

ストレスレベルの測定

これも、「多分合ってるんじゃないかなぁ……」くらいの信用度。
仕事中は高ストレスだし、読書中なんかは"休息"扱いだ。
ただ、集中して仕事していた頃に乱高下したりしており、正しいのか、それとも集中できていなかったのか、判断に困る場面もあったりする。

Body Battery

多分、一番信用ならない測定値。
GARMIN独自の機能だそうで、先述の心拍やストレスなどの測定値を利用して算出している"身体エネルギーの残量"らしい。

ただし、絶対に5を下回らない。

いつもなら、座って仕事していてもガンガン減っていくはずなのに。
いや、エネルギー無くなっても困るのだけど。
それに、「体が疲れ切っている」という意味ではまぁ、正しいと言えるかも知れない。
が。何というか、"Windowsの進捗バーが99%で延々と続く"みたいな信用の置けなさがある。

デザイン

外観はシンプルな、正に普通の時計っぽいもので、普段遣いでもビジネスシーンでも問題無いので好き。
画面は他のスマートウォッチも同様だと思うが、自由に設定できる。
通常の腕時計から離れたデザインであれば、画面を取り繕っても、悪目立ちしかねないので大変気に入っている。

……が、誤算もあった。
購入前は、「気分で画面切り替えれば楽しいやん!」くらいに思っていたのだが。

  • コロコロ変えると、どこに何の情報が出ているか把握しづらい
  • そもそも、画面について管理するアプリ(GARMIN Connect IQ)が使いにくい

という問題によって、今は固定デザインで運用している。

電池

信用ならないポイント。

まず、バッテリーの保ちは期待通りに素晴らしい。
GPS計測を使う頻度にもよるが、1週間近く無充電でも行ける。
多分、カタログスペック通りに、時計としてだけであれば14日程度はいけるんじゃないかな。

私が信用できないと感じたのは、残量表示だ。

100%に達してしばらく、充電しっぱなしになったことがあった。
その後、全量表示が100%から減らない。
3日ほど、GPS使用を挟んでようやく、1%減った。
99%→98%は、数時間でなるのに。
100%→99%は、なかなか変化しない。

総評

ところどころ信用が置けないけれども、私のユースケースでは一応は問題にならない。
という、なんとも微妙な結論となった。

なので、「悪くはないんだけど、友人知人に勧めるかは微妙なライン」といった評価だ。

Laravelでブログサイトを作る 21. 管理画面・記事のプレビュー機能

目的

記事編集途中に、プレビュー表示して確認したい。

ルーティング

routes/web.php
プレビュー用のルーティングを追加します。更新と同様に、入力値をPOST送信する予定です。

// ...省略...

Route::group(['prefix' => 'admin', 'middleware' => 'auth', 'namespace' => 'App\\Http\\Controllers\\'], function() {
    // ...省略...

    Route::post('articlePreview', 'ArticleController@preview')->name('admin_article_preview'); // プレビュー

    // ...省略...
});

テンプレート

resources/views/admin/articleEdit.blade.php
プレビューボタンを設置します。
更新する時と同じく、入力内容をsubmitします。

{{-- 省略 --}}
<form method="post">
    {{-- 省略 --}}

    <input type="button" id="preview" value="プレビュー" />
    <input type="button" id="update" value="{{ isset($articleData->id) ? '更新' : '登録' }}" />
</form>
{{-- 省略 --}}

<script>
    $(document)
        .on('click', '#preview', function(){
            $(this).parents('form').attr('action', '{{ route('admin_article_preview') }}').attr('target', '_blank');
            $(this).parents('form').submit();
        })
        .on('click', '#update', function(){
            $(this).parents('form').attr('action', '{{ route('admin_article_update') }}').attr('target', '');
            $(this).parents('form').submit();
        })
    ;
    </script>

Controller

app/Http/Controllers/ArticleController.php

記事詳細表示の共通化

フロントの記事詳細画面と、プレビュー表示で共通化できる部分が多いので、先に対応します。
(記事の詳細表示するcontent()の一部分を抜き出す。抜き出した箇所にはreturn $this->articleView($articleData, $articleChildrenData);を記述する)

    /**
     * 記事1件分のページ表示(フロントの記事詳細と、管理画面のプレビュー機能から)
     * @param $articleData
     * @param $articleChildrenData
     * @return mixed
     */
    private function articleView($articleData, $articleChildrenData) {
        // 親カテゴリを取得
        $parentCategoryList = $this->parentCategory->orderBy('number', 'asc')->orderBy('id', 'asc')->get();

        // 子カテゴリは、キーを親カテゴリIDにした連想配列にする
        $data = $this->childCategory->orderBy('number', 'asc')->orderBy('id', 'asc')->get();
        $childCategoryList = [];
        foreach ($data as $item) {
            $childCategoryList[$item->parent][] = $item;
        }

        // 目次対応
        $articleData->body = $this->makeIndex($articleData->body);

        return view('front.article', [
            'articleData' => $articleData,
            'articleChildrenData' => $articleChildrenData,
            'parentCategoryList' => $parentCategoryList,
            'childCategoryList' => $childCategoryList,
            'parentNum' => $this->getParentArticleNum(),
            'childNum' => $this->getChildArticleNum(),
            'newArticleData' => $this->getNewArticleList(),
        ]);
    }

プレビュー表示

内容はシンプルで、

  1. 入力値を取得して変数に格納する
  2. 本来はDBから取得する記事データを、入力値にする
    • JOINで取得する親カテゴリ名を別途取得してセットする
  3. 記事と関連付けられている子カテゴリは、入力値から子カテゴリのリストとして取得する
    という感じです。
    これらの情報を、先程の共通化メソッドに渡してやれば、ページ表示できます。
    /**
     * プレビュー表示更新
     *
     * @param Request $request
     * @return mixed
     */
    public function preview(Request $request)
    {
        // 入力値取得
        $id = $request['id'];
        $tiitle = $request['title'];
        $body = $request['body'];
        $parent = $request['parent'];
        $childList = $request['child'];
        $open = $request['open'];
        $private = $request['private'];
        $description = $request['description'];

        // 記事取得
        $articleData = new Article();
        $articleData->title = $tiitle;
        $articleData->body = $body;
        $articleData->parent = $parent;
        $articleData->open = $open;
        $articleData->private = $private;
        $articleData->description = $description;

        // 親カテゴリ名を保持させる
        $parentData = $this->parentCategory->where('id', $parent)->first();
        $articleData->name = $parentData->name;

        // 記事と関連付けられている子カテゴリの情報を取得
        $articleChildrenData = null;
        if (is_array($childList)) {
            $articleChildrenData = $this->childCategory->whereIn('id', array_keys($childList))->get();
        }

        return $this->articleView($articleData, $articleChildrenData);
    }

Laravelでブログサイトを作る 20. DBに後からカラムを追加する

目的

記事に、Meta用の項目"Description"を設定できるようにしたい。
(設計する時に考慮しきれなかった部分に対応する)

migration

migrationファイルの作成

サーバに入り、コマンドを実行します。

  • meta情報を追加するので、ファイルの名前は"add_meta_to_article"にします。
  • --table=で、追加するテーブルを指定します。
php artisan make:migration add_meta_to_article --table=articles

migrationファイルの内容を記述

database/migrations/XXXX_XX_XX_XXXXXX_add_meta_to_article.php
テーブル作成の時と同様に、up()に追加する項目を記述します。

// ...省略...
    public function up()
    {
        Schema::table('articles', function (Blueprint $table) {
            $table->text('description');  // 追加:ディスクリプション
        });
    }
// ...省略...

migration実行

php artisan migrate

これで、テーブルにカラムが追加されました。

テンプレートの修正

記事編集画面

resources/views/admin/articleEdit.blade.php
編集画面に、それぞれの入力欄を設置します。

// ...省略...
<form action="{{ route('admin_article_update') }}" method="post">
    // ...省略...
    <h3>Description</h3>
    <input type="text" name="description" value="{{ $articleData->description ?? '' }}" />
    // ...省略...
</form>
// ...省略...

共通テンプレート

resources/views/app.blade.php
headタグ内に、@yield('meta')だけ記載することで、各テンプレートでMetaタグを自在に設定できるようになります。
存在しない場合は、何も表示されませんので、既存のテンプレートを修正しなくてもエラーが出たりはしないので、必要な箇所にのみ記述するだけで良いのです。

<head>
    // ...省略...

    @yield('meta')

    // ...省略...
</head>

記事の表示画面

resources/views/front/article.blade.php

// ...省略...

@section('meta')
    <meta name="description" content="{{ $articleData->description }}" />
@endsection

// ...省略...

Controller

記事編集

app/Http/Controllers/ArticleController.php
入力されたDescriptionを取得して、テーブルに保存します。
なお、未入力であれば、記事の冒頭100文字を抽出して保存するようにします。


    /**
     * 記事情報更新
     *
     * @param Request $request
     * @return mixed
     */
    public function update(Request $request)
    {
        // 入力値取得
        $id = $request['id'];
        $tiitle = $request['title'];
        $body = $request['body'];
        $parent = $request['parent'];
        $childList = $request['child'];
        $open = $request['open'];
        $private = $request['private'];
        $description = $request['description'] ?? mb_substr(strip_tags($body), 0, 100);

        // ...省略...

        // トランザクション開始
        DB::beginTransaction();

        try {
            // 記事データ保存
            $articleData = $this->article->withTrashed()->where('id', $id)->first();
            if ($articleData === null) {
                $articleData = new Article();
            }
            $articleData->title = $tiitle;
            $articleData->body = $body;
            $articleData->parent = $parent;
            $articleData->open = $open;
            $articleData->private = $private;
            $articleData->description = $description;
            $articleData->save();

            // ...省略...
        } catch (\Exception $e) {
            // ...省略...
        }
    }

Laravelでブログサイトを作る 19. 管理画面・記事編集の時に、画像をアップロードできるように(TinyMCE)

はじめに

前回にて実装したTinyMCEの機能を利用して、記事の編集中に画像をアップロードする仕組みを用意します。

サーバー側での対応

Laravelのディレクトリ構造から考えまして、画像は、storageディレクトリ(storage/app/public)に保存するべきと考えました。
しかし、このディレクトリを参照することはできません。
そこで、以下のコマンドを実行しておきます。

$ php artisan storage:link

これで、public/storageにシンボリックリンクが作成されますので、storage/***.jpgのようにして画像を表示できます。

$ ls -l public/
(省略) storage -> /home/vagrant/blog/storage/app/public

TinyMCEでアップロードできるようにする

resources/views/admin/articleEdit.blade.php
前回実装したTinyMCSの記述を修正します。

<script src="{{ asset('/js/tinymce/tinymce.min.js') }}"></script>
<script>
    tinymce.init({
        selector:'textarea',
        language: "ja", // 日本語対応(https://qiita.com/nissuk/items/e31bdfa858d6c5c018c2)

        // ▼画像アップロード対応(https://www.tiny.cloud/docs/configure/file-image-upload/)
        plugins: 'image code',
        toolbar: 'undo redo | link image | code',
        image_title: true, // イメージダイアログのタイトルフィールドを有効にする
        automatic_uploads: true, // ブロブやデータのURIで表現された画像の自動アップロードを可能にする
        images_upload_url: '{{ route('admin_article_image_upload') }}?_token={{ csrf_token() }}',
        file_picker_types: 'image',
        relative_urls : false,  // 画像やリンクのパスが相対パスに変換されるのを防ぐ
        convert_urls: false,

        /* カスタムイメージピッカー */
        file_picker_callback: function (cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');

            input.onchange = function () {
                var file = this.files[0];

                var reader = new FileReader();
                reader.onload = function () {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), { title: file.name });
                };
                reader.readAsDataURL(file);
            };

            input.click();
        },
        content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
    });
</script>

アップロードされた画像を保存する

ルーティング

routes/web.php
アップロードするルーティングを追加します。

Route::group(['prefix' => 'admin', 'middleware' => 'auth', 'namespace' => 'App\\Http\\Controllers\\'], function() {
    // ...省略...

    Route::post('articleImageUpload', 'ArticleController@image')->name('admin_article_image_upload'); // 画像アップロード

    // ...省略...
});

Controller

app/Http/Controllers/ArticleController.php

アップロードされた画像を保存し、パスを返却します。

/**
 * 画像アップロード
 *
 * @param Request $request
 * @return false|string
 */
public function image(Request $request) {
    // ファイルの保存(戻り値はファイル名)
    $imgpath = request()->file('file')->store('', 'public');

    // 画像パスを返却
    return json_encode(['location' => '/storage/' . $imgpath]);
}

参考文献

https://www.tiny.cloud/docs/configure/file-image-upload/
https://qiita.com/kawa_matsu/items/50306befb7a5f879e38e
https://readouble.com/laravel/7.x/ja/structure.html