20191215のPHPに関する記事は22件です。

レンタルサーバを比較してみた

レンタルサーバと言えば、超老舗の「さくらインターネット」や、国内シェアナンバーワンの「XSERVER」などが有名だが、「カラフルボックス」という1年11カ月前にサービスを開始したばかりの最新技術を取り入れた高速サーバがあると噂を聞いて、ちょっと試してみた。(お試し期間30日と長め)

カラフルボックスとは?

カラフルボックス(ColorfulBox)とは2018年1月23日に大阪で出来たレンタルサーバの会社である。
出来てから1年と11カ月!
1996年 さくらインターネットが創業。ホスティングサーバ事業を開始
1999年 さくらインターネット株式会社が設立される
2004年 エックスサーバが設立される
2018年 カラフルボックスが設立される

歴史が浅いためか、情報収集しても「カラフルボックス」を使っているWEBサイトやブロガーは少なさそうです。

さらに「カラフルボックス wiki」で検索すると・・・

『カラフルBOX』(カラフルボックス)は2003年12月5日にSoundTailが発売した18禁恋愛アドベンチャーゲーム。

全然違う・・・

『COLORFUL BOX』(カラフル ボックス)は、1983年11月21日に発表された早見優の5枚目のアルバムである

これも全く違う・・・

WikiPediaに誰も書いてないくらいマイナーなサーバって事なのか?!

さらに別ワードで検索すると

お弁当グッズの「カラフルボックス」

確かに、「カラフルボックス」ってネーミングからは、レンタルサーバを想像しにくい:sweat_smile:

国内のシェア

エックスサーバは国内シェアがナンバーワン!
レンタルサーバでは下記の3つがビッグスリーです。
この3社が4位以降を大きく引き離している!

1位 エックスサーバー 14.18%
2位 ロリポップ 13.71%
3位 さくらインターネット 12.79%
参考・引用
https://server-hikaku-guide.com/rental-server-share/

カラフルボックスはもちろん圏外。

さて、性能比較です・・・

比較したサーバ

さくらインターネット スタンダード
 初期費用 ・・・0円
 月額料金(1年契約)・・・ 524円
 月額料金(3年契約)・・・ 524円
 無料お試し期間・・・2週間
 サーバソフト nginx
 使用ディスク HDD (DBサーバはSSD)

エックスサーバー スタンダード[x10]
 初期費用 ・・・3000円
 月額料金(1年契約)・・・ 1000円
 月額料金(3年契約)・・・900円
 無料お試し期間・・・10日間
 サーバソフト nginx
 使用ディスク 完全SSD

カラフルボックス(BOX2)
 初期費用 ・・・0円(3ヶ月契約以上)
 月額料金(1年契約)・・・ 980円
 月額料金(3年契約)・・・ 880円
 無料お試し期間 ・・・ 30日間
 サーバソフト LiteSpeed
 使用ディスク 完全SSD

費用で言うと、さくらインターネットが一番安い。
次いでカラフルボックス。
カラフルボックスは、数あるレンタルサーバの中でも安い方です。

Wordpressにて、全く同じサイトを構築し、Google Speed Insightにてスコアを比較してみた。

さくらインターネット
SP 92 PC 99

エックスサーバー
SP 98 PC 99

カラフルボックス
SP 99 PC 100

シンプルなページ(テーマCocoonで作成した簡易サイト)ではあるものの、カラフルボックスでは、PCでなんと100点!ワードプレスで作成したページで初めて見たかも!
エックスサーバでも十分速いのですが、それを上回るとは・・・。
速い.png

(もうちょっと画像とか増やして測定した方がよかったかなぁ・・・)

FTPアップロードスピード 1000ファイル(合計100MB程度)

さくらインターネット、エックスサーバー、カラフルボックスに1000ファイルを同時にアップロードしてみたところ7分で、ほぼ同時に完了した。

アップロードには大きな差は出ないようだ。

FTPダウンロードスピード 1000ファイル(合計100MB程度)

ストップウォッチで、よーいドンで合計100MB程度の1000ファイルを一斉にダウンロード開始
イメージは以下のような感じ
全体.png
ところが、カラフルボックスが優先的にダウンロードされていまい、他がダウンロードのスピードを落としている?!

やり方を変えた。
1.さくらインターネットにて1000ファイルをFTPダウンロード。時間を測定
 ↓
2.さくらが終わったら、エックスサーバにて1000ファイルをFTPダウンロード。時間を測定
 ↓
3.エックスサーバが終わったら、カラフルボックスにて1000ファイルをFTPダウンロード。時間を測定

さくらインターネット
40分(ほぼ滞りなく安定してダウンロードされた)

エックスサーバ
45分(1ファイルあたりのダウンロードスピードは非常に速いのだけど、たまに急に詰まって遅くなったり?)

カラフルボックス
16分(初動は15秒くらい待たされたりして遅かったものの、いったん開始してからスピードが速かった。)

環境にも左右されそうですが、自宅のマシンだとこの結果になりました。

PHPの速度測定

今後、気が向いたらやってみます。やる予定でしたが間に合わずすみません。

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

DBとLaravelの接続設定【備忘録】

はじめに

PHP/Laravelの勉強にて備忘録として

前提

MySQLがインストールされていること

LaravelのDB接続設定ファイル

config/database.php を開く

config/database.php
/*中略*/
    'default' => env('DB_CONNECTION', 'mysql'),
/*中略*/

となっている。

env('DB_CONNECTION', 'mysql')
上記のenvはDB_CONNECTIONが設定されていればDB_CONNECTION
設定されていなければMySQLを使用という意味である

ここでLaravelの環境変数を見てみる。

tinker(php artisan tinker)を起動し、 print_r($_ENV)を入力

$ php artisan tinker
$ print_r($_ENV)
    <中略>
    [APP_DEBUG] => true
    [APP_LOG_LEVEL] => debug
    [APP_URL] => http://localhost
    [DB_CONNECTION] => mysql
    [DB_HOST] => 127.0.0.1
    [DB_PORT] => 3306
    <中略>

DB_CONNECTIONはmysqlになっていることを確認。
ではLaravelの環境変数を決めているのは誰か。

プロジェクトトップにある.envファイルを開く

.env
<中略>
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
<中略>

これの DB_CONNECTION=mysql となっているところを使用したいDBに指定する。
今回はMySQLを使用するのでそのまま

config/database.php
        /*中略*/
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],
        /*中略*/

config/database.php で自分が使用するmysql(今回は)の詳細設定をする

重要なのはdatabase, username, passwordで、すべenvなので.envで編集する

.env
<中略>
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
<中略>

今のままではDB名、ユーザ名、パスワードが上記のままなので変える。

.env
<中略>
DB_DATABASE=test
DB_USERNAME=root
DB_PASSWORD=
<中略>

上記のようにするとDB名がtest ユーザ名がrootでパスワード無しでログインする。

