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

Dockerでnginx等を使う時はまずLinuxの知識をだな......(戒め)

DockerとかVagrantでnginx等を使う時はまずLinuxの知識をだな......(戒め)

1. 背景

フロント学んできて、そろそろバックエンドもまなんでみようと思い、せっかくなので流行りのDokcerを使用しPHPでサーバーに画像をアップロードするコードを書いた。
そしてハマった

2. 環境

・windows10<VirtualBox(vagrant)<CentOS7<Docker

起動構成

docker-compose.yml
version: '3'
services:
  web:
#ver 1.17.7
    build: ./nginx
    ports:
      - '8080:80'
    links:
      - php-fpm
    volumes:
      - ./data/public:/var/www/html/public
    depends_on:
      - php-fpm
  php-fpm:
#ver 7.3
    build: ./php-fpm
    links:
      - db
    volumes:
      - ./data:/var/www/html

3. ハマったところ

このPHPの一行からなるエラーで無限に悩んだ

upload.php
move_uploaded_file($_FILES['image']['tmp_name'], $savePath);

SnapCrab_NoName_2020-1-13_17-51-41_No-00.jpg

Permisson denied ......

4.1 まずやったこと

とりあえず浅いLinuxの知識を用いて画像保存フォルダの権限を
$sudo chmod 777 images
としてコードが動くのを確認。
もちろんこんなセキュリティガバガバな権限は例えローカルサーバーでも許せなかったので却下

4.2 解決策

phpinfoでユーザーを確認してみる。
SnapCrab_NoName_2020-1-13_18-30-53_No-00.jpg

www-dataがUSERだと判明。
じゃあwww-dataに権限付与してあげればよさそう

vagrant環境下の権限はこう

$ls -l
~
drwxrwxr-x. 2 vagrant vagrant 4096 Jan 13 08:32 images
~
$ sudo chown www-data images
chown: invalid user: ‘www-data’

有効ではないと告げられたのでユーザー一覧を確認してみます

$cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
~
vagrant:x:1000:1000:vagrant:/home/vagrant:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

これ以外にも色々出てきたけどwww-dataユーザーなんて存在しない。

どうすればいいのか

コンテナを立ち上げて
docker exec -it [id] bash
でphp-fpmコンテナに入ってみる。
その中のファイル権限は

drwxrwxr-x. 2 1000 1000 4096 Jan 13 08:32 images

1000 1000

調べてみたらvagrantユーザーのuidが1000らしい。

これも$cat /etc/passwdでユーザー一覧を確認すると

vagrant:x:1000:1000:vagrant:/home/vagrant:/bin/bash

vagrntのuidに1000番が付けられてるのが分かる

VMという仮想環境の中にさらに仮想コンテナがあってそのコンテナ内では1000番uidに名前がついていないという事らしい
つまりコンテナ内では1000番にvagrntユーザーも含めユーザーは存在してないという事
でもuidは共通みたいだ

ならばコンテナ内から確認すればwww-dataがいるはず......

www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

いた

結局どうすればいいのか

正解なのかは分からないけど思いついたのはコンテナ外で33番UIDでグループがvagrantのユーザーを作ってimagesフォルダの持ち主を33番に変える。
これが自分の中でしっくりきました。
というかこれ以外成功してない

$useradd -u 33 www-data -g vagrant
$chown www-data images

他にも
https://gtrt7.com/blog/nginx/docker_userid_share#3docker-composeyml
のようにしてvagrant側からではなくdocker側から合わせればいけるらしい。

自分はこんなエラー出てわからんくて投げてしまいましたが

web_1      | 2020/01/13 13:52:54 [emerg] 1#1: host not found in upstream "php-fpm" in /etc/nginx/conf.d/default.conf:20
web_1      | nginx: [emerg] host not found in upstream "php-fpm" in /etc/nginx/conf.d/default.conf:20

default.conf:20fastcgi_pass php-fpm:9000;

正直分からん

Dockerコンテナ内でファイル関係は完結させた方がいいんだろうなと感じてはいるもののいいやり方が浮かばず今回は断念。

ちょっと深堀するにはLinuxやnginx php-fpm等の知識が足りないので今はphpでファイルアップロードしたフォルダを作るさいは適宜権限変更するこのやり方のまま進んで、強くなったら戻ってこようと思いました。

いいやりかたあったら教えてほしい......

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

LaravelのJWT認証でいろいろカスタマイズ

仕事で考えないといけなくなったので、いろいろ試してみました。

作るもの

以下の条件を満たす認証機能を作ります。

  • JWT 認証
  • ユーザテーブルが、Laravel 標準のテーブル定義で ない
    • デフォルトの users テーブルは使いません
  • パスワードのハッシュ化が Laravel 標準の方法で ない
    • デフォルトの bcrypt ではなく SHA256 を使います

なお、Laravel 標準が使える環境ならば、そうしたほうが良いと思います。
この記事は やむを得ず そうせざるを得ない場合のために書かれています。

前提

バージョン

  • Laravel 6.9.0
  • PHP 7.4.1
  • MariaDB 10.3.11

扱わないこと

以下については、この記事では触れません。

  • Laravel 標準の認証機能について
  • JWT そのものについて
  • JWT 認証の是非について

手順

非標準のユーザテーブル

Laravel 標準では users テーブルを使用しますが、今回は、以下のようなテーブル t_user を作成します。

ユーザテーブル.png

Eloquent モデル更新

Eloquent モデル App\User を編集します。テーブル名や主キーのカラム名を設定します。また、タイムスタンプ( created_atupdated_at )は存在しないので無効にします。

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

/* ここから追加 */
    // テーブル名
    protected $table = 't_user';

    // 主キーのカラム名(デフォルトは id なので)
    public $primaryKey = 'user_id';

    // タイムスタンプ無効化(デフォルトで有効なので)
    public $timestamps = false;
/* ここまで追加 */
}

JWT認証

Laravel では標準の認証機能が存在しますが、今回は JWT 認証を行いたいと思います。
JWT 認証のライブラリは、 tymon/jwt-auth を使います。

インストール

composer でインストールします。
バージョン番号まで指定しないと、かなり古いバージョン(0.5.12)がインストールされるので注意が必要です。1

composer require tymon/jwt-auth 1.0.0-rc5

設定ファイルの配置

以下のコマンドを実行して、 config/jwt.php を作成します。

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

秘密鍵の生成

以下のコマンドを実行して、秘密鍵を生成します。

php artisan jwt:secret

.env に秘密鍵が設定されます。

JWT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Eloquent モデルの更新

Eloquent モデル App\User を編集します。

コントラクト JWTSubject にメソッド getJWTIdentifier()getJWTCustomClaims() が定義されているので、それらを実装します。

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

/* 追加 */
use Tymon\JWTAuth\Contracts\JWTSubject;

/* implements を追加 */
class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    // テーブル名
    protected $table = 't_user';

    // 主キーのカラム名(デフォルトは id なので)
    public $primaryKey = 'user_id';

    // タイムスタンプ無効化(デフォルトで有効なので)
    public $timestamps = false;

/* ここから追加 */
    // JWT の sub に含める値。主キーを使う
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    // JWT のクレームに追加する値。今回は特になし
    public function getJWTCustomClaims()
    {
        return [];
    }
/* ここまで追加 */
}

ガードの設定

config/auth.php を編集します。

'defaults' => [
    // web を api へ変更
    // 'guard' => 'web',
    'guard'     => 'api',
    'passwords' => 'users',
],
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        // token を jwt へ変更
        // 'driver' => 'token',
        'driver' => 'jwt',
        'provider' => 'users',
        // これは変更不要?
        // 'hash' => false,
    ],
],

ルーティングの追加

routes/api.php に以下を追加します。

