20210227のPHPに関する記事は15件です。

phpMyAdmin�にアクセス拒否された時の対処法(を探してます)

phpMyAdminアクセスエラーに苦戦

プログラミング初心者なので、Udemyでphp/MySQL講座を学習中にphpMyAdminにアクセスを試みたところ、アクセス拒否、色々と対策を探した道のりをメモせず場当たり的になってしまっていたので、再度メモしながらやりたいと思います。

なお、本日時点(2021年2月27日)で対処方法が分からないので、実況中継的に更新していきます。

これ何?(HY000/1045) : Access denied

対処法を探しているところなので、タイムリーにメモをアップロードしていきます。
同じ処理を繰り返して無限ループしないように、ログとして。
早く解決したい。

あまり関係ないですが、Udemyで「PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門」を学習中に躓き、解決できない状態ですでに2日かけてしまってます。
(「39.MySQLを使ってみよう」の中でphpMyAdminにアクセスが必要になりました。)

環境

MacBook Pro (13-inch, 2017)
macOS Big Sur (Version11.1)
Homebrew 3.0.2
MySQL 8.0.23
MAMP6.3 ※アプリはProではなく無料版を使用
VScode 1.53.2

エラー発生の順序

一連のアプリはインストール済みです。

1.MAMPアプリを起動し、「WebStart」からMAMP公式サイトに移動
2.MAMP公式サイト MySQLを展開
3.You can administer your MySQL databases with phpMyAdmin(リンク)からphpMyAdminを開く
4.エラー発生

エラー@phpMyAdminリンク先ページ.
Error
MySQL said:Documentation

Cannot connect: invalid settings.
mysqli_real_connect(): (HY000/1045): Access denied for user 'root'@'localhost' (using password: YES)
phpMyAdmin tried to connect to the MySQL server, and the server rejected the connection. You should check the host, username and password in your configuration and make sure that they correspond to the information given by the administrator of the MySQL server.

学習が全く進まなくて困ったので、ググりながら一つ一つ原因っぽいものを確認し、潰していきます。

原因と対策

原因候補1.MySQLが起動していない?もしくは起動できない?

ターミナルからMySQLを起動してみます。

terminal.
-> % mysql -u root -p
Enter password: 

問題なく起動できた。

結果.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 42
Server version: 8.0.23 

原因候補2.パスワードが間違ってる?

一つ一つ手順を追っていきます。

①MySQLユーザーを確認

MySQL.terminal
>mysql SELECT Host, User FROM mysql.user;
+-----------+------------------+
| Host      | User             |
+-----------+------------------+
| localhost | ***              |
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+
5 rows in set (0.00 sec)

②パスワードかけてたかな、、mysql.userのパスワードハッシュを取得

MySQL.terminal
mysql> SELECT Host, User, authentication_string FROM mysql.user;

結果にauthentication_stringカラムが追加され、
パスワードがしっかりかかってル事がわかりました。
パスワード変更などしていないので、phpMyAdminにはまだログインできず。

③権限がどうなってるのか、一応確認してみる

MySQL.terminal
SHOW GRANTS FOR root@localhost;

全権限がroot@localhostにありそうなことはわかりました。たぶん。
ただ、これも設定を何も変更していないのでログインできず。

*パスワードハッシュ:ユーザーごとに設定してあるパスワードにハッシュ関数を適用してハッシュ値にしたものになります。

原因候補3.なぜかMAMP記載のパスワードがrootになってる

root@localhostには別のパスワードを設定しているはずだけど、MAMP公式サイトではパスワードがrootになっている。これは何かおかしそうなのでとにかく変更してみる。

パスワードを変更するにはconfig.inc.phpというファイルにアクセスする必要があるようです。

①config.inc.phpを探す

このパス/Applications/MAMP/bin/phpMyAdmin/config.inc.phpで辿り着けました。

②パスワード変更

ファイルを開いて、パスワードの記載箇所を検索(Command+F)passwordと入力すると、ありました、僕の場合は中身を何もいじっていないファイルの92行目に見つかりました。

config.inc.php
$cfg['Servers'][$i]['password']      = 'root';   

やっぱrootになっとるやんけ・・・
ということで早速変更してみよう。アクセスできるかな。
上記のrootをroot@localhostのパスワードに変更します。

③MAMPサイトからphpMyAdminにリトライ

あれ?
MAMPサイトから↓が消えて、
”You can administer your MySQL databases with phpMyAdmin(リンク)"

代わりに↓が表示され、リンクすらなくなってしまった。MySQLは確かに動いてるのになぜ?
"You can administer your MySQL databases with phpMyAdmin (MySQL server not running)."

④一旦MySQLを停止して、phpMyAdminに再アクセスしてみる

パスワードをrootに戻し、

config.inc.php
$cfg['Servers'][$i]['password']      = 'root';   

ターミナルからbrewコマンドでMySQLを停止

terminal.
% brew services stop mysql@5.7

この状態では、MAMPページにリンク表示されるものの、リンク先は変わらず(HY000/1045) : Access deniedが表示されている。

⑤試しにMySQLを停止したまま、パスワードだけを変えてトライしてみる

再度↓のパスワード('root')をroot@localhostのパスワードに変更し、

config.inc.php
$cfg['Servers'][$i]['password']      = 'root';   

MAMPサイトをみてみると、↓のリンクがちゃんと出ている!!
You can administer your MySQL databases with phpMyAdmin(リンク)
ということで、無事にphpMyAdminにアクセスできました!!

無事、アクセスできました。

phpMyAdminの使い方はまだわかりませんが、ようやく進められます!
原因は分からず、、、ですが、対策はMySQL停止→パスワード変更→アクセスという手順を踏んで、アクセスできました。
根本的な原因解決ができていないので心配ですが、とりあえず進められそうで・・・
ほんとよかったわ。

↑では、解決してませんでした。

原因候補③の対策後、設定を変えずにMAMPサイトに戻ったところ、アクセスリンクが消えていて再アクセスできない状態になっていました。

これ↓が、
You can administer your MySQL databases with phpMyAdmin(リンク)
こう。↓
"You can administer your MySQL databases with phpMyAdmin (MySQL server not running)."

また原因と対策探しをしていきたいと思います。

続・原因と対策

原因候補4。また追記します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者がPHPを学び始める2

配列について