testデータベースを作成する
$ sudo service mysqld start
$ mysql -u root
mysql> CREATE DATABASE `test`;
データベースが接続されているか確認
$ php artisan tinker
>>> DB::connection();
=> Illuminate\Database\MySqlConnection {#2891} 
タイムゾーン設定

タイムゾーンの設定を「東京(Asia/Tokyo)」にする
レコード保存時に時間情報が設定したタイムゾーンの時間で保存される

config/app.php
    'timezone' => 'Asia/Tokyo',

以上

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

Laravelで複数の宛先にメールを送信するときにつまづいた。

環境

Laravel 5.5

背景

仕事で、とあるアクションを行った時に複数の宛先にメールを送る方法がわからずに四苦八苦してしまった。

問題点

app/Mail/sendmail.php
public function build()
    {
        return   $this->from('XXXXfromXXXX@gmail.com')
                ->subject('テスト送信完了')
                ->to(????)
                ->view('emails.sendmail');
    }

初めは直接->to()の中に記述していたが、エラーでうまくいかず...

見るべき場所

途中Maiableを継承している場所を確認すればいいことに気づいた。

app/Mail/sendmail.php
use Illuminate\Mail\Mailable;

class OrderShipped extends Mailable
{

illuminateなのでvendor以下のMailableを確認する。

vendor\laravel\framework\src\Illuminate\Mail\Mailable.php
/**
     * The "to" recipients of the message.
     *
     * @var array
     */
    public $to = [];

変数$toは配列で初期化されていることを発見。
(中の処理を詳しく見たい方はvendor以下のMailableクラスを参考にしてください。)

問題解決

配列を作成して、引数として渡す方法で解決した。

app/Mail/sendmail.php
public function build()
    {
        $to = array('XXXto1XXX@gmail.com','XXXto2XXX@yahoo.co.jp');

        return   $this->from('XXXXfromXXXX@gmail.com')
                ->subject('テスト送信完了')
                ->to($to)
                ->view('emails.sendmail');
    }

気づき

継承しているクラスを確認するのが大事。

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

AmazonインセンティブAPI実装(Amazonギフト券)

経緯

Amazonギフトカード実装をお願いされ、気軽にokを出したものの、概要がとってもわかりずらく、かつ参考文献が全くなく、意外と四苦八苦したので、同じ悩みの方が出ないように投稿する事にしました。
なお、事前に用意されたphpのサンプルコードからLaravelに実装し直す、という流れが一番良いと思いますが、あまりにも面倒だったのと時間との勝負だった為、決してベストプラクティスでないことはご了承ください。。

本題

登録→確認方法

基本的には導入マニュアルがあるので、これに沿って開発をしていきます。
AmazonインセンティブAPIとの統合プロセスについて
登録した後、サンドボックス環境で正常に動作するか確認

サンプルコード参照

FAQ付録にサンドボックス環境のサンプルコードがあります。
今回はphp/Laravelで実装予定だったので、ここのphpのサンプルコードを読み、htmlSDKv2_php_cr⁩/htmlSDKv2_php/rollups⁩/function.phpにキー発行実装があります。

実装方法

  1. fuciton.phpのみ取り出し、app/Library配下に設置
  2. controllerからのinvokeRequest を指定
  3. Laravel実装の為、globalで引っ張ってきてるところをconfigにまとめ、アクセスキー等は.envに設定
  4. 実装完了。。。。

注意点としては、サンドボックス環境とプロダクション環境の2つともアクセスキーを発行しなければいけません。

余談(メイン)

今回は、jsonで返したので、XML2Array.phpは使用していません。
今回の僕の実装の流れとしては、
登録した後、サンドボックス環境でidが合っているか確認するところまでは順調でした。
ただresponseを叩いた時に、雲行きが怪しくなりました。responseの情報以外に沢山の情報があり、気づいてしまったんです。

あれ、、???これ、、、、api叩くだけじゃダメじゃね、、、?

そうです。僕は必要なrequestを提げて、apiを叩けばそれで実装が完了すると思っていました。
そんな甘い世界ではありません。このapiではSignatureキーを採用しているので、あちらで発行するキーと同じ発行をしなければなりませんでした。
その実装が開発マニュアルに書いてあるのかと思いきや、そのような実装は見つからずましてや必要なrequestすら曖昧すぎてよくわからなかったです。泣きそうでした。

そんな泣きそうな中、FAQ付録にサンドボックス環境においてのサンプルコードを見つけ、phpのサンプルコードのhtmlSDKv2_php_cr⁩/htmlSDKv2_php/rollups⁩/function.phpにたどり着きました。。

実装する人は少ないと思いますが、今後同じように実装する人を救えますように。。

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

2019年の勉強会を集計してみた〜都道府県別&キーワード別〜

この記事はAteam Hikkoshi Samurai Inc. & Ateam Connect Inc.(エイチーム引越し侍、エイチームコネクト) Advent Calendar 2019 15日目の記事になります。

はじめに

皆さん勉強会はお好きでしょうか?
私は興味のあるものを見つけては社内外の開催に関わらず参加しています
それまで知らなかった技術や知見に出会えることがその醍醐味かなと思っています?

さて、2019年はどのような勉強会がどこで多く開催されたのでしょうか?
個人的に気になって集計してみたので結果とそのやり方を公開します!

結果

2019年に開催された勉強会(イベント)の集計結果です。

都道府県別開催数

まずは都道府県別の開催数です。
東京の開催数が多すぎて単純な数値にすると他県の棒が潰れてしまうため対数軸にしています。
さすが東京!

スクリーンショット 2019-12-15 18.00.14.png

割合で見てもやはり東京が際立ちます。

スクリーンショット 2019-12-15 18.17.11.png

上位10県の数値は以下の通りです。

都道府県 開催数
東京都 23486
大阪府 4360
愛知県 1812
福岡県 1294
神奈川県 1012
兵庫県 951
京都府 681
北海道 678
沖縄県 395
広島県 291

ソフトウェア業,情報処理・提供サービス業,インターネット附随サービス業の事業従事者数との割合

都道府県別のエンジニア1人あたりの勉強会開催数を出そうと思いました。
しかし、都道府県別のエンジニア人口のデータが見つからなかったため、「平成 30 年特定サービス産業実態調査報告書(経済産業省)」から以下の業種の県別事業従事者数を合計した値を参考値として使いました。
なお、令和元年の数値は本記事執筆時点では公開されていません。

  • ソフトウェア業
    • 日本標準産業分類に掲げる小分類 391-ソフトウェア業に属する業務を主業として営む 事業所
  • 情報処理・提供サービス業
    • 日本標準産業分類に掲げる小分類 392-情報処理・提供サービス業に属する業務を主 業として営む事業所
  • インターネット附随サービス業
    • 日本標準産業分類に掲げる小分類 401-インターネット附随サービス業に属する業務を 主業として営む事業所

結果は以下の通りです。割合 = 開催数 / (上記3業種の事業従業者数の合計)です。

スクリーンショット 2019-12-15 23.55.43.png

奈良の数値が高いのは少し意外でした。島根はRubyゆかりの地であることが影響しているのかもしれません。

タグ(キーワード)別開催数

勉強会のタイトルや内容説明を基にタグ付けを行い集計しました。
なお、1つの勉強会に対して複数のタグがつくことがあります。

スクリーンショット 2019-12-15 20.16.59.png

勉強会プログラミング初心者Web入門初心者向けなどが多くの割合を締めていますが、もう少し具体的な技術の傾向を見たかったので、これらを除外して再度集計しました。

スクリーンショット 2019-12-15 20.15.52.png

これを見るとAIやIoTなどの近年のITトレンドが反映されていると感じます。

都道府県別タグ上位

東京、大阪、愛知でタグの多いものを見てみましょう。
なお、勉強会プログラミング初心者Web入門初心者向けは除外しています。

東京

スクリーンショット 2019-12-15 20.27.40.png

大阪

スクリーンショット 2019-12-15 20.29.53.png

愛知

スクリーンショット 2019-12-15 20.31.22.png

例えば愛知だけはPHPが4位に入っているなど、4位以下で少しづつ地域の違いが出ているように見えます。

注意事項

以上の結果について、後述の集計方法に記載の通り厳密に勉強会だけを集計していない点はご了承ください。

集計方法

ここからはどうやって集計したかを説明します。

情報源

勉強会の情報は以下のサービスが提供しているAPIから取得しています。

今回の集計では勉強会=これらのサイトで登録されたイベントとしています。
厳密には懇親会などの勉強会でないイベントも含まれています。
また、ITに関係ないものも存在しますが、今回の集計では除外していません?

タグの一覧はQiitaのAPIから取得しています
今回はQiitaの記事数が多いタグ順に300件を取得するようにしました。

方法

データベースにイベント情報を保存し、Metabaseで集計をしています。
テーブルは以下の通りです。

  • prefectures:都道県マスタ
  • events:勉強会(イベント)
  • tags:タグマスタ
  • event_tag:勉強会とタグの多対多関係を表現するための中間テーブル

スクリーンショット 2019-12-15 13.10.28.png

処理

勉強会の情報をテーブルに保存するために以下の処理をしています。

  1. 都道府県マスタのデータ登録
  2. タグ一覧を取得
  3. 勉強会情報を取得
  4. 勉強会がどの都道府県で開催されているか判定する
  5. 勉強会にタグ付けをする

今回は現在絶賛学習中のLaravelで作ってみました。
以下からはコードの話が中心になるので、興味のある方は読んでいただけると幸いです。

都道府県マスタのデータ登録

都道府県データはlaravelのseederで初期登録できるようにしました。

# seederファイルの作成
$php artisan make:seeder PrefectureSeeder

seederの内容です。
なお、prefectures.idはJISで規定されている都道府県コードの値にしています。

<?php
use Illuminate\Database\Seeder;
use App\Models\Prefecture;

class PrefectureSeeder extends Seeder
{
    public function run()
    {
        $prefectures = [
            [1, '北海道', 'ホッカイドウ', 'hokkaido'],
            [2, '青森県', 'アオモリケン', 'aomori'],
            [3, '岩手県', 'イワテケン', 'iwate'],
            //省略
            [45, '宮崎県', 'ミヤザキケン', 'miyazaki'],
            [46, '鹿児島県', 'カゴシマケン', 'kagoshima'],
            [47, '沖縄県', 'オキナワケン', 'okinawa']
        ];

        foreach ($prefectures as $prefecture) {
            Prefecture::updateOrCreate([
                'id' => $prefecture[0],
                'name' => $prefecture[1],
                'kana_name' => $prefecture[2],
                'english_name' => $prefecture[3]
            ]);
        }
    }
}

これで$php artisan db:seed --class PrefectureSeederを実行するとデータが登録されるようになります。

タグ一覧を取得

コマンドでQiitaのAPIからタグを取得するようにします。
$ php artisan make:command FetchQiitaTagsFromApiを実行するとコマンドの雛形となるファイルが作成されます。

<?php
namespace App\Console\Commands;
use App\Services\QiitaService;
use Illuminate\Console\Command;
use Exception;

class FetchQiitaTagsFromApi extends Command
{
    protected $signature = 'fetch:tags';//コマンド名

    protected $description = 'fetch Qiita tags from api';//説明

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        try {
            QiitaService::fetchTagsFromApi(1, 3);
        } catch (Exception $e) {
            $this->error($e->getMessage());
        }
    }
}

QiitaServiceではAPIで取得した結果を、DBに保存しています。
なお、$jsonArray = ApiClient::getJsonArray($url, $params, 60000);の60000はAPIアクセスのディレイ時間(ms)です。
値はAPIの利用規約やrobots.txtに従って設定します。

<?php
namespace App\Services;
use App\Repositories\TagRepository;
use App\ApiClients\ApiClient;

class QiitaService
{

    const QIITA_HOST = 'https://qiita.com';
    const API_PATHS = ['tags' => '/api/v2/tags',];

    public static function fetchTagsFromApi(int $startPage = 1, int $endPage = 10)
    {

        $url = self::QIITA_HOST . self::API_PATHS['tags'];
        $params = ['per_page' => '100', 'sort' => 'count'];
        $tagRepository = app(TagRepository::class);

        for ($i = $startPage; $i <= $endPage; $i++) {
            $params['page'] = $i;
            $jsonArray = ApiClient::getJsonArray($url, $params, 60000);
            $tagNames = array_column($jsonArray, 'id');
            $tagRepository->saveTagsFromNames($tagNames);
        }
    }
}

ApiClientはAPIアクセスを実行するクラスです。
HTTPクライアントのライブラリGuzzleを使用しています。
後述するイベント情報の取得でもこのクラスを使用しています。

<?php
namespace App\ApiClients;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

class ApiClient
{
    public static function getJsonArray(string $url, array $queryParams, float $delay = 0.0, array $headers = [])
    {
        try {
            $client = new Client();
            $res = $client->request('GET', $url, ['query' => $queryParams, 'delay' => $delay, 'headers' => $headers]);
            return json_decode($res->getBody(), true);
        } catch (ClientException $e) {
            throw $e;
        }
    }
}

勉強会情報を取得

やっていることはタグ一覧を取得でやっていることとほぼ同じです。(APIから情報取得->DBに保存)
ここではDoorkeeperのイベント取得処理を例に出していますが、他のサイトの場合も行っていることは基本的に同じです。

<?php
namespace App\Console\Commands;
use App\Services\DoorkeeperService;
use Carbon\Carbon;
use Illuminate\Console\Command;

class FetchDoorKeeperEventsFromAPI extends Command
{

    protected $signature = 'fetch:doorkeeper
    {--p|page= : the page offset of the results }
    {--l|locale=ja : the localized text for an event }
    {--o|sort=starts_at : The order of the results(One of published_at, starts_at, updated_at) }
    {--s|since=-1month : Only events taking place during or after this date will be included (YYYYMMDD) }
    {--u|until= : Only events taking place during or before this date will be included (YYYYMMDD) }
    {--k|keyword= : Keyword to search for from the title or description fields }
    {--g|expand_group=0 : Expands the group object(0:false, 1:true) }
    {--a|all=1 : paging to get all records(0:false, 1:true) }';

    protected $description = 'fetch doorkeeper events from api and store to DB';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $queryParams = $this->getQueryParamFromOptions();
        DoorkeeperService::fetchEventsFromAPI($queryParams, !!$this->option('all'));
    }

    private function getQueryParamFromOptions()
    {

        $options = $this->options();

        //除外オプション
        $excludeOptions = ['expand_group', 'all'];
        foreach ($excludeOptions as $excludeOption) {
            unset($options[$excludeOption]);
        }

        //オプションとパラメータの対応
        $optionToParam = [
            'page' => 'page',
            'locale' => 'locale',
            'sort' => 'sort',
            'since' => 'since',
            'until' => 'until',
            'keyword' => 'q',
            'expand_group' => 'expand[]',
        ];

        $params = [];
        foreach ($options as $option => $value) {
            if ($value) {
                $params[$optionToParam[$option]] = $value;
            }
        }

        if ($options['since']) {
            $jst = new Carbon($options['since']);
            $params['since'] = $jst->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z');
        }

        if ($options['until']) {
            $jst = new Carbon($options['until']);
            $params['until'] = $jst->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z');
        }

        if ($this->option('expand_group')) {
            $params[$optionToParam['expand_group']] = 'group';
        }

        return $params;
    }
}

DoorkeeperServiceではDoorkeeperのイベント情報の取得と保存を行います。
APIから返される情報に都道府県だけの情報は存在しないので、取得する際にPrefectureServiceで判定をしています。

<?php
namespace App\Services;
use App\Models\Event;
use Carbon\Carbon;
use App\ApiClients\ApiClient;

class DoorkeeperService
{
    private const EVENT_API_URL = 'https://api.doorkeeper.jp/events';