Route::group([
    'middleware' => 'api',
    'prefix' => 'auth'
], function ($router) {
    Route::post('login', 'Api\AuthController@login')->name('login');
    Route::post('logout', 'Api\AuthController@logout');
    Route::post('refresh', 'Api\AuthController@refresh');
    Route::post('me', 'Api\AuthController@me');
});

コントローラの追加

以下のコマンドを実行して、コントローラの雛形を作ります。

php artisan make:controller Api/AuthController

作成された App\Http\Controllers\Api\AuthController を編集します。

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    /**
     * コンストラクタ
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

    /**
     * ログイン
     * 認証に成功したら、トークンを返却する
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth()->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * ユーザ情報の取得
     * 認証されたユーザの情報を返却する
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth()->user());
    }

    /**
     * ログアウト
     * ログアウトし、トークンを無効にする
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * トークンのリフレッシュ
     * 古いトークンを新しいトークンでリフレッシュする
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

    /**
     * トークン返却
     * トークンに関する情報を配列化してからJSON形式で返却する
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60
        ]);
    }
}

非標準のパスワードのハッシュ化

Laravel 標準では bcrypt によるパスワードのハッシュ化が行われます。2

今回は SHA256 によるハッシュ化を行います。

ハッシュドライバ

ハッシュドライバ App\Extensions\SHA256Hasher を作成します。ハッシュドライバでは、ハッシュ作成やチェックなどの処理を行います。

メソッドを4つ実装していますが、実際に必要なのは make()check() のみです。3

namespace App\Extensions;

use Illuminate\Contracts\Hashing\Hasher as HasherContract;

class SHA256Hasher implements HasherContract
{
    // ハッシュ作成
    // $value のハッシュを返す
    // password_hash() に相当する
    public function make($value, array $options = [])
    {
        return hash('sha256', $value);
    }

    // ハッシュのチェック
    // $value のハッシュと、与えられたハッシュが一致するかをチェックする
    // password_verify() に相当する
    public function check($value, $hashedValue, array $options = [])
    {
        return $this->make($value) === $hashedValue;
    }

    // ハッシュの再計算が必要かのチェック
    // password_needs_rehash() に相当する
    // SHA256 では不要なので、常に false を返す
    public function needsRehash($hashedValue, array $options = [])
    {
        return false;
    }

    // ハッシュの情報取得
    // password_get_info() に相当する
    // SHA256 では不要なので、常に null を返す
    public function info($hashedValue)
    {
        return null;
    }
}

サービスプロバイダ追加

サービスプロバイダ App\Providers\SHA256ServiceProvider を作成します。

ドライバ名が sha256 の場合は、先程作成したハッシュドライバを使うように Hash::extend() で指定しています。4

namespace App\Providers;

use App\Extensions\SHA256Hasher;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Hash;

class SHA256ServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function boot()
    {
        Hash::extend('sha256', function ($app) {
            return new SHA256Hasher();
        });
    }
}

また、 config/app.phpSHA256ServiceProvider を追加します。

'providers' => [
    /* 省略 */

    Illuminate\Hashing\HashServiceProvider::class,

    /* 省略 */

    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
    App\Providers\SHA256ServiceProvider::class, // 追加
],

ハッシュ設定の変更

config/hashing.php を編集します。
driverbcrypt から sha256 へ変更します。5

return [

    /*
    |--------------------------------------------------------------------------
    | Default Hash Driver
    |--------------------------------------------------------------------------
    |
    | This option controls the default hash driver that will be used to hash
    | passwords for your application. By default, the bcrypt algorithm is
    | used; however, you remain free to modify this option if you wish.
    |
    | Supported: "bcrypt", "argon", "argon2id"
    |
    */

    // bcrypt から sha256 へ変更
    // 'driver' => 'bcrypt',
    'driver' => 'sha256',

    /* 省略 */
];

動作確認

ログイン

リクエストを送信すると、JWT が返却されます。

リクエスト

POST /api/auth/login?email=test@example.com&password=password HTTP/1.1
Host: test.example.com

レスポンス

成功時

{
    "access_token": "xxxxxxxxxx",
    "token_type": "bearer",
    "expires_in": 3600
}

失敗時

{
    "error": "Unauthorized"
}

ユーザ情報取得

ログインで取得した JWT を Authorization Bearer ヘッダ6に指定して、リクエストします。

リクエスト

POST /api/auth/me HTTP/1.1
Host: test.example.com
Accept: application/json
Authorization: Bearer xxxxxxxxxx

レスポンス

成功時

{
    "user_id": 1,
    "user_name": "山田太郎",
    "email": "test@example.com",
    "password": "password",
    "create_date": "2020-01-12 21:32:17",
    "update_date": "2020-01-02 18:23:36"
}

失敗時

{
    "message": "Unauthenticated."
}

注意点
Accept ヘッダ7application/json を指定する必要があります。

まとめ

ここまで書いて、以前チラ見した 【Laravel】JWTを使って認証システムを構築する を見たら、ほぼ同じことが(しかもより詳しく)書いてあることに気づいたのでした。

ただ、JWT 認証の事例が基本的なものばかりだったので、 JWT 認証でも全然カスタマイズできるよ ということを書いておきたいなと思いました。8

JWT 認証そのものにも、まだまだ調べないといけないことが多いのですが、一旦、今回はここまでということで...9

参考


  1. 現在入手可能な最新版は 1.0.0-rc51.0.0-rc5 で Laravel 6 に対応したため、6 を使う場合は、このバージョンを使う必要がある。それにしても、いつになったら 1.0.0 になるのだろうか... 

  2. 他にも Argon2 がデフォルトで選択可能です。詳しくは、ドキュメントの ハッシュ を参照。 

  3. Laravel は password_hash() をはじめとする 関数群 を使う想定のようです。SHA256 で使う hash() には password_get_info() などに相当する機能がないので、適当に実装しています。 

  4. いろいろな記事を見ると、 config/app.php で既存のプロバイダをコメントアウトして、新しく作成したプロバイダを追加しているようです。コメントアウトしなくても、 カスタムガードの追加Auth::extend() )と同様に追加できないのか? というのが、そもそものきっかけでした。結果、 HashManager (ファサード Hash の実体)の親クラス Illuminate\Support\Managerextend() が存在しており、 Hash::extend() で追加できることがわかりました。 

  5. Illuminate\Support\Manager\extend() を実行すると、配列 customCreators にドライバ名とクロージャが登録されます。 HashManager (ファサード Hash の実体)の driver() を呼び出すと、まず getDefaultDriver() が実行されます。 getDefaultDriver()config/hashing.phpdriver に設定されている値を取得します。次いで、取得した値をもとに Manager\createDriver() が実行されます。 createDriver() は、配列 customCreators にドライバ名が登録されているかを確認します。登録されていたら、それに紐付けられているクロージャを呼び出します。こうして、SHA256 によるハッシュ化が行われるわけです。 

  6. MDN HTTP認証 

  7. MDN Accept 

  8. それぞれのパーツが(しつこいくらい)抽象化されているので、パーツの中をカスタマイズしても、全体としてみればちゃんと動くようになっているわけですね。なかなかソースコードを追うのは大変でしたが、理解したら「なるほど〜」と唸らされました。 

  9. Laravelのソースコードを3日間読んだら、さすがに疲れた...... 明日から仕事? 先生、本当ですか... 

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

PHP7系のNoticeについて

時刻選択するフォームを作成中です。
フォームにアクセスした段階で現在時刻(5分刻み)に近い選択肢を選択中にしたいため、
下記サイトに掲載されているスクリプトの「現在の時間を選択リストの初期値に反映する」を試しました。

参考サイト
https://codezine.jp/article/detail/33