これまでの変数が一つしか値を扱えなかったのに対し、配列を用いると複数の値をまとめて保存することができます。配列は仕切りのある箱のようなもので、それぞれのスペースにデータが入っており、0, 1, 2...というインデックス番号によってスペースの名前が付けられています。

配列の追加、上書き

配列の末尾に値を追加するときは「$配列名[] = 値;」とします。また、すでに存在するインデックス番号を指定すると、配列の値を上書きすることもできます。

例えば下記のように記述することができます。

 <?php
    $colors=array("赤","青","黄");
    echo  $colors[0];
    $colors[] = "白";
    echo $colors[3];
  ?>

連想配列

連想配列は、配列と同じく複数のデータをまとめて管理するのに用いられます。配列との違いは、個々の要素を管理するのにインデックス番号ではなく、「キー」と呼ばれる文字列などの値を指定することができる点です。
連想配列では「$配列名 = array('キー名' => '値1', ・・・);」といったように、「=>」を用いてキーと値をセットします。

foreach

foreach文とは、配列または連想配列に対して、先頭のデータから順に繰り返し処理を行うための命令です。
「as」の後ろの変数に、ループの度にデータが先頭から順に代入されていきます。asの後ろの変数名は何を指定しても大丈夫です。

foreach文では、配列内のデータが順次「キー変数」、「値変数」に代入され、それに対して処理が繰り返し適用されます。

例えば以下のような記述場合
数学は70点です。英語は90点です。国語は80点です。
と繰り返すことになります。

  <?php

    $scores = array('数学' => 70, '英語' => 90, '国語' => 80);

    foreach($scores as $key => $value){
      echo $key."は".$value."点です。";
    }

  ?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者がPHPを学び始める

PHPとは

HTMLでは決められたテキストしか表示することができないのに対してPHPを使うと、見る人や状況に応じて、表示するテキストを変えることができます。
PHPはHTMLに埋め込んで使うことができる。
その際に<?php 〜 ?>の部分がHTMLに変換された上で表示される。

文字列を出力する。(echoを使う)

「echo」は文字列などを出力するための命令です。文字列を出力する場合はシングルクォーテーション「'」かダブルクォーテーション「"」で囲みます。
ちなみに計算のしたい場合はダブルコーテーションで囲まない。囲ってしまうと囲った部分が文字列として扱われるため計算できない。

例えば、「こんにちは!!」と表示したいときは以下のようになる。

  <?php
    echo "こんにちは!!"
  ?>

変数の書き方

「$変数名 = 値;」のように記述する。
よってfruitという変数を作成しりんごという値を代入し出力する場合は以下のようになる。

  <?php
    $fruit = "りんご";
    echo $fruit;
  ?>

文字列の連結

ドット「.」記号を用いると文字列を連結することが出来ます。文字列同士の連結、変数と文字列の連結、変数同士の連結をすることができます。
例えば下記の場合は果物はりんごというように出力できます。

  <?php
    $fruit = "りんご";
    echo "果物は".$fruit;
  ?>

if文

if文を使うと、条件に応じて処理を分岐することができます。
条件が成り立つ場合、{}の中の処理が実行されます。
条件が成り立たない場合は処理が実行されません。

下記の場合は条件が成り立つため
$xは50より大きいです。と出力されます。

  <?php
    $x = 100;
    if($x > 50){
      echo "$xは50より大きいです。";
    }
  ?>

else文

ifと組み合わせてelseを使うと、「もしも〜だったら・・・、そうでなければ・・・」といった条件分岐が可能になります。if文の条件が「false」であった場合、elseの中の処理が実行されます。
下記の場合は年齢が30歳以上ではないためelse文の処理が行われます。
すなわちあなたは30歳未満です。が出力されます。

 <?php
    $age = 26;

    if($age >= 30){
      echo "あなたは30歳以上です。";
    }else{
      echo "あなたは30歳未満です。";
    }
  ?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OctoberCMS 管理画面実装テク:テキストフィールドの文字数制限を変更する

OctoberCMSの自作プラグインで管理画面を実装する際のテクニックのお話。

管理画面フォームのtextタイプのフィールドはデフォルトで255文字の制限がある。
これはattributesプロパティを使えば属性を上書きできる。

myfield:
    label: "My Field"
    type: text
    attributes:
        maxlength: '1000'

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OctoberCMS 管理画面実装テク:フォームに非表示フィールドを作る

OctoberCMSの自作プラグインで管理画面を実装する際のテクニックのお話。

fields.yamlでフィールドを定義して管理画面のフォームを実装する際、<input type="hidden">のようなフィールドが意外と直感的に作れない。

hiddenプロパティでできそうだが、これだとPOSTデータに含まれなくなる。
readOnlydisabledだと変更不可にはなるがページに表示されてしまう。

そこで、containerAttributesプロパティを使用してdisplay: noneを当ててやる。

containerAttributes:
    style: "display: none"

これで、POSTデータに含まれるが表示されないフィールドが実装できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelの構成概念 第1回 ライフサイクル編

Laravelフレームワークの全体の処理の流れやサービスコンテナ、サービスプロバイダーについて調べたことをまとめます。

シリーズ

  • Laravelの構成概念 第1回 ライフサイクル編
  • Laravelの構成概念 第2回 サービスコンテナ編(近日公開)
  • Laravelの構成概念 第3回 サービスプロバイダ編(近日公開)

調べるきっかけ

こちらのイベントに参加するにあたって、読んでおかないと話についていけないだろうなぁと思って復習がてら読んでいきます。

初心者の方へ

ライフサイクル・サービスコンテナ・サービスプロバイダ周りは始めたばかりの人には難易度が高いですが、Laravelの処理の流れや初期設定などの仕組みを理解できるので概要だけでも把握できると良いです。

環境

  • Laravel 8.5.11

参考

Laravelの構成概念については公式ドキュメントにより詳しい内容がまとまっているので詳細を知りたい方は上記の公式ドキュメントをご覧ください。

用語概要

用語: Laravel ライフサイクル

Laravelが起動して終了するまでのLaravel全体の処理の流れをライフサイクルといいます。
ちなみに入り口はHttpとConsoleの二つあります。

用語: Laravel サービスコンテナ

クラスの依存関係を管理し、依存注入(DI)を実行するための機能です。

用語: Laravel サービスプロバイダ