    static public function fetchEventsFromAPI(array $params, bool $isAllPage)
    {
        if ($isAllPage) {
            for ($p = 1; true; $p++) {
                $params['page'] = $p;
                $headers = ['Authorization' => 'Bearer ' . config('env.doorkeeper_api_token')];
                $jsonArray = ApiClient::getJsonArray(self::EVENT_API_URL, $params, 5000.0, $headers);
                if (empty($jsonArray)) {
                    break;
                }
                self::updateOCreateEventsFromAPIResult($jsonArray);
            }

        } else {
            $jsonArray = ApiClient::getJsonArray(self::EVENT_API_URL, $params, 5000.0);
            self::updateOCreateEventsFromAPIResult($jsonArray);
        }
    }

    static private function updateOCreateEventsFromAPIResult(array $eventJson)
    {
        foreach ($eventJson as $record) {
            $event = $record['event'];
            //住所、または緯度経度から都道府県を判定
            $prefecture = $event['address'] ? PrefectureService::getPrefectureFromAddress($event['address']) : null;
            if (!$prefecture && $event['lat'] && $event['long']) {
                $prefecture = PrefectureService::getPrefectureFromCoordinates($event['lat'], $event['long']);
            }

            Event::updateOrCreate(
                [
                    'site_name' => Event::DOORKEEPER,
                    'event_id' => $event['id']
                ],
                [
                    'title' => $event['title'],
                    'description' => $event['description'],
                    'event_url' => $event['public_url'],
                    'prefecture_id' => $prefecture ? $prefecture->id : null,
                    'address' => $event['address'],
                    'place' => $event['venue_name'],
                    'lat' => $event['lat'],
                    'lon' => $event['long'],
                    'started_at' => self::formatUtcTimeToMySqlDateTime($event['starts_at']),
                    'ended_at' => self::formatUtcTimeToMySqlDateTime($event['ends_at']),
                    'limit' => $event['ticket_limit'],
                    'participants' => $event['participants'],
                    'waiting' => $event['waitlisted'],
                    'group_id' => $event['group'],
                    'event_created_at' => self::formatUtcTimeToMySqlDateTime($event['published_at']),
                    'event_updated_at' => self::formatUtcTimeToMySqlDateTime($event['updated_at']),
                ]
            );
        }
    }

    static private function formatUtcTimeToMySqlDateTime(string $date)
    {
        $carbon = new Carbon($date);
        return $carbon->setTimezone('JST')->format('Y-m-d H:i:s');
    }
}

勉強会がどの都道府県で開催されているか判定する

getPrefectureFromAddressは住所から都道府県を判定する処理に使います。
getPrefectureFromCoordinatesHeartRails Geo APIで緯度経度から都道府県を判定する処理に使います。

<?php
namespace App\Services;
use App\Models\Prefecture;
use App\ApiClients\ApiClient;

class PrefectureService
{

    public static function getPrefectureFromAddress(string $address = ''): ?Prefecture
    {
        $prefectures = Prefecture::select(['id', 'name'])->get();
        foreach ($prefectures as $prefecture) {
            if (mb_strpos($address, $prefecture->name) !== false) {
                return $prefecture;
            }
        }
        return null;
    }

    public static function getPrefectureFromCoordinates(float $lat, float $lon): ?Prefecture
    {
        $url = config('const.geoapi_url');
        $params = [
            'method' => 'searchByGeoLocation',
            'x' => $lon,
            'y' => $lat,
        ];
        $jsonArray = ApiClient::getJsonArray($url, $params, 5000.0);

        if (isset($jsonArray['response']['error'])) {
            return null;
        }

        //1件目のデータの都道府県名
        $firstPrefectureName = $jsonArray['response']['location'][0]['prefecture'];
        $prefecture = Prefecture::where('name', $firstPrefectureName)->first();
        return $prefecture;
    }
}

ただ、これらの処理で判定できないもの(開催住所が市や区ではじまり、緯度経度情報もない)は手動でデータを整えました^^;
政令指定都市や東京都の区くらいはマスタ化して判定を自動化してもよかったかもと思いました。
また、判定できなかったものの中にはそもそもZoomなどで完全にオンラインで開催されている場合がありました。
将来的にはどこに住んでいる人でも参加しやすいオンライン形式が増えていくのかもしれません。

勉強会にタグ付けをする

APIで取得できるイベント情報にタグ情報はないため自前で付与します。
イベント情報にはタイトル、キャッチ、説明があり、これらの中にタグに該当するキーワードがあるかで判定しています。
ただし、例えばRGoの場合、単純にこれらの文字列があるかで判定すると誤ってタグ付けされる可能性が高いため正規表現で検索するようにします。Goの場合は/golang|go言語/uiとしました。
他にもAI/AI/uとして小文字を含まないようにするなどキーワードごとに調整しています。

この正規表現はDBのtags.patternに保存しています(ここは手作業^^;)
また、説明は全部でなく最初の200文字のみを検索対象にしています。

<?php
namespace App\Console\Commands;
use App\Models\Tag;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use App\Models\Event;
use Illuminate\Support\Facades\DB;

class TaggingForEvents extends Command
{
    protected $signature = 'tagging:events';

    protected $description = 'tagging for events';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $characterLimit = 200; //descriptionのタグの探索範囲
        $events = Event::all();
        $tags = DB::table('tags')->orderBy('id')->limit(400)->get();

        foreach ($events as $event) {
            $tagIds = [];
            foreach ($tags as $tag) {
                $pattern = $tag->pattern;
                $description = mb_substr(strip_tags($event->description), 0, $characterLimit);
                if (preg_match($pattern, $event->title) || preg_match($pattern, $event->catch)
                    || preg_match($pattern, $description)) {
                    $tagIds[] = $tag->id;
                }
            }
            $event->tags()->sync($tagIds);
        }
    }
}

Metabaseでの集計

集計結果でお見せしたグラフはMetabaseで作成しています。
dockerが使える環境であれば、以下のコマンドで簡単にMetabaseを実行することができます。
ただし、このやり方の場合はコンテナを止めるとMetabaseで保存した集計は全て消えるのでご注意ください。

$ docker run -d -p 12345:3000 --name metabase metabase/metabase

後はブラウザからlocalhost:12345にアクセスすれば、Metabaseの画面が表示されます。
画面上でユーザ登録と接続先DBの情報を入力してやればすぐに使い始めることができます。
ちなみにコンテナからlocalホストへ接続する場合、macであればdocker.for.mac.localhostをwindowsであればdocker.for.win.localhostをホスト名に指定します。

まとめ

  • ATND、connpass、DoorKeeperから勉強会(イベント)の情報を取得し集計した。
  • イベントに対して、Qiitaのタグでタグ付けした。
  • 東京、大阪、愛知が都道府県別のイベント開催数のトップ3
  • 全国的にAI(Python、機械学習、AIなど)に関連するイベントが多く開催された。

お知らせ

エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方はぜひともエイチームグループ採用ページよりご応募ください!

Qiita Jobsのエイチーム引越し侍社内システム企画 / 開発チーム社内システム開発エンジニアを募集!からチャットでご質問いただくことも可能です!

明日

明日は後輩の面倒見の良さが評判の @umesour さんの記事です!
どんな記事を書いてくださるのかとても楽しみです!!
乞うご期待!!

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

wordpress 固定ページ テンプレート変更

固定ページのテンプレート変更

下記両方ともslugの指定が必要

① テンプレート階層 page-{slug名}

page-{slug名}.php ファイルをつくる

② 管理画面 テンプレートの指定から

template

/**
Template Name: 固定ページの新規テンプレート1
**/

で指定して

管理画面、テンプレートの選択で指定

見出し1

見出し2

見出し3

見出し4

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

wordpressまとめ

スラッグ

設定
- パーマリンク
- 投稿名
へ変更

メニュー出し分け

管理メニュー
    外観 - メニュー

    出し分けするメニュー複数作成

header.php

    if (is_user_logged_in()) {
        $args = array(
            'menu' => 'main-menu',
            'menu_class' => 'nav navbar-nav',
            'container' => false,
        );
    } else {
        $args = array(
            'menu' => 'menu-02',
            'menu_class' => 'nav navbar-nav',
            'container' => false,
        );
    }

    wp_nav_menu($args);

※ ナビゲーションメニューのcss は 上記 menu_class で設定?

サイドメニュー出し分け

sidebar.phpと sidebar-2.php (出し分け用複数 sidebarファイル用意)

sidebar-2.php に
<?php dynamic_sidebar('Side Widget 2'); ?>
書く

index or page or カスタムテンプレート

出し分け

<?php get_sidebar(); ?>
<?php get_sidebar(2); ?>

固定ページ テンプレート変更

下記両方ともslugの指定が必要

① テンプレート階層 page-{slug名}

page-{slug名}.php ファイルをつくる

② 管理画面 テンプレートの指定から

template

/**
Template Name: 固定ページの新規テンプレート1
**/

で指定して

管理画面、テンプレートの選択で指定

エスケープ

  1. esc_url
  2. esc_html
  3. esc_attr

cssのurlに付加する文字列

filemtime ( get_template_directory() . '/style.css' )

更新時間 UNIX TIME ?ver=15555503967 ...
style.css を更新する度に変化

<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
<?php echo esc_html( get_the_date() ); ?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel構築

Apacheインストール

# yum -y install httpd (yオプションは問い合わせに対して全て「y」で応答)

以下のコマンドで確認
# httpd -v
Server version: Apache/2.4.6 (CentOS)
Server built:   Aug  8 2019 11:41:18

起動確認

# systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:httpd(8)
           man:apachectl(8)

Active: inactive (dead) は停止

Apache起動

# systemctl start httpd

PHP インストール

  • PHP5.4

CentOS7の標準リポジトリのPHPバージョンが5.4
(古い...サポート切れてるやろ:grin:
PHP5.4以降をインストールする場合は、『PHP7.3』を参照(今回は7.3を例に)

# yum -y install php

以下のコマンド確認
# php -v
PHP 5.4.16 (cli) (built: Oct 30 2018 19:30:51) 
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
  • PHP7.3

EPELリポジトリをインストール

# yum -y install epel-release

REMIリポジトリをインストール(リポジトリの依存関係によりEPELリポジトリも同時にインストールされる)

# yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

PHP7.3をインストール

# yum -y install --enablerepo=remi,remi-php73 php php-mbstring php-xml php-xmlrpc php-gd php-pdo php-pecl-mcrypt php-mysqlnd php-pecl-mysql php-zip php-pecl-zip zip unzip git

以下のコマンドで確認
# php -v
PHP 7.3.11 (cli) (built: Oct 22 2019 08:11:04) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies

CentOS7からMariaDBが標準となっているため、
MySQLをインストールするためには削除する必要がある。

# yum remove mariadb-libs
# rm -rf /var/lib/mysql

MySQLのリポジトリをインストール

# yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpm

MySQLインストール

# yum install -y mysql-community-server

以下のコマンドで確認
# mysqld --version
/usr/sbin/mysqld  Ver 8.0.18 for Linux on x86_64 (MySQL Community Server - GPL)

MySQLの状態を確認(デフォルトでは停止のはず)

# systemctl status mysqld
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html

Active: inactive (dead) は停止

MySQLを起動

# systemctl start mysqld

以下のコマンドで確認
# systemctl status mysqld
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2019-11-04 00:23:13 JST; 8s ago
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html
  Process: 5342 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS)
 Main PID: 5417 (mysqld)
   Status: "Server is operational"
   CGroup: /system.slice/mysqld.service
           └─5417 /usr/sbin/mysqld