ところが、PHP7系ではシステムログ(apache error_log)にNotieceが毎回出力されてしまい、困っています。
なんとかログ出力レベルを変えずに回避する方法はないものでしょうか?

システムログ
PHP Notice: A non well formed numeric value encountered in /var/www/(vhost)/index.php on line 600, referer: ...

回避方法をご存知の方、どうかお知恵を拝借させてください。
よろしくお願いいたします。

現在のスクリプト
$t = strtotime('00:00');

$step = 5;

$start = null;

for($i=0; $i<5*12*24; $i+=$step){

$dispTime = date('H:i', strtotime("+{$i} minutes", $t));

$now = date('H:i');

$start .= '<option';

if($now >= $dispTime and $dispTime + $step > $now) $start .= ' selected';

$start .= ' value="'.$dispTime.'">'.$dispTime.''.PHP_EOL;

}

echo $start;

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

横文字が飛び交う世界で無知無知の新卒が一人前のSEになるまで ー1年目ー

要約

  • 「IT?何それ?」な新卒社員がIT部署に配属されてからの軌跡
  • 一年を通しての振り返り
  • 何を見て何を学び、何が出来るようになったのかのまとめ
  • 参考になるかは貴方次第

はじめに

こんにちは。タスク管理で予想時間以内にタスクが終わった試しがない、あやしいブロッコリーです。

という紹介文が馴れ馴れしいほどのQiita初心者です。

2019年、皆様はどんな年でしたでしょうか。
個人的な感想としては、題にもある通り新卒ということで沢山の苦悩・苦難があった年でした。
例えば、先輩社員や上司の方々がどこぞのクラウドサービス炎上でドタバタする中、
私はTwitterで現状を追うことしかできなかったり。。
あの時の自分の何もできない感といったらまあもう味わいたくないですね。
アマゾン森林の大火災なんて笑えません全く。

そんな2019年でしたが、今回は新年明けての挨拶も兼ねて、
昨年の振り返り、今年の抱負、やりたいこと等々色々考えてみました。
脳内の大掃除なくして2020年は迎えられませんからね。

現在携わっているプロダクト

Webサイトにおける様々なログを収集・分析し、そのデータをレポートとして提供するサービス。
現在はその内部システムを担当しているチームに所属しています。

2019年振り返り

2019年では「えんじにあ…??」⇒「デプロイ作業やってみよう」まで変化できたかなと思います。

当初はエンジニアになるとは思ってもいなかったため、右も左も分からなかった状態でした。
幸いにも、部署の研修プロセスは基礎の基礎からの叩き上げが重視されていたため、
「理解するまで理解する」ことに集中できました。
やることはひたすら『調べること』『得た知識を発表』
『フィードバックを受けてさらに洗練』のループ&ループ。
調べに調べまくっては発表し、痛い指摘を受けてはまた調べての毎日を送っていました。

上長の口癖は「じゃあそれってなんだっけ?」。
どんな説明をしても全てこの一言で一刀両断されます。その切れ味は妖刀紅桜をも凌駕します。
これが本当に痛くて苦しく、そして悔しくて原動力になる言葉でした。
半年ほどかけて最終的に上長からその言葉を聞かなくなった時の達成感は、
今でも糧になっています。

現在はシステムのデプロイ作業の練習をしており、
終了後はとうとう自分がハンドルを握る・・・

それまでに自分が何を学んできたのか、振り返ってみました。

2019年やってきたこと

業務を中心にひたすらインプットとアウトプットの繰り返し。
Try&Errorの繰り返し、。
とにかく書いて、とにかく触って、とにかく動かしていく毎日.

ザックリ言うと以下の通り。
1. IT基礎知識の習得
2. システム問い合わせ対応 / 一部開発・改修作業
3. その他インプット

2019年で学んだこと、出来るようになったこと

上記のやってきたことベースで思い出し書き。

IT基礎知識の習得

ITの基礎知識の定着に、配属されてからの約4ヶ月間を費やしました。
主なテーマは以下の通り。
 1. 開発プロセス
 2. Webシステム
 3. http/https通信の違い
 4. Webサイト作成

開発プロセス

SEとしてシステム開発に携わる前提として、直後に「そもそもシステム開発ってなに?」「ただコード書けばいいの?」とならないように、一般的な開発フェーズやプロセスとは何たるかを学びました。
自分の、そしてチーム全体のスコープを見失わずに開発を進めていくうえで必須な考え方を身に付けました。

  • 開発フェーズ
    • 上流(要件定義、基本設計)、下流(詳細設計、実装)、テスト(単体、結合、システム、UAT)
  • 開発プロセスの種類
    • ウォータフォール、プロトタイプ、スパイラル、アジャイル開発
  • 開発管理
    • プロジェクト管理(Redmine)、構成管理/バージョン管理(Git)、変更管理、QA
Webシステム

Webシステムでは、私が今までChromeやSafari等のブラウザを通して当たり前のように
サイト巡回をしていたその裏の仕組み
を学びました。ここが一番重要且つ重いテーマでした。
この時も「そもそもWebってなんだっけ?」で始まりました。
はじめの頃って調べるの慣れておらずこの三文字に1時間かけました。
そして毎テーマ必ず「そもそも・・・」という原点追及を欠かさなかったのが
自分自身で印象に残ってます()

大まかにいうと内容はこんな感じ。

  • コンピュータの構造
    • クライアントサーバシステム、ソフトウェア、ハードウェア、アプリケーション、ミドルウェア
  • プロトコル
    • プロトコル、ネットワークアーキテクチャの種類や違い、各レイヤーでの役割
  • リクエスト/レスポンス
    • メソッド、リソース(URL、IPアドレス、FQDN、DNS)、User-Agent、Referrer、Cookie
  • サーバ側の処理
    • Webサーバ・アプリケーションサーバ・データベースサーバの役割の違い
  • レンダリング処理
    • レンダリングの流れ(Parse、Scripting、Rendering、Painting)、HTML/CSS
http/https通信の違い

Webシステムから続いて、http/https通信の違いについて学びました。
「電子署名って何なんだよ・・・」でかなり苦戦しましたね。
公開鍵で暗号化するって言ったじゃん!!!!秘密鍵使うなよ!!!!!
って2日間くらいなってました。
ただ、個人的にここでの学習が後に大いに役立つとは思ってませんでしたが。

以下内容。

  • 通信の暗号化
    • SSL/TLS、共通鍵方式、公開鍵方式
  • サーバ証明書
    • 認証局、自己署名証明書、電子署名
Webサイト作成

上記の全てを踏まえて、実際に自分でWebサイトを作成し公開してみました。
サイトのテーマは「ログインサイト」。
単純にログインの機能を持ったサイトをAWSの一部のサービスを用いて作成するということでした。

作成スケジュールは以下の通り。

Day1:先輩社員の方をクライアントに見立てて、作成したいWebページの要件を聞き出し設計書におこす。
   クライアントと擦り合わせながら設計書を完成させていき、スケジュールを確定させる。
Day2:AWSのプライベートアカウントを作成し、サイトを公開するためのインフラを構築。
Day3:HTML/PHPファイルを作成し、ページの機能を完成。
Day4:CSSを適用し、サイトを完成。単体テストを実施。先輩からレビュー。
Day5:レビューを基に修正。完成。

しかしこれが大幅にスケジュールオーバー。
インフラ構築に二日かかってしまい、HTML/PHPの完成に二日費やしてしまいました。
これによってCSSを適用できなかった私のサイトはみすぼらしく、点数は0点。。
悔しすぎて、夏季休暇に暇してた時間全部つぎ込んで「パスワード再設定メールの送信」という
要件にない機能まで付けて無理やり完成させました。(要件にない機能:-10点)