Laravelアプリケーション全体の起動処理における初期起動処理を行っています。
サービスプロバイダからサービスコンテナへDIの設定(コンテナ結合)を行います。

サービスプロバイダーでは、コンテナ結合以外にもイベントリスナ、フィルター、ルーティングの設定なども行います。

用語: DI(Dependency Injection)とは

Dependency Injectionは依存性の注入となります。
PHPを実行する際に抽象クラス(interface)を具象クラスに差し替えて実行できる。

テストの時は、DBやAPIを実際にアクセスさせないために差し替えたり等テストコードが書きやすくなるメリットがあります。

本題: Laravelライフサイクル

http://localhost/foo/bar 等のHttp経由の場合、public/index.phpから始まります。

php artisan foo:bar 等のConsole経由の場合、artisanから始まります。

それぞれファイルの中身を見てみましょう。
(補足のコメントを入れてます)

public/index.php

Http経由の流れです。

public/index.php
<?php

use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

// タイマー開始。フレームワークの起動にかかる時間などを測れる
define('LARAVEL_START', microtime(true));

// メンテナンスモードのファイルがあればメンテナンス画面を表示して終了する
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Console/stubs/maintenance-mode.stub
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
    require __DIR__.'/../storage/framework/maintenance.php';
}

// composer dump-autoload で生成されたオートローダーを読み込む
// オートローダーとは外部にあるPHPファイルを自動的に読み込む仕組みです
// https://genkiroid.github.io/2016/07/15/about-composer-autoload
require __DIR__.'/../vendor/autoload.php';

// ここからLaravelのコアシステムが始まります
// Illuminate\Foundation\Application のインスタンスを取得して $app に格納してます
// サービスコンテナを生成してHttpカーネル、Consoleカーネル、例外ハンドラーのインスタンスの生成を行います
// https://github.com/laravel/laravel/blob/8.x/bootstrap/app.php
$app = require_once __DIR__.'/../bootstrap/app.php';

// 上の行で生成したHttpカーネルのインスタンスを取得してます(App\Http\Kernel)
$kernel = $app->make(Kernel::class);

// tapやsendの挙動がどうなっているのか怪しいですが...
// 送信されてきたHttpリクエスト(GET, POSTの中身)をHttpカーネルへ渡してHttpレスポンスを取得してます
$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

// Laravelの終了処理
$kernel->terminate($request, $response);

artisan

Console経由の流れです。

artisan
#!/usr/bin/env php
<?php

// タイマー開始。Httpの時と同じ
define('LARAVEL_START', microtime(true));

// オートローダーの読み込み。Httpの時と同じ
require __DIR__.'/vendor/autoload.php';

// Illuminate\Foundation\Application インスタンスの取得。Httpの時と同じ
$app = require_once __DIR__.'/bootstrap/app.php';

// サービスコンテナからコンソールカーネルのインスタンス(App\Console\Kernel)を取得している
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);

// 引数入力インスタンスとコンソール出力インスタンスをコンソールカーネルへ渡して実行する
// 実行結果のステータスを受け取る
$status = $kernel->handle(
    $input = new Symfony\Component\Console\Input\ArgvInput,
    new Symfony\Component\Console\Output\ConsoleOutput
);

// Laravelの終了処理
$kernel->terminate($input, $status);

// $statusを出力して、スクリプトを終了する
// https://www.php.net/manual/ja/function.exit.php
exit($status);

public/index.phpの補足

public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';

bootstrap/app.php

bootstrap/app.php ファイルの補足です。

bootstrap/app.php
<?php

// 新しいLaravelアプリケーションのインスタンスを生成してます
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

// インターフェースに具象クラスをサービスコンテナにバインドしてます

// Httpカーネルのバインド
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// Consoleカーネルのバインド
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// 例外ハンドラーのバインド
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

まず、bootstrap/app.php#L29-L32

Illuminate\Foundation\Applicationのインスタンスを生成してます。

Illuminate\Foundation\ApplicationIlluminate\Container\Containerを継承してます。なのでサービスコンテナのインスタンスを作ってると言っても良いですね。

Illuminate\Foundation\Applicationのコンストラクタを見てみましょう。

Illuminate\Foundation\Application
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

    protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('app', $this);

        $this->instance(Container::class, $this);
        $this->singleton(Mix::class);

        $this->singleton(PackageManifest::class, function () {
            return new PackageManifest(
                new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
            );
        });
    }

    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            // ... 略
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }
}

この段階ではLaravelを起動するための最低限のクラスが読み込まれています。
深掘りしていくとキリがないのでここは気になったら詳しくみていけば良いかなと思います。

続いて、

Illuminate\Contracts\Http\Kernelインターフェースをキーとして、Illuminate/Foundation/Http/Kernelクラスを登録してます。

Consoleカーネル、例外ハンドラーも同様に登録してますね。
最後に $app を返して bootstrap/app.php は終了です。

public/index.php
$kernel = $app->make(Kernel::class);

先ほどサービスコンテナに登録した Illuminate\Contracts\Http\Kernelをキー使って、App\Http\Kernelインスタンスを取得します。

App\Http\Kernelのコンストラクタを見てみます。
このファイルはvendor配下ではなく、app/Http/Kernel.phpと配置されてます。
また、Illuminate\Foundation\Httpを継承していて、ここのコンストラクタが実行されます。