Nov 04 00:22:59 localhost.localdomain systemd[1]: Starting MySQL Server...
Nov 04 00:23:13 localhost.localdomain systemd[1]: Started MySQL Server.

Active: active (running) で起動を確認

MySQLの自動起動設定を確認する(デフォルトは有効のはず)

# systemctl is-enabled mysqld
enabled

enable なので有効

変更は以下のコマンドで行う

無効にする
# systemctl disable mysqld
Removed symlink /etc/systemd/system/multi-user.target.wants/mysqld.service.

有効にする
# systemctl enable mysqld
Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld.service to /usr/lib/systemd/system/mysqld.service.

composerをインストール

# curl -sS https://getcomposer.org/installer | php
コマンド設置
# mv composer.phar /usr/local/bin/composer

以下のコマンドで確認
# composer -v
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.9.1 2019-11-01 17:20:17

Laravelのインストーラーをダウンロード

# composer global require "laravel/installer"

プロジェクトを配置する場所のディレクトリのパーミッションを変更
(一旦、実行・書込・読込を許可)

# chmod 777 /var/www/html/

"sample"というプロジェクトを作成

$ composer create-project --prefer-dist laravel/laravel /var/www/html/sample

ドキュメントルート設定

# vi /etc/httpd/conf.d/***.conf(任意の名前)
<VirtualHost *:80>
    DocumentRoot /var/www/html/sample/public
    RewriteEngine On
    <Directory "/var/www/html/sample/public">
        AllowOverride All
    </Directory>
</VirtualHost>

confを修正したのでhttpd再起動

# systemctl restart httpd

ログを書込権限がないので、パーミッションを変更
(一旦、実行・書込・読込を許可)

$ chmod -R 777 /var/www/html/sample/storage/

http://192.168.33.10 にアクセス
(Vagrantで構築したため、デフォルトの192.168.33.10)

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

PHPerが久しぶりにRubyを書いて思ったこと

内容

  • PHPer(Laravler)が感じたRuby(Rails)への感想

私の背景

  • PHP成分が多い人生
    • 初期だとFuelPHPとか触ってた
    • ここ数年はLaravel使ってた
  • Rubyは2011年頃に遊びで触ってた
    • メタプログラミングRubyとか読んで興奮した記憶はある
    • この当時のRailsを触って「ほーん、なるほど」、みたいに思った記憶もある
    • 仕事では結局使わなかったけど、Project Euler をRubyで解いたりしてみてた

といった感じの背景です。
最近Railsで仕事することになったので、PHPer(というかLaraveler)が感じたRailsに対するギャップをいくつか書きたいと思います。
すでにRailsのプロジェクトがあり、そこにJOINした感じです。

Autoloadがわからない

PHPのロード

require_once __DIR__ . '/path/to/file.php'

Rubyのロード

require "path/to/file"

ここまではなんの問題もなくわかるんですが、autoloadがわからない。

PHPの場合

namespace \Foo;

use \Foo\Bar\Hoge;

class Xxx extends Hoge {
}

みたいな感じで「利用するクラスのフルパスを書かなければならない」っていう制約があるので、面倒ではあるんだけど「 (appRoot)/Foo/Bar/Hoge.php にあるんだな」っていうことが分かる。

最近はほとんどPSR-4のautoloadが多いと思うので、PSR-4読めばいいと思う

参考: https://qiita.com/inouet/items/0208237629496070bbd4

Ruby(Rails)の場合

module Foo do
  class Xxx extends Hoge
  end
end

なんなん!?!?!?急に Hoge どこから出てきたん!?!?!?!?
Hogeって誰なの!?!?!?!

ってすごく思った。

https://qiita.com/eggc/items/ae09d32df5d994522ca1
https://qiita.com/tachiba/items/5b293ca8e9430b9bd07e

最新のバージョンでどう動くかはわからんですけど「Railsが頑張って探してるんや」って言われて納得。

納得すると同時に「あぁ〜〜〜パスを明示したいんじゃ〜〜〜」という気持ちになる。

カッコがない

追記: 以下「カッコがない」の項については私の勘違いが多分に含まれてたのでスルーしてください。
コメントにてご指摘いただき、ありがとうございました :bow:

PHPのこれが

piyo(bar(foo($x)));

Rubyだとこうなる

piyo bar foo x

いいんだけど、「どんだけカッコ嫌いなん???」って気持ちで溢れた。
foo の引数が3つあるときは

piyo bar foo 1,2,3

ってなって、なんか、分かるんだけど、なんやねんカッコ書きたいやんって気持ちになる