ちなみに、設計書の内容は「全体構成概要図」「内部設計」「DB定義」「各画面基本設計」。
以下は完成した実際のサイト(現在はサーバー落としているので画像)。
新規アカウント作成画面.png

そして、以下はこのサイトの内部設計。
※画像内のIP/Subnet maskは例
内部設計.png

苦労した点は、NATの仕組みと使い方や、セキュリティグループの設定、
パスワード再設定メール送信機能の設定等々、インフラ構築の部分が多かった印象です。
特にメール送信機能では、サブミッションポート587を利用したかったのですがなかなか上手くいかず。
やむなく25番を利用した時の悔しさたるや。

CSSの適用にもかなり苦戦しました。
ここで学んだのは、その場しのぎのコードは後に巨大なブラックボックスを生んでしまうこと
「これでいいじゃん♪開発」で進めて、正しいと思っている位置にCSSが適用されないことが多発しました。
反省した後、ノートに全てのページで適用するデザインフレームワークを作成し、
各パートに要素を入れていく形で進めるとCSSを瞬殺することが出来ました。
(後々にLaravelすげぇってなる男の軌跡)

その逆に、https通信の確立に最初は3時間くらいかかっていたものを今では5分で出来るようになり、
研修で身に付けたhttps通信の知識が大いに活きた瞬間でもあります。

実際の完成したサイトでテストをした時、もう楽しすぎて30分くらいサイト巡回してました。

※神教本
https://www.amazon.co.jp/Amazon-Web-Services-%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E3%81%AE%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E6%A7%8B%E7%AF%89/dp/4822237443/ref=pd_aw_sbs_14_4/

システム問い合わせ対応 / 機能開発・改修・結合テスト

研修と併せて所属部署が担当する自社システムに触れる機会が増えていき、
利用者からの問い合わせや結合テストを担当することが多くなりました。

最初はパスワードを数回間違えると掛かってしまうアカウントロックの解除しか
対応できませんでしたが、最近ではDBからのデータ抽出や改修対応を担当することも増えました。
先輩方に任せっきりだったところも、解決後に必ず始終の説明を受け自分でも手を動かすことで
どんどん自分が扱える範囲を広げていきました。

そして問い合わせ内容から発展し一部開発や改修を担当することもありました。
基本的にはPHP/JS/HTML等のフロント部分が多く、MVCについての知識も得られました。

結合テストに関しては、ただテスト項目を消化するのではなく、
「今どんな背景でこのテストが必要とされていて、なぜこの部分をテストするのか」
まで考慮に入れて実施することを念頭に置いて実施していました。
そして口を酸っぱくして言われたのは「テスト項目が100%正ではない」ということ。
項目書も人が作ったもの。システムである以上動かした際の結果が真実。
終了したことには芯の部分からその機能を理解できるようになれました。

印象に残っているのは、ChromeのSamesite属性の検証テスト。
上記テストの環境構築も自ら行うことによって、AWSの各サービスについての理解や
インスタンス・コンテナ・ロードバランサー等への理解を深められた重要なテストでした。

その他インプット

基本的に言語学習はプログラミング学習サイトを利用しました。
Webアプリケーションの開発部でもあるため、業務で多く利用したのはPHP/HTML/JS。
また、AWSサービスについても様々な場面で触れました。
先に述べたSamesiteテストでの環境構築一つとっても、CloudFront/Route53/ELB/EC2/ECS…等々、
仕組みや繋がりを理解しながら実施していき、どこに何をすればどうなるのかを形にできました。

  • プログラミング言語
    • PHP
    • HTML/CSS
    • JavaScript
    • Shell Script(主にJenkinsにて)
  • AWSサービス
    • Athena(CTAS、パーティションの貼り方、クエリチューニング)
    • S3(バージョニング、CLIからのバケットコピーシェル作成)
    • CloudFront(Behaviors、OriginGroups)
    • Route53
    • EC2/ECS/ELB(AMI/Snapshot/EBSボリューム、起動設定/AutoScaling、CLBとALBの違い)

来年やりたいこと

そんなこんなで右往左往した1年目でしたが、得るものに余りはありませんでした。
手に落ちてくるものを零さないように必死に飲み干していく毎日。
それは来年も変わらないとは思いますが、その中でもやりたいこと、
やり遂げたいことをまとめてみました。

知識面

自社システム構造のドキュメント化

自社のシステムを自分の中で整理することを目的として、様々なドキュメントを作成したい。
これがなかなか難しく時間が取れないけれど、視覚的に頭に入れることは時に絶大な力を
発揮すると信じてやまないので、少しずつ完成させていきます。

リリース作業ハンドル

リリース作業を自分がハンドルを握って完結させること。
リリースの内容のみならず、手順全てとトラブル時の対応力、
今まで学んできたことの集大成として、「開発」を完結させる能力を身に付けること。
現在開発環境でのデプロイ作業を練習として行っており、
本番環境のリリースに携われるように勉強しています。

Java・Pythonをいじりたい

インタプリタ言語がメインだった2019年。
今年はJava等のコンパイル言語も動かしてみたいです。
Javaは大学の時にいじりましたが正直赤子程度の知識まで風化してしまいました。
ここで一度学びなおし、フロントエンドもバックエンドも扱えるようになり、
簡単なアプリケーションであればサッと開発できるようになることが目標です。
また、Pythonについてもシステム内で使用している点も含めて学習したい言語として見据えています。

資格

去年ITパスポートの資格を取得した時、大きい資格とは言えないまでも
確実な自信につながることを実感しました。
何より、身に付けた知識が本当に身についているのかを証明できる一つの形でもあるため、
来年以降も継続していきます。

  • 技術系
    • 基本情報処理技術者
  • AWS
    • ソリューションアーキテクト-アソシエイト
    • デベロッパー-アソシエイト
  • 英語
    • TOEIC 800点

まとめ

簡単にですが、配属されてからの8ヶ月間を振り返ってみました。
最初から見てみると、やはり中身が空っぽであった分多くのことを
学んでいたのだなと実感できました。
恐れずに配属希望を出して本当に良かったと、今は心の底から思っています。

後輩にバリバリにプログラムが書ける人が入ってくることに恐れながら、
今年も去年より飛躍する年に。

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

【初学者向け】セキュリティ対策入門①〜XSS編〜

前提

確認環境

以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜

本シリーズの目的

以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜

本記事の目標

XSS(クロスサイトスクリプティング)の概要、原因、対策について理解することです。

本記事を読み進める上での必要事項

以下の内容を終えていることです。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜

概要

XSS(クロスサイトスクリプティング)とは

不正なスクリプトを混入・実行させることです。掲示板などエンドユーザからの入力値をWebページに表示させる機能を持ったWebアプリケーション全般で行われる可能性があります。

これだけだとわかりにくいと思いますので実際に見てみましょう。

実際に体験してみよう

本シリーズで使うコンテナを起動して
http://localhost:8080/xss/bad.php?name=J
にアクセスしてみましょう。

『こんにちは、Jさん』とブラウザに表示されていると思います。これはnameパラメータの内容をもとに挨拶文言を表示させているものです。ソースコードはtutorial-php-security/www/html/xss/bad.phpにあります。

tutorial-php-security/www/html/xss/bad.php
<?php
$name = $_GET['name'];
?>

<p>こんにちは、<?= $name; ?>さん</p>

$_GET['name']でクエリストリングスのnameパラメータを参照しています。これが上述するところの『エンドユーザからの入力値』の一例です。クエリストリングスだけではなく、POST送信で渡ってきたデータやDBに格納されたデータを出力する場合も、エンドユーザの入力によるものであれば同様です。

次に
http://localhost:8080/xss/bad.php?name=<script>alert(1)</script>
にアクセスしてみましょう。