Illuminate\Foundation\Http\Kernel
    /**
     * Create a new HTTP kernel instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    public function __construct(Application $app, Router $router)
    {
        $this->app = $app;
        $this->router = $router;

        $this->syncMiddlewareToRouter();
    }

    protected function syncMiddlewareToRouter()
    {
        $this->router->middlewarePriority = $this->middlewarePriority;

        foreach ($this->middlewareGroups as $key => $middleware) {
            $this->router->middlewareGroup($key, $middleware);
        }

        foreach ($this->routeMiddleware as $key => $middleware) {
            $this->router->aliasMiddleware($key, $middleware);
        }
    }

Httpカーネルクラスの初期処理でミドルウェアの設定が行われてますね。
どんなミドルウェアが登録されているかはApp\Http\Kernelを見るとわかります。

public/index.php
$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

// Laravelの終了処理
$kernel->terminate($request, $response);

$request = Request::capture()

$_GET, $_POST などを元に Illuminate/Http/Requestのインスタンスを作っています。

$kernel->handle(...)
Httpカーネルのhandleメソッドを実行してIlluminate/Http/Requestインスタンスを渡して実行してます。
ルーティングやコントローラを通って、最終的にIlluminate\Http\Responseインスタンスを生成して返します。

$response = tap(...)->send()

Illuminate\Http\Responsesend メソッドを実行してます。
Illuminate\Http\ResponseSymfony\Component\HttpFoundation を継承していて、Symfonyコンポーネントのメソッドを呼び出してます。
ここでは、Httpレスポンスヘッダーやレスポンスボディの出力を行ってます。
つまりはここで最終的なHTMLが出力されてる訳ですね。

tapヘルパーメソッドを使うと、メソッドチェーンで関数を実行しても、返り値はtap関数の第一引数に渡した変数が返ってきます。
つまりは、 Illuminate\Http\Response のインスタンスが $response に入ります。

最後にLaravelの終了部分です。

$kernel->terminate($request, $response);

Illuminate\Foundation\Http\Kernel
    public function terminate($request, $response)
    {
        $this->terminateMiddleware($request, $response);

        $this->app->terminate();
    }

    protected function terminateMiddleware($request, $response)
    {
        $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
            $this->gatherRouteMiddleware($request),
            $this->middleware
        );

        foreach ($middlewares as $middleware) {
            if (! is_string($middleware)) {
                continue;
            }

            [$name] = $this->parseMiddleware($middleware);

            $instance = $this->app->make($name);

            if (method_exists($instance, 'terminate')) {
                $instance->terminate($request, $response);
            }
        }
    }

Laravelの終了時にもミドルウェアを差し込めるようですね。
各種ミドルウェアにterminateメソッドがあればそれを実行して終了処理を行っていきます。

さいごに

今回はLaravel ライフサイクルについて詳しく見ていきました。
改めて調べるとまだまだ知らない部分が発見できてよかったです。

サービスコンテナやサービスプロバイダについてもまとめて紹介しようと思いましたが、
流石に長くなるので別記事に分けて紹介しようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】ロギングでファイルに出力しつつもSentryにもエラーログを飛ばしてWEBから見れるようにする

わざわざログファイル見に行くのがめんどくさい

エラー起こったときにわざわざサーバーに見に行くのめんどくさいじゃないですか。
なんでライブラリMonologを使ってログファイルに書き出しつつもSentryへもその内容を飛ばしてスマホからとかでもエラー内容を確認できるようにしてみます

Monologの導入

composerでインストール

$ composer require monolog/monolog

composer(PHPのパッケージマネージャー)入れてない場合は先に入れてください

呼び出しつつ出力先とログレベルを指定する

index.php
<?php
require_once __DIR__.'/vendor/autoload.php';

$log = new \Monolog\Logger('app');
// ここではログレベルをWARNING以上の場合にログ出すように設定
$log->pushHandler(new \Monolog\Handler\StreamHandler('/app.log', \Monolog\Logger::WARNING));

設定できるレベルは以下の通り

Log Levels
Monolog supports the logging levels described by RFC 5424.

DEBUG (100): Detailed debug information.
INFO (200): Interesting events. Examples: User logs in, SQL logs.
NOTICE (250): Normal but significant events.
WARNING (300): Exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
ERROR (400): Runtime errors that do not require immediate action but should typically be logged and monitored.
CRITICAL (500): Critical conditions. Example: Application component unavailable, unexpected exception.
ALERT (550): Action must be taken immediately. Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
EMERGENCY (600): Emergency: system is unusable.

(引用:Using Monolog | monolog

Sentryの導入

サイトで登録まで済ます

以下から登録する(GitHubアカウントあれば楽です)

発行されたURLを確認する

https://***********************.ingest.sentry.io/*****みたいなURLが発行されるので確認しておく

Projectsとかの設定も済ませておく)

PHP SDKをcomposerでインストール

composer require sentry/sdk

この時点でcomposer.jsonは以下の感じになってるはず

composer.json
{
    "require": {
        "monolog/monolog": "^2.2",
        "sentry/sdk": "^3.1",
    }
}

二つを合わせて実装してみる

上で設定したMonologSentryを入れて強制的に例外発生させて動きを確認してみます

実装例

index.php
<?php
require_once __DIR__.'/vendor/autoload.php';
// Sentry setup (URLはSentry登録後に発行されるやつを書く)
Sentry\init(['dsn' => 'https://***********************.ingest.sentry.io/*****']);

try {
    // Logger
    $log = new \Monolog\Logger('app');
    $log->pushHandler(new \Monolog\Handler\StreamHandler('/app.log', \Monolog\Logger::WARNING));

    throw new \Exception("テスト用のエラー"); // ここでわざと例外発生させてみます

    return;
} catch (\Throwable $exception) {
    // ログをファイルに出力しつつ
    $log->error($exception);
    // Sentryへも飛ばす
    Sentry\captureException($exception);
}

ログファイルに書き込まれているか確認

↓こんな感じでログファイル(例ではapp.log)に出力される

app.log
[2021-02-27T12:47:56.704675+09:00] app.ERROR: Exception: テスト用のエラー in C:\work\test\index.php:10 Stack trace: #0 {main} [] []

Sentry上にエラー内容が送信されているか確認

うまくいくと左メニューにあるIssuesに以下のようなエラーが表示される
c988dbab-7fb4-46c2-bbd6-6bb06618f39a.png

おわり

  • 自分は遊びで作ったやつに入れたんで特に設定してないですけどSlackとかにも飛ばせるみたいです
  • それはそうと久しぶりにPHP書くと楽しかった(こなみかん)

参考URL

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelはRequestを使えと怒られたお話

LaravelではRequestを使おう!

はじめに

先輩「何この\$_POSTって、Requestを使わない理由は何?」
僕「え?りくえすと??\$_POSTと何が違うの??」
先日Laravelを初めて使用してサービスを作った際に先輩からご指摘をいただきました。
\$_POSTではなくRequestを?。。。はぁ?
調べてみるとなるほどな、納得納得。

とまあこんな初心者レベルですが、備忘録として、あわよくば同じ立場の初心者さんのためにも残しておこうかと思います。

開発環境

PHP 7.3.25
Laravel 8.16.1

Requestってなーに?

Laravelで、リクエスト(Request)というのは、ブラウザを通してユーザーから送られる情報をすべて含んでいるオブジェクトのことです。例えば、会員登録のフォームなら、画面でユーザーが入力したEメール、パスワード、名前、住所だけでなく、何のブラウザを使用したか(User Agent)、どのIPから送られたか、どのURLからアクセスしたかなど、また、会員ログイン後の画面なら、会員認証において保存されたクッキーもブラウザを介して、リクエストに含まれます。
引用:ララジャパン

ざっくりいうと、\$_POSTとか\$_GETとか、その他ユーザーが通信を行った情報を全てまるっとゴリっとまとめてくれたものか!

ちなみにこの\$_POSTとか\$_GETとかいうのは、PHPのスーパーグローバル変数の1つで、
これもブラウザからの情報を取得する際に使用できます。
詳細はこちらの記事を参考にするといいかも。

ふむふむ、めちゃくちゃ便利じゃん!

でもなぜRequestを使った方がいいの?

例えばあなたの前に一般電卓と関数電卓があったとします。
四則演算の範疇に収まらない複雑な計算をするときに一般電卓でも出来ないわけではないですが、関数電卓があれば楽ちんに計算出来ますよね?

そうです。
Requestの方が楽ちんなんです!
上記でも記載した通り、Requestには色々な情報がギュッと詰まっています。
ユーザーがブラウザからフォームで送信したデータはもちろん、
よく使うところの送信が行われたURLからユーザーのIPアドレスまでほんと色々。

これは使わない手はないです。

他にも「型」にも違いがあります。

\$_POSTなどのハイパーグローバル変数は基本的に連想配列と文字列で構成されます。
それに比べてRequestはオブジェクト型で構成されます。

さほど影響ないのでは?
果たしてそうでしょうか。。。

取得データの扱いが変わります

まずは下記条件で実験してみましょう。

formデータ:
// user_name→anomeme
// test_data→空欄

<input name="user_name" class="form-control" type="text-input" value="anomeme">
<input name="test_data" class="form-control" type="text-input" value="">

$_POST
array:2 [▼
  "user_name" => "anomeme"
  "test_data" => ""
]

request()->all()
array:2 [▼
  "user_name" => "anomeme"
  "test_data" => null
]

お分かりいただけますでしょうか。
そう、test_dataのvalueが別物に変わってますよね?

空白もnullも一緒じゃないか!と思う人がいるかもしれませんが、これは別物です。
・空白は文字列が0のデータ!
・nullは存在しないよ!
です。

例えば isset() 関数でこれを見てみましょうか。

isset($_POST["test_data"])
→ bool: true

isset(request()->all()["test_data"])
→ bool: false

一緒のデータを使用しているのに結果が変わってしまいましたね?
(is_null() でも判定が変わりますね)

長くなりましたが、requestは便利だけどこういった違いがありますよというお話でした。

終わりに

取れる値については他の人がさらに研究をしたりリファレンスを読み解いているので、こちらを参考にしてみるといいと思います。

PHPを勉強してきた人ならフォームデータを\$_XXXを取得したくなる気持ちは分かりますが、
一度に様々なデータが取得できるRequestを是非使ってみてください。
これは私からのリクエストです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel6 メールのファイル添付機能で日本語のファイル名が文字化けした

目的

  • laravel6のメールにファイル添付をする機能を作成したら日本語のファイル名が文字化けしたのて解決策をメモ的に紹介する。

環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする
Laravel バージョン 6.X commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

方法

  • ファイル名指定部分にてmb_encode_mimeheader()を用いてファイル名を指定しエンコードを行うと正常に表示された。
  • mb_encode_mimeheader(添付ファイル名)とすることでエンコードすることができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel6.x〜】デバッグするならdd()?いやいや、ddd()を使おう!!

概要

ZennやQiitaでLaravelの記事を色々見ていたらたまたまddd()というデバッグ用のヘルパー関数を見つけたので実際に使ってみました。

結論:今後はddd()を使う!!

環境

$ composer -V
Composer version 1.10.20 2021-01-27 15:41:06

$ php artisan --version
Laravel Framework 6.20.16

dd()とddd()とは

どちらのデバッグ用のヘルパー関数です。

デバッグ関数 概要
dd() Laravel本体のヘルパー関数
ddd() facade/ignitionのヘルパー関数

(ヘルパー関数:PHPとは別にLaravelや各パッケージが用意している関数)

ddd()Laravel6.xから統合されたfacade/ignitionのヘルパー関数だそうです。

Laravel Ignition Introduces the ddd() Helper

Laravel本体のヘルパー関数でないから

でも見つけることができませんでなかったわけですね。

※ddd()がfacade/ignitionのヘルパー関数であることはこの記事へのコメント、Twitterで教えていただきました。ありがとうございます?‍♂️(どちらもLaravel本体のヘルパー関数だと思っていました)

サンプルケース

記事投稿アプリで特定のidの記事の編集画面表示の処理とします。
ViewからControllerのアクションに記事(Article)クラスのインスタンス(=編集する記事データ)を渡している仕様とします。

app/Http/Controllers/ArticleController.php
class ArticleController extends Controller
{
    // 略

    /**
     * 記事編集画面表示
     *
     * @param  App\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function edit(Article $article)
    {
        return view('articles.edit', compact('article');
    }

    // 略
}

dd()使用時の画面

app/Http/Controllers/ArticleController.php
class ArticleController extends Controller
{
    // 略

    /**
     * 記事編集画面表示
     *
     * @param  App\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function edit(Article $article)
    {
        // dd()
        dd($article);
        return view('articles.edit', compact('article');
    }

    // 略
}

スクリーンショット 2021-02-27 0.05.42.png

モデルインスタンスの場合、多くの場合

  • attributes:デバッグ対象のプロパティ
  • relations:デバック対象のリレーション関係にある情報

を見るかと思いますし、dd()で取得できるのはそれくらいかと思います。

ddd()使用時の画面

app/Http/Controllers/ArticleController.php
class ArticleController extends Controller
{
    // 略

    /**
     * 記事編集画面表示
     *
     * @param  App\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function edit(Article $article)
    {
        // ddd()
        ddd($article);
        return view('articles.edit', compact('article');
    }

    // 略
}

【Debugタブ(初期表示)】

これはdd()で表示される内容と同じですね。

スクリーンショット 2021-02-26 23.34.00.png

【Stack Traceタブ】

スクリーンショット 2021-02-26 23.32.59.png

スタックトレースとは、こちらの記事でこう書かれています。

エラーが発生するまでに、どんな処理を、どの順番で呼び出したか表現したもの

こうは書いていますが、今回のように処理を中断した場合は中断したところまでの処理の流れを追えるようになっています。

今回の場合でいうとpublic/index.phpの55行目がまずは実行されて、最終的にapp/Http/Controllers/ArticleController.phpの89行目(ddd($article);)が実行されて処理が中断されているという流れがわかります。
(ここまでに計44個の工程があるんですね〜)

【Requestタブ】

スクリーンショット 2021-02-26 23.33.30.png

リクエストの情報が記載されています。

  • GETであること
  • CSRFトークンの値
  • リクエストヘッダー

などが一目でわかります。

【App】

スクリーンショット 2021-02-26 23.33.46.png

  • Controller:実行したアクションメソッド
  • Route name:ルーティング名
  • Route Parameters:アクションメソッドに渡したパラメーター(引数にあたる)
  • Middleware:ルーティングに設定したミドルウェア

こんな情報が一目で確認できます。

【User】

スクリーンショット 2021-02-26 23.33.51.png

Userタブではログイン中のユーザーの情報が表示されます。

この情報は

dd(Auth::user());

で取得できるUserクラスインスタンスのattributesプロパティからpasswordの情報を除いたものです。(これはセキュリティの観点からなのかな...?)

【Context】

スクリーンショット 2021-02-26 23.33.56.png

  • Repository:GitHubのリポジトリ
  • Message:最新のコミットメッセージと
  • Laravel version:Laravelのバージョン
  • Laravel locale:言語設定
  • Laravel config cache:設定ファイルのキャッシュをクリアしたか(※)
  • PHP version:PHPのバージョン

(※)
この画像ではfalseになっていますが、

$ php artisan config:cache

実行後に再度、ddd($article);するとtrueに変わっていました。

config系のキャッシュをクリアしてない(=config系の設定が反映されてない)ことに起因するエラーの発見もしやすくなりそうですね。

まとめ

ddd()、めっちゃ良い!!

  • UI(タブ切り替えでわかりやすい)
  • 情報量(ログインユーザーや設定情報もすぐわかる)

の観点から完全にdd()の上位互換な印象ですね。

ddd()ではなくdd()を使う理由が見つからないので今後はddd()使います。

(もっと早く知りたかった...?‍♂️)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】配列の要素をintにキャストする

はじめに

配列の要素にstringが含まれていて、困るシーンがあったので、配列の要素をintにキャストする関数を考えました。

コード

$arr = ['1', '2', '3'];

$res = array_map('intval', $arr)
// array(3) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
// }

array_mapにコールバックintval()を渡すことで、全要素に`intval()が行われてキャストされるという仕組みです。

おまけ

Utilsに自分で追記してもいいのかなと思ってます☺

/**
 * 配列の要素を指定に型にキャストする
 *
 * @param array $arr キャストしたい配列
 * @param string $cast_type 【int, str, float, bool】を指定することが可能
 * @return array キャストした配列を返却
 */