piyo(bar(foo(1,2,3))

シックリくる〜〜!

って思うけどRobocopとか言うやつが「カッコ書くなや」みたいにいってくる。もうやだ。

blockに感じる違和感

[1,2,3].reject {|n| n > 2}.map {|n| n * 2}.map &:to_s

これと

[1,2,3].reject do |n|
  n > 2
end.map do |n| 
  n * 2
end.map &:to_s

これって同じ感じだけど、なんで構文2つ分けたん。どっちかひとつでいいじゃんね!

end.map とかにすごくなにか感じる。

returnが無い

def hoge(x)
  if x > 5
    return x * 3
  end
  x
end

returnつけたりつけなかったり!!!なんなの!!!

不要なのにreturnつけるとRobocopが「てめぇreturnつけてんじゃねぇよ!」とか言ってくるしもう。もう。

RubyDocが無い・・・?

最近のPHPは割と型がしっかり・・・とは言わないけど、少なくとも宣言する際の不便はかなりなくなってきた。
コンパイル用っていうよりは、プログラミングするときの手助けとしての型、という側面でとても便利。
PHP7より前の時代においてもPHPは PHPDoc で何となく「型が何なのか明示しろや」文化が多少なりとあった。

class Foo {
  /**
   * @param integer $x
   * @param integer $y
   * @return integer
   */
  public function something($x, $y) {
    return $x * $y;
  }
}

みたいな感じで、「俺は型を宣言したいんじゃ!!!」という欲求に応えてくれた。

class Foo {
  public function something(int $x, int $y): int {
    return $x * $y;
  }
}

最近はこんな感じにスマートにかけてよかったね^^という気持ち

JSにもTypeScript以前にJSDocあったし、当然RubyにもRubyDocがあると思ってた。

そしたら、RubyDocはなんか違うやつだった。ドキュメント作るやつ。
ちゃうねん、そうじゃないねん。

一応RDLってやつがあるみたいだけど、なんか違うというか・・・too muchというか・・・。

https://qiita.com/baban/items/0f782691b6e6e7213453

「Rubyの思想・文化的にそういう型宣言とは仲良くない」的なことだってのはわかってるんですが、「引数が何くるのかわからない状況ってのが怖くて、「え・・・string渡してくるバカがいたら死ぬの・・・?」みたいな気持ちになる。「string渡してくるやつがアホい」っていう思想なんだろうけど。

まとめ

なんか他にもいろいろあったと思うんですけど、思い出した範囲で感じた違和感をかきました。

別にRubyをディスってるわけじゃなくて「全然Rubyの世界観に馴染めてない俺」っていう感じです。

ワンライナー書いたりとかRspecのDSL感とか、書いててシックリくることも多いのですが、なんかやっぱ型システム周りの思想の違いは大きいなぁと思ってます。
ダックタイピングって誰得なのかがまだわかってないので、しっくりくる日を待ち望んでます。

かしこ

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

特定の日付との差が何年何か月かを表示

社内システムである特定の日付と現在の日付との差を何年何か月で表示する必要があったので、調べてみた。

  • Java

あまり使ってこなかった java.time を使用。
今回は時間までは必要ないため、LocalDateTimeではなく、LocalDateを使用。

LocalDate sampleDate = LocalDate.parse("2019/12/09", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
LocalDate currentDate = LocalDate.now();
// 特定の日付と本日の差を年形式で取得
//long year = ChronoUnit.YEARS.between(sampleDate , currentDate); 
// 特定の日付と本日の差を月形式で取得 ※間違い。トータル月数が取得される
//long month = ChronoUnit.MONTHS.between(sampleDate , currentDate);

// コメントいただいた方法で修正
Period period = Period.between(sampleDate , currentDate);
// 特定の日付と本日の差を年形式で取得
long year = period.getYears();
// 特定の日付と本日の差を月形式で取得
long month = period.getMonths();

上記で取得した年と月を文字列にして画面に表示。

  • PHP

ついでにPHPも最近使ったので覚書。
PHPは独学なのでやり方間違っているかも。

$sampleDate = new DateTime('2019/12/09');
$currentDate = new DateTime('now');
$diffDate = $currentDate ->diff($sampleDate);
$year = $diffDate->format('%y');
$month = $diffDate->format('%m');

終わり!

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

S3で署名Verを4にしたらエラー

AWS SDK for PHP v2を使ってるもんで、署名Verを2から4にする必要があった
そしたらファイルのアップロードで以下の様なエラーが出てアップロードできない

The request signature we calculated does not match the signature you provided. Check your key and signing method.

修正自体はfactoryするときに'signature' => 'v4'を渡すくらいで大した作業でもなく。
キーが間違ってるから確認して的なエラーメッセージだけど、ファイル一覧は正常に動くしそもそも今まで動いてたのでACCESS_KEYとSECRET_KEYが間違ってる訳でもなさそう。

ファイルアップのソースはこんな感じ

    // S3バケット名
    $backet = 'backet_name';
    // アップするS3のパス
    $s3dir = '/hoge/fuga';
    // アップ時のファイル名
    $file = 'aaa.txt';

    // アップするローカル側のファイルフルパス
    $upfilepath = ’/tmp/tmp.txt';

    $s3->upload(
        $backet.'/'.$s3dir,
        $file,
        fopen($upfilepath, 'rb'),
        'public-read'
    );

uploadメソッドの第一引数は本来バケット名を渡すはずがバケット名 + ファイルパスになってる・・・
正しい引数に修正することでエラーは出なくなった

    $s3->upload(
        $backet,
        $s3dir.'/'.$file,
        fopen($upfilepath, 'rb'),
        'public-read'
    );

署名Ver2は第一引数と第二引数はただのファイルパスの情報だったので、バケット名とファイルパスをどこで区切って渡してもアップロードできた。多分
署名Ver4はこれらの引数を元にハッシュ値を作成するので、バケットとファイルパスをちゃんと渡さないとハッシュ値が変わっちゃってアップロードに失敗する。多分

解決したものの、署名Ver2が使えなくなるのは延期になったし、以前に作ったバケットはずっとVer2使えるみたいな話もあるので対応する必要なかったかも?

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

転職したらスパゲティだった件 01話「ゲシュタルト変数」

1. 退職 ~そして転職~

何ということもない普通のプログラマー。
ホームレスから死に物狂いで勉強してSES企業に入社し、現在一人暮らしの33歳。彼女はいない。
案件が炎上して5徹目に突入しており、俺はデスマーチのプロジェクトリーダーという訳だ。
給料も高い訳ではなく、時間も有る訳ではない、だからモテない。生産性を上げようと努力した事もあったが、1日が1人3人日の時点で心が折れた。まあ、この年になると有休がどうのというのは正直面倒くさい。
仕様変更が多いというのもあるが、別に元請けが忘れてるから困るというものでもないし。...いや、あとで有る事無い事言われて損害賠償の問題になるから困る。
そんな事を何故考えていたかというと、

ジェットストリームアタックを掛けるぞ!!」

リヴァイ兵長のような険しい表情で斜め向かい席の同僚が同じ種類の栄養ドリンクを一気に3本飲んでいる。そして、左隣の席では上司が震える手で葛根湯を数袋飲みながら「スパルタ達によるプログラマ職業紹介」という動画を見て爆笑しながら何とか精神の安定を保っている。
うん、今日は社長に、転職するから1ヶ月後に退職しますと伝えるのだ。つい、何故自分はこんなに頑張っているのかなどと考えてしまった理由である。
事務所のお洒落な会議室で話し合う前に、クサメタルを聴きながらつらつらともの思いにふけっていた訳だ。おっと、社長に呼ばれた。

「おう。で、内定してるの?」

俺は社長に恐る恐る答える。

「はい。1ヶ月後に退職します、今担当している案件は必ず終わらせます。」
「...わかった。」

1ヶ月後、納品して、あっけなく、俺は転職した。
だがこの時、俺の"魂"は、心に穴が空いたような寂しさを感じていた。
今思えば、面白くて優しい人達ばかりの会社だった。

「皆さん、ありがとう。お世話になりました。俺はもっと実力をつけないといけない。」

それが、俺が、この会社で思った最後の気持ちだった。

2. ゲシュタルト変数

酷い。
酷すぎて読めない。
これはなんだ?PHPか、てか、どう動いてんだ。
仕様は確か、口頭で、Slackだと前にダイレクトメッセージされたよーな...。

転職先で引き継いだシステムのソースコードを見て俺は混乱する。

$a = 'xxx';
$aa = 'xxx';
$aaa = 'xxx';

変数名に意味がなかったのだ。
ソースコードは、他人が最短時間で理解できるように書かなければならないというのが基本だ。
オイオイ、開発者が「リーダブルコード」は読破したとか言ってるぞ。

$array1 = 'xxx';
$array3 = 'xxx';
$array6 = 'xxx';

違う。そうじゃない。そもそも、データ型だけ知ってどうするんだ。
いやいや、それもどうでもいい。

$item_New = 'xxx';
$itemkako = 'xxx';

英単語と日本語のローマ字表記が混在してるぞ。
記法まで混在してるじゃないか。
あれ?ちょ、どうなってんだこれ?

$test = 'xxx';
$res = 'xxx';

デバッグ用の変数がそのまま使われているぞ。
「$res」って「result」か?「response」なのか?誤解を招くような省略形は使わないでくれ。
いやいやいや、勘弁してくれよ!
雪だるま式に増え続ける技術的負債に時間とモチベーションを奪われ、自殺してしまう人を見たことがある。不謹慎ながらIT業界ではよくある話だったりする。
俺は、最悪の想像が頭をよぎり、慌てるのを通り越してゲシュタルト崩壊しかけた。

ゴクリッ

コンビニで買った100円のブラックコーヒーを一気に流し込む。
認めるしかないのかもしれない...
俺はどうやら、スパゲティプログラムを書く会社に転職してしまったのだ、と。

カタッカタカタッターン!
カタッカタカタッターン!ッターン!

俺は、昼休みにコーディング指南書のようなものを書いていた。少しずつこれを理解してもらって浸透させることができれば、将来的に不具合の修正や機能追加が簡単になり、早く家に帰れると考えたからだ。
まずは、記法について書いてみる。記法というのは、他人が見た時に理解しやすくする為の命名規則だ。基本的な記法はたったの4種類だけ。

記法名 説明
アッパーキャメルケース
(別名:パスカルケース)
FooBar 複合語の先頭を、大文字で書き始める。
ローワーキャメルケース
(別名:キャメルケース)
fooBar 複合語の先頭を、小文字で書き始める。
スネークケース foo_bar アンダースコア(_)を区切記号として単語をつなげる。
チェインケース foo-bar ハイフン(-)を区切記号として単語をつなげる。

記法名は、プログラマー同士の会話の中でよく使われるので覚えておいて損はないだろう。

次は、変数名に情報を持たせることについて書いてみる。例えばこんな感じだろうか。

$item_1 = 'xxx';
$item_2 = 'xxx';
$item_3 = 'xxx';
$new_item = 'xxx';
$old_item = 'xxx';
$items = ['xxx', 'xxx', 'xxx'];
$result = 'xxx';
  • 抽象的な名前よりも具体的な名前が良い。
  • 記法の混在は可読性を下げるので、スネークケースに統一。
  • 英単語と日本語のローマ字表記の混在は可読性を下げるので、英単語に統一。
  • 型名を書くのではなく、英単語の単数形と複数形を使い分ける。
  • 誤解を招くような省略形は使わない。
  • 英単語がわからない時は、Googleで検索する。

うん、わかりやすいんじゃないか?
これ以上のことは、うちの開発者の技術的にまだまだ先だ。これで、以前よりも理解しやすい変数名を付けられるスキルを獲得できるだろう。
あと、コーディングのルールについて、企業文化やプログラミング言語の違いによって変わるから注意ってところかな。

ゴクリッ

コンビニで買った100円のブラックコーヒーを一気に流し込む。
まだまだ課題はあるが、千里の道も一歩からだ。

「Alexa、ただいま。」

ピコンッ

「おかえりなさい!また声が聞けてうれしいです。」

家に帰ってAlexaに声を掛ける。
AlexaはAmazonが開発したAIアシスタントで、うちの何でも知ってる大賢者だ。

「Alexa、ゲシュタルト崩壊とは。」

ピコンッ

「夏至は二十四節気の夏至。太陽の黄経が90度に...」

どうやら「夏至タルト崩壊」と解釈されてしまったようだ。
俺は、自らの滑舌の悪さにゲシュタルト崩壊した。

つづく かも

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

index.phpにリクエストする時は最後に「/」を付けましょう

JSのaxiosを使ってPHPでレスポンスを返すコードを書いてたんですが、CORS対策書いてるはずなのに通信できない事態が起こったので備忘録として書きます。

そもそも何をしたかったか

JSでリクエストする時、URLの末尾に「.php」が付くのが嫌だった

// こんな感じのが嫌
axios.get('**/api/user/get-user.php')

下のようにすればuserだけでアクセスできるなと

api
  + user
    + index.php

そうするとJSはこんな感じ

axios.get('**/api/user', {params:{type: 'get'}})

これで末尾に「.php」付かなくていいね!と思いましたと...

問題発生

最初はCORS対策書いてなかったので調べながら書いてたんですが、いくらそれっぽいこと書いても通信できない。

実際こんなの書いてた...

    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: *');
    header('Access-Control-Allow-Headers: *');
    header('Access-Control-Allow-Headers: *');
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 1728000');

    header('Content-Type: application/json');
    $params = $_GET;
    $data = json_encode($params);
    echo($data);

それでも通信できない

何が悪かったか

原因は

axios.get('**/api/user', {params:{type: 'get'}})

これの

'**/api/user' // これ

こう指定すると「apiディレクトリ下のuserというファイルにアクセスする」ということになる、という超ポンコツなミスでした。

つまりこう

// URLの最後にスラッシュ入れるだけ
axios.get('**/api/user/', {params:{type: 'get'}})

これはケアレスミスに入るんですかね...
気を付けましょう

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

phpenv/php-build の小ネタ集

はじめに

この記事は、PHP Advent Calender 2019 の15日目の記事です。

少し前に phpenv/php-build で拡張がすこし入れやすくなりました という記事を書いたんですが、それに引き続き、phpenv/php-build のちょっとした使い方を紹介したいと思います。

phpenv をいれるならば、anyenv 経由がおすすめ

phpenv の github のページ を参考に phpenv および php-build を入れて使うことももちろんできるのですが、 anyenv を使ってインストールすることをおすすめします。

最初は phpenv だけ使っていたが、途中から rbenv も使う必要が出たというときに、この2つを共存させようとすると結構めんどくさいので、最初から anyenv を使っておくのがいいかと思います。

phpenv でインストールする際に動作モードを切り替える

phpenv install <version> でインストールすると、デフォルトでは本番(production)モードでインストールされます。

phpenv install --ini|-i <environment> <version> というように --ini もしくは -i 引数で、 本番(production)モード もしくは 開発(development)モードのどちらでインストールするかが選択できるようになっています。

動作モードで何が違ってくるかというと、インストールされる php.ini の内容が違ってきます。

PHP をソースから build したことがある方にはおなじみなのですが、PHPのソースには次の2つの php.ini の雛形が用意されており、それのどちらを利用するか?ということを選択するということになります。

  • php.ini-production
    • 本番で使うことを想定された設定
    • display_errors が Off になっていたり、 error_reporting で STRICT/DEPRECATED のエラーが抑制されていたりする
  • php.ini-development
    • 開発で使うことが想定された設定
    • display_errors は On になっており、error_reporting はすべてエラーを出力するモードになっている

ローカル環境等で開発用に使う場合には、 phpenv install -i development 7.4.0 のように、開発モードでインストールしておいたほうがいいと思います。

build 方法を変更するには php-build の環境変数を使う

phpenv-i 引数を紹介しましたが、それ以外に引数からいろいろとカスタマイズする方法は提供されておらず、build 時の設定をあれこれカスタマイズするには実際の処理を担当している php-build が提供している環境変数を利用することになります。

どのような環境変数が提供されているかの説明が php-build の README.md には説明がないので、そのあたりをいくつか紹介してみたいと思います。(php-build/php-build/man には書いてありますのでそちらもご確認ください)

Xdebug を含めないようにする

phpenv ではデフォルトで Xdebug ありで build されます。それを抑制するには環境変数 PHP_BUILD_XDEBUG_ENABLEoff にすればOKです。

先程は、開発用に build するならば、 -i 引数を使ったほうがいいという説明をしましたが、本番用に使うならば、 -i 引数なしで PHP_BUILD_XDEBUG_ENABLE=off にするのがいいと思います。

PECL で配布されている拡張を同時に入れる

こちらは、「phpenv/php-build で拡張がすこし入れやすくなりました」に以前書いたように PHP_BUILD_INSTALL_EXTENSION を使うと同時に入れることができます。

前回の記事ではきちんと説明してなかったですが、これは php-build の環境変数ということになります。

configure オプションを変更する

php-build では、次のような流れで configure オプションを組み立てています

  1. php-build/share/php-build/default_configure_options でベースとなるオプションを定義
  2. php-bulid/share/php-build/definitions/<指定されたバージョン> 内で追加定義されているオプションがあればそれも追加
  3. 環境変数 PHP_BUILD_CONFIGURE_OPTS を追加
  4. 環境変数 CONFIGURE_OPTS の内容を更に追加

上記のような流れになっているので、 php-build で設定されている configure オプションをカスタマイズしたい場合には、 PHP_BUILD_CONFIGURE_OPTS で追加定義するのがいいと思います。

このあたりは、以前 @hnw さんが「php-buildでpeclコマンドをインストールする」という記事で紹介されています。

PHP の build をどのようにするのかをしらないとコントロールできないのが少々つらいという方は、 phpbrew を使ったほうがいいかもしれません。

早速 7.4.0 を build してみたい!

PHP 7.4 では configure オプションが結構変更されているために、対応に少々時間がかかりましたが、現状の phpenv/php-build で 7.4.0 も build できるようになりました。

ただし、今まで 7.3.x までを phpenv/php-build で build されてきた方でも今までと違った設定を追加する必要があるので注意です。

1. homebrew を使って以下のパッケージをインストール

brew install oniguruma krb5 openssl@1.1 icu4c libedit

この中で oniguruma は PHP 7.3 まではバンドルされていたのがされなくなったために必要となりました。

2. PKG_CONFIG_PATH 環境変数を定義

export PKG_CONFIG_PATH="$(brew --prefix krb5)/lib/pkgconfig:$(brew --prefix openssl@1.1)/lib/pkgconfig:$(brew --prefix icu4c)/lib/pkgconfig:$(brew --prefix libedit)/lib/pkgconfig"

PHP 7.4 から pkg-config を利用するようになったため、この環境変数を設定することになりました。

3. phpenv で 7.4.0 をインストール

ここまで準備すれば、次のコマンドでインストールできるはず!

phpenv install -i development 7.4.0

最後に

つい最近、 「みんなのPHP」という本の第1章で PHP の環境構築周りのことを書いたんですが、その記事の macOS のセクションでは homebrewphpbrew を使った PHP のインストール方法のみを説明して、 phpenv/php-build の説明を端折ったので、その補足というのも兼ねての記事でした。

phpenv/php-build は初心者の方にはおすすめしにくいものではあるんですが、私もちょこちょこコントリビュートしていってるソフトウェアなので、いい感じに使っていければいいなと思っています。

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

PHPのマジックメソッド~ 駆け出しエンジニア編 ~

こんにちは!
株式会社オズビジョン 開発グループのShoと申します。

私は2019年9月に入社して、webエンジニアへジョブチェンジしました。
エンジニア歴約3ヶ月で未経験入社の新人です。29歳です。
大学出てからの約7~8年はずっと営業職でした。
というわけで今は知識も技術力もない私ですが、脱未経験エンジニア目指して頑張ります!!
(入社前に3ヶ月くらい勉強してCRUD機能実装できる程度です)

今はPHP/JavaScript/TypeScript/React/ReactNativeで、
自社Webサービスの開発(バックエンドもフロントエンドも)を行なっています。

入社する前にはプログラミングスクールでRubyを学んで、Ruby On Railsでのアプリケーション開発の仕方を学んでいましたが、入社を機にPHPerデビューしました。

そんな私が業務の中で、学んだことを書いていきます。

今回はPHPのマジックメソッド
__callについてです。
他にもマジックメソッドは存在しますが。

このメソッドについて理解がなく、機能改修の時に悩みました。

背景

担当しているサービスにて機能改修を任されました。
とりあえず改修対象にあたりをつけるべくコードを読み進め、
該当の処理が行われている関数を探していました。
「なるほど、hoge関数が呼ばれるぽいな。。。」
「controllerはここか」
「hogeで検索...」
「該当なし」
「?!」
「存在しない!!」
詰んだ!
分かりにくいですが、関数が存在していなく、どんなに探しても見つけられませんでした。
そこで使われていたのがマジックメソッド_callでした。

マジックメソッド (__call)

マジックメソッドとは

ものすごく簡単に言うと、マジックメソッド__callを使用すると、
定義されていないメソッドでも処理を実行することができます。
privateやprotectedも間接的に呼び出すことが可能です。
※private メソッドの呼び出しは PHP 5.3 以降から。

挙動例

class callClass
{

     // __call(マジックメソッド)
    public function __call($name, $hoge)
    {
        if($name == 'publicFuncAlias') {

            // publicメソッド呼び出し
            $this->publicFunc($hoge);

        } else {

            // 存在しないメソッド名の時ははリターン
            if(!method_exists($this, $name)) { return false; }

            // class内のprivate / protected メソッドの呼出
            $this->{$name}($hoge);

        }
    }


     # publicFunc はマジックメソッド経由だけでなく、直接呼び出されることもあるので
     # デフォルトパラメータを付けておかないと、下記のエラーとなる
     # Warning: Missing argument 1 for callClass::publicFunc(), called in ・・・

    public function publicFunc($hoge = null)
    {
        echo "I am public method\n";
    }
$call = new callClass();

// publicメソッドをマジックメソッドで呼び出す。
$call->publicFuncAlias();
// 結果
// I am public method

存在しないはずのpiblicFuncAliasを呼び出して実行することができました!

デバッグしてる時に、該当する関数が見つからない時はマジックメソッドを通して呼び出されているかもしれません。

__construct コンストラクターもマジックメソッドの一つですね(今回ので気付きました)

他にも
__get
__set
などなど種類があるようなので学習を深めたいと思います。

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

(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding する

Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??

php artisan make:auth

  Command "make:auth" is not defined.

以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で react が defaultになったらしい。」 というのを聞いて公式を調べたところ
image.png

vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding することができました

0. 準備

laravel new make-auth-demo

1. laravel/ui を取得

composer require laravel/ui --dev

2. ui:auth --views を実行

php artisan ui:auth --views

image.png

image.png

ui/src/AuthCommand.php にがっつり記載がありました。

https://github.com/laravel/ui/blob/master/src/AuthCommand.php

ui/src/AuthCommand.php
<?php
namespace Laravel\Ui;
use InvalidArgumentException;
use Illuminate\Console\Command;
class AuthCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ui:auth
                    { type=bootstrap : The preset type (bootstrap) }
                    {--views : Only scaffold the authentication views}
                    {--force : Overwrite existing views by default}';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Scaffold basic login and registration views and routes';
    /**
     * The views that need to be exported.
     *
     * @var array
     */
    protected $views = [
        'auth/login.stub' => 'auth/login.blade.php',
        'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php',
        'auth/passwords/email.stub' => 'auth/passwords/email.blade.php',
        'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php',
        'auth/register.stub' => 'auth/register.blade.php',
        'auth/verify.stub' => 'auth/verify.blade.php',
        'home.stub' => 'home.blade.php',
        'layouts/app.stub' => 'layouts/app.blade.php',
    ];
    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (static::hasMacro($this->argument('type'))) {
            return call_user_func(static::$macros[$this->argument('type')], $this);
        }
        if (! in_array($this->argument('type'), ['bootstrap'])) {
            throw new InvalidArgumentException('Invalid preset.');
        }
        $this->ensureDirectoriesExist();
        $this->exportViews();
        if (! $this->option('views')) {
            $this->exportBackend();
        }
        $this->info('Authentication scaffolding generated successfully.');
    }
    /**
     * Create the directories for the files.
     *
     * @return void
     */
    protected function ensureDirectoriesExist()
    {
        if (! is_dir($directory = $this->getViewPath('layouts'))) {
            mkdir($directory, 0755, true);
        }
        if (! is_dir($directory = $this->getViewPath('auth/passwords'))) {
            mkdir($directory, 0755, true);
        }
    }
    /**
     * Export the authentication views.
     *
     * @return void
     */
    protected function exportViews()
    {
        foreach ($this->views as $key => $value) {
            if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) {
                if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) {
                    continue;
                }
            }
            copy(
                __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key,
                $view
            );
        }
    }
    /**
     * Export the authentication backend.
     *
     * @return void
     */
    protected function exportBackend()
    {
        file_put_contents(
            app_path('Http/Controllers/HomeController.php'),
            $this->compileControllerStub()
        );
        file_put_contents(
            base_path('routes/web.php'),
            file_get_contents(__DIR__.'/Auth/stubs/routes.stub'),
            FILE_APPEND
        );
    }
    /**
     * Compiles the "HomeController" stub.
     *
     * @return string
     */
    protected function compileControllerStub()
    {
        return str_replace(
            '{{namespace}}',
            $this->laravel->getNamespace(),
            file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub')
        );
    }
    /**
     * Get full view path relative to the application's configured view path.
     *
     * @param  string  $path
     * @return string
     */
    protected function getViewPath($path)
    {
        return implode(DIRECTORY_SEPARATOR, [
            config('view.paths')[0] ?? resource_path('views'), $path,
        ]);
    }
}