以下2点が重要です。

  1. ダイアログが表示
  2. ダイアログで『OK』をクリックしたのち『こんにちは、さん』と表示

何が起きているかというとnameパラメータの<script>alert(1)</script>がスクリプトと見なされてJavaScriptのコードが実行されてしまっているのです。alert()はダイアログを表示させるJavaScriptの関数です。

今回は単にダイアログが表示されただけなので大したことがないと思うかもしれません。しかし、別のサーバにリダイレクトさせられたりCookie情報を盗まれてしまったり様々な被害が生じてしまいます。

原因

不正なスクリプトを表示させていることです。これを避けるにあたって様々な対策があります。その一部を以下で具体的に紹介していきます。

対策

エスケープする

以下にアクセスしてみましょう。

http://localhost:8080/xss/good.php?name=<script>alert(1)</script>

bad.phpではなくgood.phpにアクセスしていることに注意しましょう。実際にアクセスすると『ダイアログが表示されない』『「こんにちは、<script>alert(1)</script>さん」と表示されている』ことがわかると思います。

good.phpではある処理を追加しています。それを次に確認しましょう。

tutorial-php-security/www/html/xss/good.php
<?php
$name = $_GET['name'];
?>

<p>こんにちは、<?= htmlspecialchars($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'); ?>さん</p>

ポイントはhtmlspecialchars()の部分です。これはエスケープするための関数です。エスケープとは<>などHTMLにおいて特殊な意味を持つ文字列をそれぞれ&lt;&gt;など安全な文字列に置き換えることです。詳細は『文字参照』などで検索してみてください。&lt;は画面上では<と表示されるため『こんにちは、<script>alert(1)</script>さん』と表示されているわけです。

そもそも出力しない

エスケープが機能しない場合もあります。たとえば

<script><?= $js_code; ?></script>
<a href=<?= $link_url; ?>>危ないリンク</a>

というケースを考えてみましょう。

前者ではalert(2)という文字列が$js_codeに格納されている場合、エスケープしたところで意味がありません。bad.phpと同様ダイアログが表示してしまいます。

後者ではJavaScript:alert(3)という文字列が$link_urlに格納されている場合、エスケープしたところで意味がありません。bad.phpと同様ダイアログが表示してしまいます。ちなみにJavaScript:XXXのような記述をJavaScript擬似プロトコルといいます。JavaScript擬似プロトコルで記述できる箇所ではエスケープが意味を成しません。

こういう場合では、ユーザが入力したデータをそのまま出力しない仕様にするのが得策です。

入力チェックを行う

ユーザの入力をDBに格納するときやユーザの入力データを出力する際に、入力チェックを行うことも有効です。極端な例ですが、nameパラメータに12という文字列以外入力を認めないようにバックエンドでチェックすればスクリプトは混入しなくなります。ちなみに、フロントエンドでの入力チェックやHTMLタグの属性などエンドユーザ側で書き換えできてしまうチェック方法は意味がないので気をつけてください。

ライブラリを使う

ブログアプリケーションを作成するときのように、ユーザにタグ入力を認める機能を実装する場合は、ユーザの入力データからスクリプトだけを上手く取り除くロジックが必要です。それを0から作るのはあまり現実的ではないためライブラリを導入ことが検討に値する解決策となります。

一例ですがHTML Purifierというものがあります。

参考文献

独習PHP 第3版

今回の内容は以上です。最後までご覧いただきありがとうございました。

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

今更だけどCSSって何ぞや あとPHPってHTMLの中に書けるのに.phpってどういう意味だってばよ?


という方に向けてざっくりCSSとPHPを解説して行きたいと思います。



まあCSSがCascading Style Sheetsの略で、表示に関する記述って言うのは誰でも分かると思うんですが、じゃあCSSの中身はどういう仕組みになってるの?という問いに対して答えられる人ってIT人口の何割なんだろうねって気がします。

HTMLに関してもPHP使うなら必修だと思うけど、pとかaとかimgとか、主要なタグさえ覚えればheadの中身とか理解しなくても使えてしまったりするので「今更HTMLとかなぁ~」って思ってる人こそ勉強してみると楽しいと思う。

あと分かってるとは思うけど、仕組みと言いつつ低レイヤーの話はしない(出来ない)のでCSガチ勢の方は帰ってどうぞ。



CSSの役割とは何ぞや



これに関しては長々と解説するより実例を見てもらった方が早いでしょう。

Qiita.png

みなれたQiitaのトップページはCSSを抜くとこんな感じになります。HTMLのお仕事はテキストとそれに付随するコンテンツを規定するだけなのでまあ当然ですね。

ここからCSSの魔法をかけて一気に現代的なサイト表示を作り上げていきます。


色やサイズ、位置の調整

これは簡単で、対象を指定して『プロパティ:パラメータ;』と書いていくだけで勝手に表示が変わっていきます。便利ですね。

まあこれだけだとざっくりし過ぎな気もするので簡単な例を書いておきます。これさえマスターすれば君も今日からフロントエンドエンジニアだ!()

body{
color:white;  /*文字色の変更*/
}

.huga{
float:left; /*要素を左から横に並べる*/
}
.hoge{
padding :10px 30px ; /*要素の内側に指定幅の余白を取る*/
background-color: #FF0000; /*背景色の変更*/
color:#FFFFFF;
position: relative; /*位置プロパティを『相対』に指定*/
top:29px; /*上側を指定幅ずらす*/
left:34px; /*左側を指定幅ずらす*/
}

/*基本要素全てまとめようと思ったけど想像以上に面倒かったのでこれで許して
ちなみにfloatとpaddingとmarginとposition辺りを使いこなせれば位置調整に困ることはないはず
:hoverとか:actionとか使えばオサレな表示も思いのままゾイ 詳しくはググってどうぞ*/




適用範囲の指定方法は。

これも非常に簡単でHTMLのタグ内でclass="(クラス名)"を宣言してCSS側で.(クラス名)と指定すればそれでOK。一応IDに被せるやり方もあるけど、JS使いに怒られるらしいのでやめた方が無難。QiitaのCSSもclassで書いてるのでまあ指定がない限りclassで書けば良いんじゃないかなって気はする。

ちなみに.(クラス名)の後に要素名を書けば指定範囲を要素だけに限定できるぞい。あとbodyはクラスでは無いので.を付けなくても良かったりする。

body {
font-family: 'Arial';   /*フォントの指定*/
}

.hoge h1{/*H1タグのみに範囲を指定*/
color:red;
}



このようにとってもとっても便利なCSSだけど、ただ1つ気をつけないといけない点があって実はどんなに雑に書いても動いたりする。

要素指定の重複はもちろんOKだし、書く順番もごちゃごちゃでOK、クラス名をタイポしてもエラーは吐きません。だからまあ………………CSSぐらい楽勝でしょってノリで書くと多分その先は地獄なんじゃないかなって思ったりもします。



PHPってHTMLの中に書けるのに.phpってどういう意味だってばよ?


まあ結論から言ってしまうと、HTMLとPHPを使って.htmlの設計図を記述するのが.phpです。コンテンツを表示する場合は、サーバー側が.phpを処理してクライアント側に.htmlを送信します。バックエンドですね。

文法の解説はここではしません。参考書の劣化コピーになりそうですし、何より面倒いので………………

特徴を上げるとすれば良く出来た言語だと思います。WEB系の言語としては最近RUBYに食われつつある感じはありますが、RUBYって学習コスト高くない? 

railsさえマスターすればOK!みたいな言い草で語られるけど、1つの技術に依存すれば需要が落ちた時買い叩かれるのは明白だし、いざ2つ目のスキルを考える段階でもRUBYとのマルチランゲージを考えると適切な言語が存在しない。強いて言えばPythonか???



………………話が逸れましたね。

後は何の話をしましょうか。技術的な面と言っても自分は入門書を数冊読んだだけのレベルなのであんまり深い事は言えなかったりする。まあ動的型付けや配列サイズが不定なのって便利だねと思ったり、Javaとは一味違う方法でオブジェクト指向を実装するのも中々楽しいです。

という訳で最後に一言

   みんな‼ PHPやろうぜ!



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

必要コマンド

● pwd
現在のフォルダ(ディレクトリ)を調べるためのコマンド「Print Working Directory」の略。

● mkdir
フォルダを作るためのコマンド「Make Directory」の略。

$ mkdir 作成したディレクトリ名

● cd
ディレクトリ間を移動するためのコマンド「Change Directory」の略。

$ cd 移動したいディレクトリ名

また

$ cd ..
1つ上(親)のディレクトリに戻ることができる。

また

$ cd ~
または
$ cd
ホームディレクトリに移動が可能。そのターミナルを実行しているユーザーの「ホームディレクトリ」を意味している。

●Is
ディレクトリの中身を表示するためのコマンドで「LiSt directory contents」の略。

$ mkdir space
$ Is
で、mkdir作成したspaceというディレクトリが、現在のディレクトリに存在することを確認できる。

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

WordPressを更新したら500エラーになった

WordPressのアップデートをいつも管理画面の「WordPress の更新」から気楽にしていました。先日WordPressのバージョンを5.3.2にアップデートしたところ500エラーが出ました。

WordPressの管理画面にはアクセスできるものの、管理画面の投稿一覧にアクセスすると500エラーになり、「ホストに接続できません」と表示されます。
サイトのページも真っ白です。。。

環境

  • KUSANAGI for AWS
  • WordPress 5.3.2(最新化済み)
  • PHP 5.6

エラー内容を確認する

エラー内容を確認するため、WordPressをデバッグモードにします。
wp-config.php を開きます。KUSANAGIのデフォルトでは /home/kusanagi/kusanagi_html/DocumentRoot にファイルがありました。

define('WP_DEBUG', false);

その行を次のように変更します。

define('WP_DEBUG', true);

nginxの再起動などは不要。サイトにアクセスすればエラー内容が確認できるようになっているはずです。

エラー内容

デバッグモードによって表示されたエラー内容は次の通りです。

Call to undefined function date_create_immutable_from_format() 
in /home/kusanagi/kusanagi_html/DocumentRoot/wp-includes/general-template.php 
on line 2645

解決方法

上記エラー内容だけ見てネット検索したところ、PHPのアップデートが必要との記事がヒットしました。
私は KUSANAGI for AWS を使っているため、「PHP5を削除してPHP7をインストールする」といった手段は取らず、以下のコマンドを実行してPHP7にアップデートしました。

# yum update -y
# kusanagi update plugin
# kusanagi php7

500エラーはなくなり、投稿一覧が表示されるようになりました。 wp-config.php を元に戻しておしまいです。

さいごに

真っ白い画面が出たときは正直焦りました。
PHPバージョン古いのと、バックアップもとらずにWordPressの更新を気楽にやりすぎですね。気を付けたいと思います。

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

Laravel の環境構築でつまづいた

はじめに

macOSでの話です。

Laravel公式のインストールサイト(日本語訳)通りに進んでいたが、

composer global require laravel/installer

の入力が上手くいかず、下記のエラーが出て数時間つまづいたので誰かの役に立ったらと思い書きます。

Your requirements could not be resolved to an installable set of packages.

Problem 1

Installation request for laravel/installer ^2.1 -> satisfiable by laravel/installer[v2.1.0].
laravel/installer v2.1.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.

どのようにエラーを取ったか

同じ症状で悩んでいる人が teratailにいたので質問していたのでその回答通りにやってみました。(pecl install zip は peclコマンドが使えなかったのでスルーした。)

brew で phpを入れるのは、https://laracasts.com/series/laravel-6-from-scratch/episodes/1 の動画サイトを1~3まで見ればわかります。

同じ症状で悩んでいる人書いてくれている

brew link php

を実行する。

そうすると、

Linking /usr/local/Cellar/php/7.4.1... 
Error: Could not symlink bin/pear
Target /usr/local/bin/pear
already exists. You may want to remove it:
  rm '/usr/local/bin/pear'

To force the link and overwrite all conflicting files:
  brew link --overwrite php

To list all files that would be deleted:
  brew link --overwrite --dry-run php

というエラーが出てくるので

 brew link --overwrite php

を実行、そして再起動してcomposer global require laravel/installer
を実行して完了です。

補足

・・・composerのダウンロード
https://kohkimakimoto.github.io/getcomposer.org_doc_jp/doc/00-intro.html
これを手順に

composerの使い方。composer.jsonやcomposer.pharを使わなきゃいけなくなった時このサイトを勝つよすると良い。https://laboradian.com/php-composer/

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

PHPを勉強したのでフィボナッチ数列を求めるコードを書いてみた

初投稿です。

プログラミング学習を始めてある程度時間が経って簡易的なCRUD機能アプリは作ってみたのですが、四則演算等アルゴリズム的なのはあまり使用しなかったので腕試しの意味も込めやってみました。

フィボナッチ数列

フィボナッチ数列とは下記の数列のように今の項と前項の和が次の項となるような数列です。

1 1 2 3 5 8 13 21 34 55 89 144 ・・・・N

第一項の値1と第二項の値1の和が第三項の値2になっています。
以降これの繰り返しを行う数列のことです。

式で表すと

第N項の値 + 第N+1項の値 = 第N+2項の値

こんな感じです。

中学入試や大学入試でよく見るやつです。
(漸化式懐かしい)

実際にPHPで書いてみる

image.png

書いてみて

色々復習になった。
こういった問題でアウトップト必要ですね。

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

エラー「Uncaught Error: Access to undeclared static property: 〜」の対処方法

発生状況

phpでクラスから別クラスの変数を取得しようとした際にエラー発生しました。

簡単な処理を以下に記載します。
別クラスから取得した変数を出力する処理で、yamada と出力されれば成功です!

処理内容
$person = new GetInfo;
$person->getName();

class PostInfo{
    public $name = 'yamada' . PHP_EOL;
}

class GetInfo{
    public function getName(){
        echo PostInfo::$name;
    }
}
結果
Fatal error: Uncaught Error: Access to undeclared static property: PostInfo::$name

このように PostInfoクラスの $name 変数が取り出せずにエラーになってしまいます。

解決方法

$name 変数前に static を記載することで解決しました。

example.php
$person = new GetInfo;
$person->getName();

class PostInfo{
    static public $name = 'yamada' . PHP_EOL;
}

class GetInfo{
    public function getName(){
        echo PostInfo::$name;
    }
}
結果
yamada

このように成功しました!

staticプロパティについて

こちらのエラーをわかりやすく解説しているサイトがありましたので、学んだことを簡単に引用させていただきました。

staticキーワードを付けたプロパティには、外部から直接アクセス出来ます。
staticキーワードを付けて宣言したプロパティは、クラスをインスタンス化せずに使用できます。
staticを指定していないプロパティにstatic形式でアクセスすると、「PHP Fatal error: Access to undeclared static property」エラーが発生します。

staticプロパティの参考サイト
http://akutaka00.blog102.fc2.com/blog-entry-69.html

まとめ

つまり今回「staticを指定していないプロパティにstatic形式でアクセス」していたことが原因だったことがわかりました。
プログラミングの学習をしていると様々なエラーが発生し心折れそうになりますがきちんと原因がわかると、知識が増えた・理解が深まったとポジティブな方向に持っていけることが良いことだなと思いました。

これからも理解したことを少しずつアウトプットしていければと思います。

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

brew の PHP が 7.4 に上がったら Mcrypt PHP extension required. が出たので対応

Mac OS X 10.15.2 で brew でインストールした PHP のバージョンが 7.4 に上がった際に一部のプログラムが Mycrypt PHP extension required. で動かなくなったのでその対応

mcrypt の手動 Install

$ wget https://pecl.php.net/get/mcrypt-1.0.1.tgz                                                                                                                                                                                                          
$ tar zxvf mcrypt-1.0.1.tgz
$ cd mcrypt-1.0.1
$ phpize
$ ./configure
$ make 
$ make install
$ rm -rf ../mcrypt-1.0.1*

インストールが完了したら PHP の設定を変更する

$ vim /usr/local/etc/php/php7.4/php.ini
...
extension=mcrypt ; mcrypt を読み込むように設定
...

試したこと

あまり調べていないので単純に設定等が足りないだけの可能性もある

pecl でのインストール

PHP7.3 利用時にインストールしていたものがすでにインストールされているということでインストールできなかった

$ sudo pecl install channel://pecl.php.net/mcrypt-1.0.1
pecl/mcrypt is already installed and is the same as the released version 1.0.1
install failed

pecl での再インストール

PHP7.2 から PHP7.3 でないとインストールできなかった

$ sudo pecl uninstall mcrypt-1.0.1
Extension mcrypt disabled in php.ini
uninstall ok: channel://pecl.php.net/mcrypt-1.0.1
$ pecl install mcrypt-1.0.1
pecl/mcrypt requires PHP (version >= 7.2.0, version <= 7.3.0, excluded versions: 7.3.0), installed version is 7.4.1
No valid packages found
install failed
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今日は何のゴミの日か教えてくれるLineBotを作成してみた

要件定義

Lineに「今日は何のゴミの日?」とか「明日は?」、「明後日は」とかメッセージを送ると「燃えるゴミの日です」とか「資源プラゴミの日」ですとか返信してくれるLineBotを作成します。

開発環境

CENTOSバージョン:8.0

$cat /etc/redhat-release
CentOS Linux release 8.0.1905 (Core) 

PHPバージョン:7.3

$ php -v
PHP 7.3.12 (cli) (built: Nov 19 2019 10:24:51) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.12, Copyright (c) 1998-2018 Zend Technologies

MySQLバージョン:8.0

$ mysql --version
mysql  Ver 8.0.17 for Linux on x86_64 (Source distribution)

データベース設計

データベース名は「linebot」とします。とりあえず、今後もいろんなlinebotを作成していくため。
テーブル名は「garbageday」(訳すとゴミの日)とします。

カラム

  • id
  • day:日付
  • garbage:ゴミ捨ての種別

Line側の設定

以下にアクセスし、ログインして、諸々設定します。
https://developers.line.biz/ja/

コチラを参考にして、Messaging APIの設定を実施
https://qiita.com/caique/items/50e00c4a5801e2d583a4

MySQLの設定

mysqlインストール

$ sudo yum -y install mysql  mysql-server

rootでログイン

# mysql -u root -p
Enter password: 

rootのパスワード変更

  • 8文字以上
  • 数字、英字、記号含む
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY "(パスワード)";

ユーザーの作成

mysql> create user "jum"@"localhost" identified by '(パスワード)';

データベースの作成

mysql>create database linebot default character set utf8mb4;
Query OK, 1 row affected (0.00 sec)

linebotデータベースに権限を付加する。

mysql>grant all privileges on linebot.* to "jum"@"localhost" identified by '(パスワード)';
Query OK, 0 rows affected, 1 warning (0.00 sec)

※mysql 8.0ではidenti…からは必要ない。

ユーザーを変更する

mysql> exit
$ mysql -u (ユーザー名) -p

テーブルを作成する

テーブル名は「garbageday」とする

mysql> use linebot;
Database changed
mysql> create table garbageday(id int not null primary key auto_increment,day date,garbage varchar(50));
Query OK, 0 rows affected (0.02 sec)

テーブルの構成を確認する。

mysql> show columns from garbageday;
+---------+-------------+------+-----+---------+----------------+
| Field   | Type        | Null | Key | Default | Extra          |
+---------+-------------+------+-----+---------+----------------+
| id      | int(11)     | NO   | PRI | NULL    | auto_increment |
| day     | date        | YES  |     | NULL    |                |
| garbage | varchar(50) | YES  |     | NULL    |                |
+---------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

挿入するデータを作成する

sql文のファイルを作成する

$ vi insert.sql
insert.sql
insert into garbageday(day,garbage) values("2020-01-14","燃えるゴミ");
insert into garbageday(day,garbage) values("2020-01-15","不燃物ゴミ");
insert into garbageday(day,garbage) values("2020-01-16","資源プラゴミ");
insert into garbageday(day,garbage) values("2020-01-17","燃えるゴミ");
insert into garbageday(day,garbage) values("2020-01-18","無し");
insert into garbageday(day,garbage) values("2020-01-19","無し");
insert into garbageday(day,garbage) values("2020-01-20","缶とびんゴミ");
insert into garbageday(day,garbage) values("2020-01-21","燃えるゴミ");
insert into garbageday(day,garbage) values("2020-01-22","資源プラゴミ");
insert into garbageday(day,garbage) values("2020-01-23","スプレー缶蛍光管ゴミ");
insert into garbageday(day,garbage) values("2020-01-24","燃えるゴミ");
insert into garbageday(day,garbage) values("2020-01-25","ペットボトルゴミ");
insert into garbageday(day,garbage) values("2020-01-26","無し");
insert into garbageday(day,garbage) values("2020-01-27","古紙・布類ゴミ");
insert into garbageday(day,garbage) values("2020-01-28","燃えるゴミ");
insert into garbageday(day,garbage) values("2020-01-29","無し");
insert into garbageday(day,garbage) values("2020-01-30","資源プラゴミ");
insert into garbageday(day,garbage) values("2020-01-31","燃えるゴミ");

データを挿入する

$ mysql -u jum -p linebot < insert.sql

挿入されたか確認する

$ mysql -u jum -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 31
Server version: 8.0.17 Source distribution

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql> use linebot
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from garbageday;
+----+------------+--------------------------------+
| id | day        | garbage                        |
+----+------------+--------------------------------+
|  1 | 2020-01-14 | 燃えるゴミ                     |
|  2 | 2020-01-15 | 不燃物ゴミ                     |
|  3 | 2020-01-16 | 資源プラゴミ                   |
|  4 | 2020-01-17 | 燃えるゴミ                     |
|  5 | 2020-01-18 | 無し                           |
|  6 | 2020-01-19 | 無し                           |
|  7 | 2020-01-20 | 缶とびんゴミ                   |
|  8 | 2020-01-21 | 燃えるゴミ                     |
|  9 | 2020-01-22 | 資源プラゴミ                   |
| 10 | 2020-01-23 | スプレー缶蛍光管ゴミ           |
| 11 | 2020-01-24 | 燃えるゴミ                     |
| 12 | 2020-01-25 | ペットボトルゴミ               |
| 13 | 2020-01-26 | 無し                           |
| 14 | 2020-01-27 | 古紙・布類ゴミ                 |
| 15 | 2020-01-28 | 燃えるゴミ                     |
| 16 | 2020-01-29 | 無し                           |
| 17 | 2020-01-30 | 資源プラゴミ                   |
| 18 | 2020-01-31 | 燃えるゴミ                     |
+----+------------+--------------------------------+
18 rows in set (0.00 sec)

Lineで返信するための、コードを作成

linebot.php
<?php

//line apiより
$accessToken = '(ここにはline apiよりアクセストークンを確認し入力してください)';

//ユーザーからのメッセージ取得
$json_string = file_get_contents('php://input');
$json_object = json_decode($json_string);

//取得データ
$replyToken = $json_object->{"events"}[0]->{"replyToken"};        //返信用トークン
$message_type = $json_object->{"events"}[0]->{"message"}->{"type"};    //メッセージタイプ
$message_text = $json_object->{"events"}[0]->{"message"}->{"text"};    //メッセージ内容

//メッセージタイプが「text」以外のときは何も返さず終了
if($message_type != "text") exit;

//mysqlに接続
define("DB_DATABASE","linebot");
define("DB_USERNAME","jum");
define("DB_PASSWORD","(設定したパスワード)");
define("PDO_DSN","mysql:host=localhost;dbname=" . DB_DATABASE);

 try{
   $db = new PDO(PDO_DSN,DB_USERNAME,DB_PASSWORD);
   $db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
   $db->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
    } catch(PDOException $e) {
   $db->rollback();
   echo $e->getMessage();
   exit;
 }

//日付関連の処理
date_default_timezone_set('Asia/Tokyo');
$date = new DateTime();
$today = $date->format('Y-m-d');
$tomorrow = $date->modify('+1 days')->format('Y-m-d');
$day_after_tomorrow = $date->modify('+1 days')->format('Y-m-d');

//ユーザーが確認したいゴミの日
$Verification_Garbage_Day = "";

if(strpos($message_text,"今日") !== false) {
  $Verification_Garbage_Day = $today;
}
elseif(strpos($message_text,"きょう") !== false) {
  $Verification_Garbage_Day = $today;
}

elseif(strpos($message_text,"明日") !== false) {
  $Verification_Garbage_Day = $tomorrow;
}
elseif(strpos($message_text,"あした") !== false) {
  $Verification_Garbage_Day = $tomorrow;
}
elseif(strpos($message_text,"明後日") !== false) {
  $Verification_Garbage_Day = $day_after_tomorrow;
}

else {
  $Verification_Garbage_Day = "";
}

//変数$Verification_Garbage_Dayに値が入っていた場合、データベースと照合する
if(!empty($Verification_Garbage_Day)){

//返信する内容を保存する変数
$Return_Verification_Garbage_Day = "";

//データベースから読み取ったゴミの日
try{
    $sql = "select * from garbageday";
    $stmt = $db->query($sql);
    }
    catch(PDOException $e) {
    $db->rollback();
    echo $e->getMessage();
    exit;
                          }

    //データベースとユーザーが投稿した内容が一致するか確認する
    while($Garbage_Day_db = $stmt->fetch(PDO::FETCH_ASSOC)){

    if($Garbage_Day_db["day"] == $Verification_Garbage_Day) {

      $Return_Verification_Garbage_Day = $Garbage_Day_db["garbage"];

    }
  }
}else {
  $Return_Verification_Garbage_Day = "もう一度入力してください";
}

  sending_messages($accessToken, $replyToken, $message_type, $Return_Verification_Garbage_Day);
?>

<?php
  function sending_messages($accessToken, $replyToken, $message_type, $Return_Verification_Garbage_Day){



    //レスポンスフォーマット
    $response_format_text = [
        "type" => $message_type,
        "text" => $Return_Verification_Garbage_Day
      ];

        //ポストデータ
        $post_data = [
            "replyToken" => $replyToken,
            "messages" => [$response_format_text]
        ];

        //curl実行
        $ch = curl_init("https://api.line.me/v2/bot/message/reply");
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json; charser=UTF-8',
            'Authorization: Bearer ' . $accessToken
        ));
        $result = curl_exec($ch);
        curl_close($ch);
    }