function array_cast(array $arr, string $cast_type): array {
    return array_map($cast_type.'val', $arr);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】spl_autoload_register()っていったい何してんの?

初めに

/public/index.phpを起点にLaravelの処理を追っていたところ、vendor/composer/autoload_real.phpの中にspl_autoload_register()という何やらよくわからない関数が登場したので、それの説明をする解説記事となります。

動作環境

  • PHP 7.4.3
  • Composer 2.0.11

該当の関数が登場したソースコード

vendor/composer/autoload_real.phpgetLoader()メソッドの中にそれはありました。

vendor/composer/autoload_real.php
class ComposerAutoloaderInitcde23787628405c61112bd11321f024e
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        // ~~中略~~

        spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
        spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));

        // ~~後略~~
    }
}

では、このspl_autoload_register()がなにをしているのかという謎を解決してみましょう。

結論

先に結論を申し上げておきますと、spl_autoload_register()は、

未定義のクラスやインターフェースが読み込まれたときに実行してほしい処理を登録しておくことができる関数

と、私は解釈いたしました。

つまり、

呼び出したクラス、インターフェースが、自ファイルやrequireされたものの中に定義されていなかった場合に、クラスのオートロード処理といったいわばセーフティネットのようなものを登録できる関数

と認識すればわかりやすいのではないでしょうか。