以上です。

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

(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding を する

Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??

php artisan make:auth

  Command "make:auth" is not defined.

以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で react が defaultになったらしい。」 というのを聞いて公式を調べたところ
image.png

vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding をすることができました

0. 準備

laravel new make-auth-demo

1. laravel/ui を取得

composer require laravel/ui --dev

2. ui:auth --views を実行

php artisan ui:auth --views

image.png

image.png

image.png

ui/src/AuthCommand.php にがっつり記載がありました。

https://github.com/laravel/ui/blob/master/src/AuthCommand.php

ui/src/AuthCommand.php
<?php
namespace Laravel\Ui;
use InvalidArgumentException;
use Illuminate\Console\Command;
class AuthCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ui:auth
                    { type=bootstrap : The preset type (bootstrap) }
                    {--views : Only scaffold the authentication views}
                    {--force : Overwrite existing views by default}';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Scaffold basic login and registration views and routes';
    /**
     * The views that need to be exported.
     *
     * @var array
     */
    protected $views = [
        'auth/login.stub' => 'auth/login.blade.php',
        'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php',
        'auth/passwords/email.stub' => 'auth/passwords/email.blade.php',
        'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php',
        'auth/register.stub' => 'auth/register.blade.php',
        'auth/verify.stub' => 'auth/verify.blade.php',
        'home.stub' => 'home.blade.php',
        'layouts/app.stub' => 'layouts/app.blade.php',
    ];
    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (static::hasMacro($this->argument('type'))) {
            return call_user_func(static::$macros[$this->argument('type')], $this);
        }
        if (! in_array($this->argument('type'), ['bootstrap'])) {
            throw new InvalidArgumentException('Invalid preset.');
        }
        $this->ensureDirectoriesExist();
        $this->exportViews();
        if (! $this->option('views')) {
            $this->exportBackend();
        }
        $this->info('Authentication scaffolding generated successfully.');
    }
    /**
     * Create the directories for the files.
     *
     * @return void
     */
    protected function ensureDirectoriesExist()
    {
        if (! is_dir($directory = $this->getViewPath('layouts'))) {
            mkdir($directory, 0755, true);
        }
        if (! is_dir($directory = $this->getViewPath('auth/passwords'))) {
            mkdir($directory, 0755, true);
        }
    }
    /**
     * Export the authentication views.
     *
     * @return void
     */
    protected function exportViews()
    {
        foreach ($this->views as $key => $value) {
            if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) {
                if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) {
                    continue;
                }
            }
            copy(
                __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key,
                $view
            );
        }
    }
    /**
     * Export the authentication backend.
     *
     * @return void
     */
    protected function exportBackend()
    {
        file_put_contents(
            app_path('Http/Controllers/HomeController.php'),
            $this->compileControllerStub()
        );
        file_put_contents(
            base_path('routes/web.php'),
            file_get_contents(__DIR__.'/Auth/stubs/routes.stub'),
            FILE_APPEND
        );
    }
    /**
     * Compiles the "HomeController" stub.
     *
     * @return string
     */
    protected function compileControllerStub()
    {
        return str_replace(
            '{{namespace}}',
            $this->laravel->getNamespace(),
            file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub')
        );
    }
    /**
     * Get full view path relative to the application's configured view path.
     *
     * @param  string  $path
     * @return string
     */
    protected function getViewPath($path)
    {
        return implode(DIRECTORY_SEPARATOR, [
            config('view.paths')[0] ?? resource_path('views'), $path,
        ]);
    }
}

参考