?>

こんな感じで出来上がりました

スクリーンショット 2020-01-13 14.16.32.png

データベースに今日の日付を入れていなかったので、今日には反応しませんでした。
明日が今日になれば反応するはず。

以上です。

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

【画像処理】Webサービス運営者に知ってもらいたいTwitterカードを自動生成する方法

こんにちは、いちいちです。@ichiichi_1115
PHPで画像を自動生成してTwitterカードにしてみたので、その方法を説明します。
Webサービス運営者 特に投稿サービスを運営している人には、絶対に読んでもらいたいものとなっています。

作ったもの

画像加工といっても、ただメインの画像に文字を付けただけです。
shereimage.png
これに、文字をいい感じにつけて
4.png
こうしました。(内容から筆者が運動嫌いなことがわかる)。。。

使った言語

PHPです。
5.2.0以上に、今回使うGDモジュールが標準で入っているらしいのですが、今どきそれ以下を使っている人は多分いませんね(今Windows9x系使っているようなもの)。

それ以下の方は、インストール手順を調べてみてください。

作り方

PHPで生成しました。

image.php
$img = imagecreatefrompng("mainimage.png");
$text = "運動したくないでござる";//表示するフォント

$font = "font/ipaexg.ttf";//フォントの指定(IPAフォント)