以下、大変参考にさせていただきました記事になります。

具体例

サンプルとして同一ディレクトリ上に、Foo.phpexecute.phpがあったとします。

./Foo.php
class Foo
{
   public function test()
   {
       echo 'test in Foo class';
   }
}
./execute.php
$foo = new Foo()  // PHP Error:  Class 'Foo' not found in ...

$foo->test();

execute.phpでのFooクラスの呼び出しはrequireも何もされていないので、インスタンス化の時点で当然例外が出力されます。

ではspl_autoload_register()を使ってFooクラスをオートロードしてみたいと思います。

execute.phpspl_autoload_register()を追加しました。

execute.php
<?php

spl_autoload_register(function($class) {  // オートロード処理を登録
        echo "Want to load $class.\n";
        require __DIR__ . "/${class}.php";
});

$foo = new Foo();

$foo->test();

spl_autoload_register()execute.phpを実行してみるとどうでしょう。

Want to load Foo.  
test in Foo class  

require ./Foo.phpという記述をしていないのにも関わらず、execute.phpFooクラスオブジェクトによるtest()メソッドが実行されていることが分かります。

以下、解説です。

spl_autoload_register()の使い方

マニュアルによるとspl_autoload_register()には3つ引数を指定できるようです。

spl_autoload_register ( callable $autoload_function = ? , bool $throw = true , bool $prepend = false ) : bool

autoload_function
登録したい autoload 関数。 パラメータが指定されなかった場合は、デフォルト実装である spl_autoload() が登録されます。
throw
このパラメータは、 spl_autoload_register() が autoload_function を登録できなかったときに例外をスローするかどうかを指定します。
prepend
true の場合、spl_autoload_register() はキューの最後に追加するのではなく先頭に追加します。

引用:PHP: spl_autoload_register - Manual