下記の記事に詳しく書いて頂いておりましたので参考にさせて頂きました。
https://medium.com/@panjeh/laravel-changes-in-php-artisan-ui-auth-php-artisan-make-auth-82fdb8893726

以上です。

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

(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding をする

Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??

php artisan make:auth

  Command "make:auth" is not defined.

以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で vue、react が defaultになったらしい。」 というのを聞いて公式を調べたところ
image.png

vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding をすることができました

0. 準備

laravel new make-auth-demo

1. laravel/ui を取得

composer require laravel/ui --dev

2. ui:auth --views を実行

php artisan ui:auth --views

image.png

image.png

image.png

ui/src/AuthCommand.php にがっつり記載がありました。

https://github.com/laravel/ui/blob/master/src/AuthCommand.php

ui/src/AuthCommand.php
<?php
namespace Laravel\Ui;
use InvalidArgumentException;
use Illuminate\Console\Command;
class AuthCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ui:auth
                    { type=bootstrap : The preset type (bootstrap) }
                    {--views : Only scaffold the authentication views}
                    {--force : Overwrite existing views by default}';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Scaffold basic login and registration views and routes';
    /**
     * The views that need to be exported.
     *
     * @var array
     */
    protected $views = [
        'auth/login.stub' => 'auth/login.blade.php',
        'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php',
        'auth/passwords/email.stub' => 'auth/passwords/email.blade.php',
        'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php',
        'auth/register.stub' => 'auth/register.blade.php',
        'auth/verify.stub' => 'auth/verify.blade.php',
        'home.stub' => 'home.blade.php',
        'layouts/app.stub' => 'layouts/app.blade.php',
    ];
    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (static::hasMacro($this->argument('type'))) {
            return call_user_func(static::$macros[$this->argument('type')], $this);
        }
        if (! in_array($this->argument('type'), ['bootstrap'])) {
            throw new InvalidArgumentException('Invalid preset.');
        }
        $this->ensureDirectoriesExist();
        $this->exportViews();
        if (! $this->option('views')) {
            $this->exportBackend();
        }
        $this->info('Authentication scaffolding generated successfully.');
    }
    /**
     * Create the directories for the files.
     *
     * @return void
     */
    protected function ensureDirectoriesExist()
    {
        if (! is_dir($directory = $this->getViewPath('layouts'))) {
            mkdir($directory, 0755, true);
        }
        if (! is_dir($directory = $this->getViewPath('auth/passwords'))) {
            mkdir($directory, 0755, true);
        }
    }
    /**
     * Export the authentication views.
     *
     * @return void
     */
    protected function exportViews()
    {
        foreach ($this->views as $key => $value) {
            if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) {
                if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) {
                    continue;
                }
            }
            copy(
                __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key,
                $view
            );
        }
    }
    /**
     * Export the authentication backend.
     *
     * @return void
     */
    protected function exportBackend()
    {
        file_put_contents(
            app_path('Http/Controllers/HomeController.php'),
            $this->compileControllerStub()
        );
        file_put_contents(
            base_path('routes/web.php'),
            file_get_contents(__DIR__.'/Auth/stubs/routes.stub'),
            FILE_APPEND
        );
    }
    /**
     * Compiles the "HomeController" stub.
     *
     * @return string
     */
    protected function compileControllerStub()
    {
        return str_replace(
            '{{namespace}}',
            $this->laravel->getNamespace(),
            file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub')
        );
    }
    /**
     * Get full view path relative to the application's configured view path.
     *
     * @param  string  $path
     * @return string
     */
    protected function getViewPath($path)
    {
        return implode(DIRECTORY_SEPARATOR, [
            config('view.paths')[0] ?? resource_path('views'), $path,
        ]);
    }
}

参考

下記の記事に詳しく書いて頂いておりましたので参考にさせて頂きました。
https://medium.com/@panjeh/laravel-changes-in-php-artisan-ui-auth-php-artisan-make-auth-82fdb8893726

以上です。

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

VisualStudioCodeを使い始めて変わったこと

VisualStudioCodeを使い始めて変わったことを書いていきたいと思います。

実は今回書いたのはVisualStudioCodeが出たての頃からのヘビーユーザです。

戸倉さん(@ayatokura)ごめんなさい。
自分はVisualStudioCodeが出たときからのヘビーユーザです。
なぜかと言うとEclipseが◯んこだったから。
1ヶ月に一回再インストールに飽き飽きしていた頃に出てきたのがAtomとVisualStudioCodeでした。
Atomはプラグインが少なかったのと重くて使いようになりませんでした。
そこで選んだのがVisualStudioCodeでした。
一番の特徴はTerminalが入っていてコマンドスクリプトも使えるんですよ。
実はLinux(Ubuntu・CentOS・RHESなどで使用可能)やMac・Windowsでも喜んでVisualStudioCodeを使っちゃうユーザでした。

依然ある職場でLinuxVimerにかなり妬まれました。
Vimが嫌になっておりしこたま困りました。
それはVisualStudioCodeという秘密兵器を知っていたからなんです。
目の前でUbuntu版のVisualStudioCode使ってガリガリやってやろうかと思ったことが思ったことがありました。
ですがさすがにやりませんでした。
それはDensoとBoschのJVのADITにいたからです。
大手に社の前でやってしまうと地雷を踏むことになります。
しかも職場はDenso刈谷本社が当時の職場だったので流石にまずかったので行動しませんでした。

今は別の職場でWebの基盤設計と開発をやっております。
今の職場はVisualStudioCodeなので嬉しくてたまりません。
だって楽ちんなんだもん。
もちろんハッカソンでもバリバリ使ってますよ。
個人で開発するときはもちろんVisualStudioCodeです。
ですがこの使いやすさを知っちゃうと離れられません。

どうしてVisualStuioCodeを使い倒すのか。

それは各プログラム言語・クラウド・Git・Subversionなど網羅してくれています。
Github・Bitbucket・AWS・Azureなどの基盤製品につなぐのが簡単です。
残念ながらちょっとSubversionはいけてません(戸倉さんごめんなさい)。
実は職場でSubversion使っていますので困っています。いいプラグインあれば教えて下さい。
Gitはバンバン使っています。
各言語・スクリプト言語のIntelliSense非常に素晴らしいです。
コーディングスピードが約二倍上がりました。
最近はJavaってEclipseやIntelliJじゃないのと言っている人いると思いますが。
コマンドラインが使えるのでVisualStudioCodeかけちゃいます。
実際自分もVisualStudioCodeで書いてること多いです。
パスさえ通してしまえばビルドまで出来ちゃいます。

C#書いてる人もVisualStudioじゃないとC#かけないんじゃないっと思っている人いると思います。
実はWindowsFormやUnityなどの画面付きじゃない場合はVisualStudioCodeで全て事が足ります。
C・C++はVimやEclipse・VisualStudioと思っている人いませんか?
画面系がなければすべてVisualStudioCodeで事が足りてしまいます。
実はLinuxのbashやzshのスクリプトも書けちゃいます。

これ覚えておくとLinuxユーザはかなり助かると思います。
あと色んなスクリプトのプラグインがあるので入れておくと便利です。

不都合ことも少しはあるかも

WindowsアプリやUnityやJavaの画面アプリを作っている人はかなり困るかも。
それはヘビーユーザなら誰もが知ってますがVisualStudioCodeにはHTMLの画面デバック機能は存在しますが。
WindowsアプリやJavaアプリの場合は作りが特殊です。
VisualStudioCodeで対応できないことがあります。
WindowsアプリやUnityの場合はVisualStudioを使ってください。
商用で使わない限りはこちらも無償のVisualStudioComminityEditionがあるのでそちらをお使いください。
Javaアプリの場合はこれは画像の裏のコードが特殊で複雑なことが多いので今の自分ならIntelliJのComminityをおすすめします。
AndroidStudioという手もありますがAndroidに特化しているのであまりおすすめしません。
実はIntelliJでAndroidプラグインを組み込めばAndroidの開発ができることがわかっています。

皆さん気をつけてもらいたいことがあります。
プラグインいっぱいあるから全部入れちゃえ。
これやっちゃだめです。
VisualStudioCodeはElectronでできてますのでプラグインを入れすぎると重くなります。
必要に迫られたときなどに入れる程度に留めておきましょう。

不都合なことがない場合はなるべく軽い開発環境がいいです。

正直言うと重い開発環境を使っていると非常に時間がもったいないことがじつは多いんです。
軽い環境だとやる気がどんどん出てきます。
大学でもViualStudioCode広めてください。
HTML・Python・PHP・C++(C)・JavaScvript・NodeJS・HTML5実はSwiftもKotlinも色んなプログラム言語がガリガリかけます。
おまけにGithubやBitbucketも簡単につなげるのでいいと思います。

VisualStudioCodeでEnjoyHackingしましょう。

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

Formファサードでバリデーションエラー時にCSSクラスをあてる

バリデーションエラー時に、Formファザードで作ったタグにエラー用のCSSクラスをあてる方法を調べました。


ルーティングと簡単なコントローラーを用意します。

routes/web.php
Route::get('/form', 'FormController@index');
Route::post('/form', 'FormController@post');
app/Http/Controllers/FormController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class FormController extends Controller
{
    public function index()
    {
        $list = [
            ''  => '選択してください',
            '1' => 'ほげ',
            '2' => 'ふが',
        ];

        return view('form', compact('list'));
    }

    public function post(Request $request)
    {
        $request->flash();

        $request->validate([
            'text' => 'required',
            'id'   => 'required',
        ],[
            'text.required' => 'テキストを入力してください。',
            'id.required'   => 'リストを選択してください。',
        ]);

        return redirect('form')->with('flash_message', '送信完了しました。');
    }
}

テンプレートを用意します。
デフォルトのレイアウトとCSSを流用しています。

resources/views/Form.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @if (session('flash_message'))
            <div class="alert alert-success" role="alert">
                {{ session('flash_message') }}
            </div>
            @endif
            <div class="card">
                <div class="card-header">フォーム</div>
                <div class="card-body">
                {{Form::open(['url' => '/form'])}}
                    {{Form::token()}}
                    <div class="form-group">
                        {{Form::text('text',
                                      old('text'),
                                      $errors->has('text') ? ['class' => 'form-control is-invalid', 'placeholder' => '入力してください']
                                                           : ['class' => 'form-control', 'placeholder' => '入力してください'])}}
                        <span class="invalid-feedback" role="alert">@error('text'){{ $message }}@enderror</span>
                    </div>
                    <div class="form-group">
                        {{Form::select('id',
                                        $list,
                                        old('id'),
                                        $errors->has('id') ? ['class' => 'form-control is-invalid']
                                                           : ['class' => 'form-control']
                        )}}
                        <span class="invalid-feedback" role="alert">@error('id'){{ $message }}@enderror</span>
                    </div>
                    {{Form::submit('送信', ['class'=>'btn btn-primary'])}}
                {{Form::close()}}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

三項演算子で、$errors->has('id') ? ['class' => 'form-control is-invalid'] : ['class' => 'form-control']のようにエラーの有無でクラスの配列引数を変えています。

バリデーションエラーがあるとこんな感じでエラーの装飾があたります。

image.png

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

linuxコマンド登録ツール(PHP)

当方のスペック

OS:Windows10 Home 64bit
メモリ:8GB
CPU:言いたくないほどダサい
(なんせ安いパソコンなので)
ローカル開発環境:XAMPP