$color = imagecolorallocate($img,0,0,0);//色の指定 RGB値0~255

$fsize = 50;//文字のサイズ
$x = 0;//X座標
$y = 50;//Y座標
$rotate = 0;//文字の角度

imagettftext($img,$fsize,$rotate,$x,$y,$color,$font,$text);

imagepng($img,"image.png");

解説

$img = imagecreatefrompng("mainimage.png");

元画像のURLです。
もし、読み込むファイルがjpegならimagecreatefromjpeg();を、
bmpならimagecreatefrombmp();を使用してください。

$font = "font/ipaexg.ttf";

フォントへのパスを書きます。
拡張子がTTF、TTCの、True Type Font なら動きます。
それ以外(Open Typeなど)のフォントは動きません。

imagettftext($img,$fsize,$rotate,$x,$y,$color,$font,$text);

実際に画像に描画しているところです。
画像ID,フォントサイズ,回転,x座標,y座標,色,フォント,文字 です。

imagepng($img,"image.png");

画像リソースを、画像ファイルとして出力しているところです。

jpegならimagejpeg();としてください。

Twitterカードにする

index.php
<?php
    $filename = "image";
?>
    <meta name="twitter:card" content="summary"><!-- summary か summary_large_image -->
    <meta name="twitter:site" content="https://www.example.com/">
    <meta name="twitter:image" content="https://www.example.com/<?php echo $filename;?>.png">
    <meta name="twitter:title" content="タイトル">
    <meta name="twitter:description" content="ページの内容">