かいつまんで説明すると、

  • autoload_functionには登録したいオートローディング処理をcallable型で指定
  • throwは引用した文章の通り
  • prependには登録したコールバックを最優先に実行するかをbool型で指定

といったように理解すればいいでしょうか。(callable型についてはこちらを参照してください)

このautoload_functionに指定したコールバック関数には、引数として呼び出そうとした未定義のクラス名やインターフェース名が文字列として渡されます。

この渡される文字列を用いて、動的にrequireなどのファイル読み込み処理を記述しておける、というのがautoload_functionの意義でしょう。

spl_autoload_register()の呼び出し方法が分かったところで、execute.phpの処理を1つ1つ見てみましょう。

サンプルコードの解説

以下、再掲です。

execute.php
<?php

spl_autoload_register(function($class) {  
        echo "Want to load $class.\n";
        require __DIR__ . "/${class}.php";
});

$foo = new Foo();

$foo->test();

まずspl_autoload_register()に渡した無名関数には、

  1. "Want to load $class.\n"という文字列を出力
  2. $classに渡されたクラス名をもとに、同一ディレクトリ内の${class}.phpを読み込む

という処理が記述されています。

なので、"Want to load $class.\n"という文字列が出力されればspl_autoload_register()によるオートロードが働いているということが分かりますね。

spl_autoload_register()によりオートロード処理の登録が完了したところで、次の処理を見てみましょう。

./execute.php
$foo = new Foo();

./execute.phpでは未定義のFooクラスが呼び出されました。

通常でしたらそのまま例外が出力されるのですが、未定義クラスが呼び出されたことをトリガーとして、spl_autoload_register()に登録した処理が実行されます。

今回の例でいうと、無名関数の引数$classには"Foo"が渡され、

  1. "Want to load $class.\n"が出力され
  2. 同一ディレクトリ内の./Foo.phpを読み込む

という処理が実行されます。

このオートローディング処理を経て、無事./Foo.phpを読み込むことができました。

なので、$foo = new Foo()で例外が出力されることはなく、そして$foo->test()も例外を出すことなく正しい出力がなされるのです。

vendor/composer/autoload_real.php内のgetLoader()の解説

spl_autoload_register()の動作が分かったところで、本記事の趣旨とは外れますがvendor/composer/autoload_real.phpgetLoader()のうちspl_autoload_register()が絡んでくる部分を解説してみたいと思います。

以下、再掲です。

vendor/composer/autoload_real.php
class ComposerAutoloaderInitcde23787628405c61112bd11321f024e
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        // ~~中略~~

        spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
        spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));

        // ~~後略~~
    }
}

getLoader()メソッドを見てください。こちらでspl_autoload_register()が呼び出されています。

第1引数には

array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader')

という配列が渡されています。

先ほどautoload_functionには登録したいオートローディング処理をcallable型で指定すると述べましたが、callable型として配列を渡すことも可能になっております。

callable型の引数として配列を渡した場合、

  • 0番目にはクラスオブジェクト、またはクラス名の文字列
  • 1番目にはそのクラスで定義されているメソッド名の文字列

と指定することで、0番目のクラスに定義されている1番目のメソッドをコールバックとして指定するといったことができます。

参考:PHP: コールバック / Callable - Manual

これを踏まえて該当のspl_autoload_register()を見てみましょう。

spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);

配列の0番目に"ComposerAutoloaderInitcde23787628405c61112bd11321f024e"(つまり自身のクラスですね)が、1番目に"loadClassLoader"が渡されています。

ですので、この呼び出しでComposerAutoloaderInitcde23787628405c61112bd11321f024e::loadClassLoaderメソッドがオートローディング処理として登録されることになります。

次の行を見てみると、さっそく未定義のクラス\Composer\Autoload\ClassLoaderが呼び出されています。

self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));

なので、先ほど指定したばかりのオートロードが実行されます。つまり、自クラスのloadClassLoader()メソッドが実行されます。

public static function loadClassLoader($class)
{
    if ('Composer\Autoload\ClassLoader' === $class) {
        require __DIR__ . '/ClassLoader.php';
    }
}

先ほどのexecute.phpと似たような処理ですね。言葉で説明すると、

呼び出しクラス名が'Composer\Autoload\ClassLoader'と一致するなら、vendor/composer/autoload_real.phpと同一ディレクトリにあるClassLoader.phprequireする

となります。

そして今回、newするクラスがまさに\Composer\Autoload\ClassLoaderクラスであるので、vendor/composer/ClassLoader.phpがオートロードされて、
無事にself::$loaderとローカルスコープ上の$loader\Composer\Autoload\ClassLoaderクラスのインスタンスが代入されます。

最後に後処理として、先ほど登録したオートロード処理を

spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'))

によって解除したというところで、今回の解説は以上になります。

終わりに

composerを使っている方ならオートロードという仕組みが備わっていることを一度は耳にしたことがあると思います。

ですが、その仕組みがどのように提供されているのか詳しく知っている人はあまり多くないと思います。

そういった、普段であれば考えなくていい、ツールが裏側で担っている処理がどのように構築されているかを追ってみるのは私の中でいい経験になりました。

皆さんも一度、いつも使っているツールがどのような処理の上に提供されているのか、1から紐解いてみてはいかがでしょうか。そのツールに対して普段とはまた違った知見を得られるかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

着る服が少なくなったらメール通知する仕組みを作った?

はじめに

「洗濯物が溜まっているから着れる服が少ない!!」 そんな経験はありませんか?
「着る服がなくなる前に、あらかじめ知ることができれば...(心の声)」
生活のちょっとした不便さを、アイデアとIoT技術で解決まで導きたい。
そんな思いから洗濯アラートサービスの開発が始まりました?

何ができるか

  • 着る服が少なくなるとメール通知
  • カビが生えやすい温湿度になるとメール通知
  • 熱中症リスクのある温度になるとメール通知

ハードウェアとプログラムのせいさく!(ver0)

ハードウェアとプログラムのせいさく!(ver1)

mojikyo45_640-2.gif
⇒Sigfoxモジュール+フォトインタラプタ+温湿度センサ+Arduino
mojikyo45_640-2.gif
⇒3Dプリンタによるケース
hoge.jpg
⇒1時間に一回温湿度とハンガーの数を計測してサーバにPOSTする仕組み
⇒サーバでは温湿度や服の枚数に応じて指定アドレスにメールを送る
mojikyo45_640-2.gifmojikyo45_640-2.gif
⇒おおお、いいやんけ!!