実業務であまりphpを使う機会がないが、興味があるので以前勉強がてらに作っていたプログラムをメモとして残そうと考えている。

さらに登録するデータ量がある程度ほしいのと、資格の勉強も進めたかったことから作成してみようと考えた。
そして、lpic勉強のためのサイトpint-tのコマ問題のような雰囲気の
ものを作れたらと考えていた。

機能

linuxコマンド、コマンドのオプション、コマンドのカテゴリー、
コマンドの機能(用途)を登録、削除、参照できるプログラム

DB:sqlite3
(理由:簡単に使えるから && DBの勉強に丁度よさそうだから)

完成イメージ

完成イメージ

1.フォーム作成

登録ボタン(button1)、削除ボタン(button2)、参照ボタン(button3)

<!DOCUTYPE html>
<html lang="ja">
<meta charset="utf-8">
<form action="linux_command_resist.php" method="post">
<title>linuxコマンド登録</title>
<h1>linuxコマンド登録</h1>
コマンド<input type="text" name="lword">
オプション:<input type="text" name="lopt"><br><br>
カテゴリー:
    <select name="l_category">
        <option>1capacity_planning</option>
        <option>2Linux_Kernel</option>
         <option>3Boot_System</option>
        <option>4Device_FileSystem</option>
        <option>5Raid_Lvm</option>
        <option>6Network</option>
        <option>7Mentenance</option>
        <option>8DNS</option>
        <option>9WebServer</option>
        <option>10Samba_NFS</option>
        <option>11dhcp_ldap</option>
        <option>12mailService</option>
        <option>13SystemSecurity</option>
    </select>
    <br>
回答<textarea name="l_ans" maxlength=500 cols=100 rows=5></textarea><br><br>
回答(短縮)<textarea name="l_sans" maxlength=500 cols=100 rows=5></textarea><br><br>
<input type="submit" value="確定" name="button1">
<input type="submit" value="削除" name="button2">
<input type="submit" value="参照" name="button3">

<br>
</form>

2.登録処理

(1)ここでは、ファイル(DB)の有無をチェックする
なければ、データベースとテーブルを作成(query_c)。
(2)コマンド、オプション、コマンドの用途、コマンドの用途(短縮版)の入力後に
コマンドとオプションが登録されていれば、「既に存在している」旨の
メッセージを出し、未登録であれば登録後に登録内容を表示する

<?php $lexam=isset($_POST["lword"])?htmlspecialchars($_POST["lword"]):null;
$l_opt0=isset($_POST["lopt"])?htmlspecialchars($_POST["lopt"]):null;
$l_opt0=trim($l_opt0);
$lans0=isset($_POST["l_ans"])?htmlspecialchars($_POST["l_ans"]):null;
$lsans0=isset($_POST["l_sans"])?htmlspecialchars($_POST["l_sans"]):null;
//テキストエリア内の改行を変換
$lans0=nl2br($lans0);

$lsans0=nl2br($lsans0);
$l_cate0=isset($_POST["l_category"])?htmlspecialchars($_POST["l_category"]):null;

$db_name='linux_quiz3.db';
$ext=file_exists($db_name);

$query_c="CREATE TABLE tbl_linux(exam0 VARCHAR(20),opt0 VARCHAR(30),category0 VARCHARA(50),ans0 VARCHAR(500),sans0 VARCHAR(500))";
//抽出
$query_select2="SELECT * FROM tbl_linux WHERE exam0 = ? AND opt0 = ?";
//挿入
$query_ist="INSERT INTO tbl_linux(exam0,opt0,category0,ans0,sans0) VALUES(?,?,?,?,?)";
//削除
$query_del="DELETE FROM tbl_linux WHERE exam0 = ? AND opt0 = ?";

function DataCheck($db0,$qry0,$val1,$val2)
{
    $result3=$db0->prepare($qry0);
    //パラメータをセット
    $result3->bindparam(1,$val1);
    $result3->bindparam(2,$val2);
    $result3->execute();
    //検索結果が配列に格納する
    $select_data=$result3->fetch();
    return $select_data;
}

//設定してあるとき
if(isset($_POST['button1']) && empty(trim($lexam)) == false){
    //データベースを開く
    $db1 = new PDO('sqlite:'.$db_name);
    //データベースが存在しないとき
    if(!$ext){
        if(empty(trim($lans0)) == false && empty(trim($lsans0)) == false){
            //データベーステーブルの作成
            $result1=$db1->exec($query_c);
            //データ書き込みのsql文を準備
            $result2=$db1->prepare($query_ist);
            //パラメータをセット
            $result2->bindparam(1,$lexam);
            $result2->bindparam(2,$l_opt0);
            $result2->bindparam(3,$l_cate0);
            $result2->bindparam(4,$lans0);
            $result2->bindparam(5,$lsans0);
            //実行
            $result2->execute();
            print "$lexam "."$l_opt0"."★は★".$l_cate0."★".$lans0.":".$lsans0;
        }else{
            print "回答と回答(短縮)を記載してください";
        }
    //データベースが存在するとき
    }else{
    //抽出のsql文を準備
        $select_data=DataCheck($db1,$query_select2,$lexam,$l_opt0);
        //結果があるとき
        if($select_data){
            print "[既に登録してあります。]";
            $retstr="$lexam "."$l_opt0"."は".$select_data['category0'].",".$select_data['ans0'].":".$select_data['sans0'];
            print $retstr."";

            //ユーザー名に対する結果が存在しないとき
        }else{
            if(empty(trim($lans0)) == false && empty(trim($lsans0)) == false){
                //データ書き込みのsql文を準備
                $result2=$db1->prepare($query_ist);
                //パラメータをセット
                //一番目のパラメータに$lexamをセット
                $result2->bindparam(1,$lexam);
                //二番目のパラメータに$l_opt0をセット
                $result2->bindparam(2,$l_opt0);
                //三番目のパラメータに$l_cate0をセット
                $result2->bindparam(3,$l_cate0);
                $result2->bindparam(4,$lans0);
                $result2->bindparam(5,$lsans0);             
                //実行
                $result2->execute();
                print "[新規登録]";
                $retstr="$lexam "."$l_opt0"."は".$l_cate0.",".$lans0.":".$lsans0;
                print $retstr."";
            }else{
                print "回答と回答(短縮)を記載してください";
            }
        }
    }
    //データベースを閉じる。
    $db1=null;
}

3.データの削除処理

(1)コマンドとオプションが存在するかチェック
(2)存在すれば、該当コマンドに対するレコードを削除し、
削除した旨のメッセージを表示。
(3)存在しない場合、存在しない旨のメッセージを表示。

//削除のとき
if(isset($_POST['button2']) && empty(trim($lexam)) == false){
    //データベースを開く
    $db1 = new PDO('sqlite:'.$db_name);
    if($ext){
        if(DataCheck($db1,$query_select2,$lexam,$l_opt0)){
            $result_d=$db1->prepare($query_del);
            //一番目のパラメータに$lexamをセット
            $result_d->bindparam(1,$lexam);
            //二番目のパラメータに$l_opt0をセット
            $result_d->bindparam(2,$l_opt0);
            //実行
            $result_d->execute();
            print "$lexam "."$l_opt0"."を削除しました";
        }else{
            print "$lexam "."$l_opt0"."はありません";
        }
    }   
    //データベースを閉じる。
    $db1=null;
}

4.参照処理

(1)選択したカテゴリに登録されてあるデータを表示

//参照
if(isset($_POST['button3'])){

    $db1 = new PDO('sqlite:'.$db_name);
    $query_all="SELECT * FROM tbl_linux WHERE category0 = ?";

    if($ext){
        $result_a=$db1->prepare($query_all);
        //パラメータに$l_cate0をセット
        $result_a->bindparam(1,$l_cate0);
        //実行
        $result_a->execute();
        //検索結果が配列に格納する
        while($select_data=$result_a->fetch(PDO::FETCH_ASSOC)){
            print $select_data['exam0']." ".$select_data['opt0']."|".$select_data['category0']. "|" .$select_data['ans0']."|".$select_data['sans0']."</br>";    
            print "------------------------------------------------------------------------------------------------------"."</br>";
        }
    }   
    //データベースを閉じる。
    $db1=null;
}
?>

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

LaravelのViewComposerで継承を使わずに共通化する

この記事はLaravel Advent Calendar 2019の15日目の記事です。

ViewComposerとは?

ビューコンポーザはビューがレンダーされる時に呼び出される、コールバックかクラスメソッドのことです。ビューがレンダーされるたびに結合したい情報があるなら、ビューコンポーザがロジックを一箇所にまとめるのに役立ちます。
ビュー 6.x Laravel

基本的な実装方法も上記Laravelのドキュメントで確認することができます。

ViewComposerの実装を共通化する

本記事ではサンタクロース(santa-claus)画面とトナカイ(reindeer)画面にそれぞれViewComposerを定義し、
それぞれ共通して isChristmas をViewから参照させます。
これを継承を使った方法を紹介した上で、継承を使わずに共通化する方法を紹介します。

継承を使った実装

isChristmas を設定する ChristmasViewComposer を実装し、 SantaClausViewComposerReindeerViewComposer がそのクラスを継承します。

namespace App\Http\View\Composers;

use Illuminate\View\View;

class ChristmasViewComposer
{
    public function compose(View $view)
    {
        $view->with('isChristmas', $this->isChristmas());
    }

    private function isChristmas()
    {
        return false; // FIXME 適当な実装
    }
}

class SantaClausViewComposer extends ChristmasViewComposer
{
    public function compose(View $view)
    {
        parent::compose($view);
        // FIXME サンタクロース(santa-claus)画面に必要な実装
    }
}

class ReindeerViewComposer extends ChristmasViewComposer
{
    public function compose(View $view)
    {
        parent::compose($view);
        // FIXME トナカイ(reindeer)画面に必要な実装
    }
}

コンポーザーの定義はこのようになります。

View::composer('santa-claus', 'App\Http\View\Composers\SantaClausViewComposer');
View::composer('reindeer', 'App\Http\View\Composers\ReindeerViewComposer');

継承を使わない実装

isChristmas を設定する ChristmasViewComposer を実装しますが、 SantaClausViewComposerReindeerViewComposer はそれを継承しません。

namespace App\Http\View\Composers;

use Illuminate\View\View;

class ChristmasViewComposer
{
    public function compose(View $view)
    {
        $view->with('isChristmas', $this->isChristmas());
    }

    private function isChristmas()
    {
        return false; // FIXME 適当な実装
    }
}

class SantaClausViewComposer
{
    public function compose(View $view)
    {
        // FIXME サンタクロース(santa-claus)画面に必要な実装
    }
}

class ReindeerViewComposer
{
    public function compose(View $view)
    {
        // FIXME トナカイ(reindeer)画面に必要な実装
    }
}

継承していないためコンポーザーをこのように定義します。

View::composer(
    ['santa-claus', 'reindeer'],
    'App\Http\View\Composers\ChristmasViewComposer'
);
View::composer('santa-claus', 'App\Http\View\Composers\SantaClausViewComposer');
View::composer('reindeer', 'App\Http\View\Composers\ReindeerViewComposer');

まとめ

ドキュメントには書かれていませんが、一つのViewに対して複数の複数のViewComposerを登録することも可能なため、継承を使わなくてもViewComposerの実装を共通化できます。

クラス継承にはデメリットが多いため、クラス継承を使わない共通化の方法を紹介しました。

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