実際はいくつも投稿されるはずなので、ファイル名を変えて保存しましょう。

完成結果

自動でいい感じに生成されました。

注意点

フォントは、再配布に当たるかもしれないので、再配布OKのものを使ってください。
IPAフォントは、ライセンス文を一緒につけておけばOKです。

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

CakePHP3 Cake\I18n\Timeのmodify()に気をつける

TL;DR

  • Cake\I18n\Timeクラスが用意するmodifyメソッドの挙動に関して
    (以下、modifyと呼ぶ)
  • modifyは、生成されたインスタンス自身を上書く習性がある(ようだ)
    • 以下のサンプルコードを参考にして欲しい

サンプルコード

$time = new Cake\I18n\Time();
for ($i = 0; $i < 3; $i++) {
    // 主力結果を見易くするためにあえて改行コードを付ける
    echo $time->i18nFormat('yyyy-MM') . "\n";
    echo $time->modify('next month')->i18nFormat('yyyy-MM') . "\n";
}

/*
 * 期待値
 *
 * 2020-01
 * 2020-02
 * 2020-01
 * 2020-02
 * 2020-01
 * 2020-02
 *
 */

/*
 * 実行値
 *
 * 2020-01
 * 2020-02
 * 2020-02
 * 2020-03
 * 2020-03
 * 2020-04
 *
 */

公式ドキュメントはこちら: https://book.cakephp.org/3/ja/core-libraries/time.html

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