mojikyo45_640-2.gif
⇒服の数が少なくなりメール通知を受けている様子

ver1の問題点

無題.png
 上記イメージのように、ハンガーを引っかけた状態で服を取っても、
 計測されるから無意味じゃなイカ????(がーん?

 ⇒そこで、ハンガー状態ではなく服の状態を監視するように改良しようと決意!

ハードウェアとプログラムのせいさく!(ver2)

mojikyo45_640-2.gif
⇒KiCadで回路設計
⇒BLEモジュール+ATTINY85+フォトリフレクタなど。
mojikyo45_640-2.gif
⇒自作基板ができたゾ(いぇい✌✌

 どーん!!!!!!!!!!!!!!! 
sentaquiqv2.jpg
⇒fusion360を使ってモデリングしたことがあるが、自分の技量不足を感じ、ケースに関しては3Dプリンタガチ勢の方に依頼。
⇒うん、自作基板がいい感じに入ってるぅ↑↑

⇒フォトリフレクタを用いて上手く計測できた。
sentaquiqv23.jpg
⇒仕組みは、シンプル。フォトリフレクタとBluetoothモジュールがくっついた装置で各ハンガーの服状態を監視して常時値をM5Stickに送信!!


⇒m5stickは各ハンガーの服状態をBluetooth受信して集計値と
温湿度センサの値をクラウド(IoTサーバ)にPOST
⇒サーバ側は、温湿度センサの値、服の数に応じてメール送信!!(基本ロジックはver0,1とほぼ同じ

(余談)お気づきでしょうか...

上記画像に登場したTシャツは、そう、オリジナルTシャツなのです!!←謎のノリで製作依頼を。
1.jpg

学んだこと

  • 回路設計・電子回路技術
  • 洋服枚数、温湿度通知サーバ(PHP+nginx+Linux)構築技術
  • Sigfoxに関しての知見
  • Bluetoothのプロトコルに関しての知見
  •  オリジナルTシャツの発注方法
  • 小型マイコン(ATTINY)を用いたプログラムの書き込み方法
  • 失敗してもあきらめず続ければ完成するというマインド❤    など

気づいたこと

  • サーバをGASで構築すれば、コストを抑えれる。

最後に(宣伝)

クラウド型洗濯アラートサービス「SENTAQUIQ」は、技術力向上目的(趣味)で個人開発・運営しているIoTサービスです。 「QOL底上げIoT」をテーマに、今後もより良いシステムとなるよう成長できればと考えています。 まだまだ発展途中でありますので、温かいご支援をいただけますと幸いです✋
良かったら、コメントなども気軽にしてください!それでは~~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2上のエラーログをS3に移してAthenaで分析する

エラーログをAthenaでお手軽に見やすくしたい

ALBのアクセスログをAthenaで可視化してみたら非常に便利だったので、エラーログでもやってみました。
エラーログ、見るの手間だけどほっとくと増えてるんだよな…。

参考 AWS公式 ALBログのクエリ

環境と要件

Apache/2.4.46 ()
PHP 7.0.33

そこまでリアルタイム性は求めてないですが、毎日更新はさせたいです。
さすがにFatalが出ていたら気づきたいですし、Warningも無理のない範囲でつぶしていきたいです。
ログファイルはLogrotateで日付ごとのファイル分割と.gzファイルへの圧縮は対応済みです。
良い感じの記事が見つからなかったのでテーブルは自作します。

エラーログをS3におく

必ずしも漏らさず拾う必要もないので、ログファイルは適当に定時でS3に移動するようにします。

S3上のエラーログからAthenaでテーブルを作成する

エラーログは以下のような形式です。

[Fri Feb 26 06:50:56.127563 2021] [:error] [pid 26587] [client 127.0.0.1:21111] PHP Warning:  strlen() expects parameter 1 to be string, array given in /home/example.jp/test.php on line 20, referer: https://example.jp/

そしてテーブルの作成クエリがこちら。テーブル名とS3バケット名は任意のものに変更してください。

CREATE EXTERNAL TABLE IF NOT EXISTS in_error_logs (
    time string,
    theme string,
    pid string,
    user_name string,
    ip string,
    type string,
    detail string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
    'input.regex' = 
    '([^ ]* [^ ]* [^ ]* [^ ]* [^ ]*) ([^ ]*) [^ ]* ([^ ]*) ([^ ]*) ([^ ]*) (.+?): ((.+ on line [0-9]+)(.+))'
)
LOCATION 's3://in-log/error-log/';

以下、考慮した部分です。
同じ内容をGROUP BYでまとめるために、発生個所にreferer情報が出る場合を考慮してカラムを作成しています。
エラーのレベルと内容が分かれば十分なので、ほかの情報はあまり細かく分割していません。
例えば今回ログファイルを日付ごとに分割しているので、記録日時は重要ではないと判断しました。
参考 php エラーログの読み方

必要な情報だけを取り出す

Queryは例えばこんな感じです。

SELECT
    COUNT(TIME) AS COUNT,
    error_level,
    detail
FROM
    elblogdb.media_in_error_logs
GROUP BY
    error_level,
    detail
ORDER BY
    error_level DESC,
    COUNT DESC;

するとこうなります。めでたしめでたし?
エラー解消していきましょう。
image.png

BIツールで可視化

共有したかったのでBIツールで可視化しました。
AWSアカウントのないメンバーでも、ログの確認と対応ができます。
個人的にはRedash気に入ってまして、実際はここまでのクエリ操作もすべてRedash上で行っています。
Fatalないよね?とか、急に件数増えてる!とかわかりやすいです。

やってみて

アクセスログの分析をやっていれば問題なくできると思います。
エラーログを見るハードルがぐんと下がりました。やったね?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm install && npm run devした際のエラー

laravel8でnpm install && npm run devを実行し、npm ERR!というエラーが出た際に解決した方法を書きます。

下記コマンド実行

$ php artisan cache:clear

実行結果

Cache cleared successfully

下記コマンド実行

php artisan config:cache

実行結果

Configuration cache cleared!
Configuration cached successfully!

再度下記コマンドを実行

npm install && npm run dev

解決しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む