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

共同開発講座を受講中にコツコツ書いた「詰まったメモ」をジャンル別に全て公開します【総まとめ集編】

はじめに

タイトルの通り、共同開発講座を受講中に起きたトラブルや詰まりポイントをまとめたメモ(約50記事)をジャンル別に全て公開します。
ジャンルは以下の通りなので、気になるものがあればぜひご覧ください。

【ジャンル一覧】

GitHub関連
Laravel関連
データベース(migration/seeding)関連
環境関連
総まとめ集

下記の点を、ご了承ください。
・ジャンルで統一しているため、内容に関しては統一性はありません。
・メモの難易度もバラバラです。
・初学者向けの内容となっております。
・自分用のメモを転用しておりますので、表現が稚拙な部分があるかと思います。
・あくまで僕のメモです!!

・マイグレーションとシーディングの説明と一連の流れ

マイグレーションとは

ザックリ言うとテーブルを作成するための作業
マイグレーションファイルをコマンドで作成し、作りたいテーブルのカラムを記載していく。
記載したファイルをコマンドでマイグレーションすると、データベースにテーブルが作成される。
また、マイグレーションファイルにはカラムに対してデータ型や修飾子、FKを記載しておくとマイグレーション実行時に反映される。
ただ、FKの関係でマイグレーションする順番が重要になってくるので、マイグレーションファイルを作成する順番に気を付ける。(FKで指定されているテーブルがないと、エラーが起こるので、先に親テーブルからマイグレーションされるように作成順番を考える。→子テーブルを後に作成する)
今回はphpmyadminをしようしているので、ブラウザ上で視覚的に確認することができる。
Phpmyadmin上で、使用するデータベースを作成しておかないと、テーブルをその中に作ることができないので注意

マイグレーションファイルの作成(カラム、FK、その他修飾子の作成)

php artisan make:migration ファイル名

でファイル作成。

ファイルの中身はこんな感じ

 public function up()
    {
        Schema::create('m_products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('product_name', 64);
            $table->integer('category_id')->unsigned();
            $table->integer('price')->unsigned();
            $table->string('description', 256);
            $table->integer('sale_status_id')->unsigned();
            $table->integer('product_status_id')->unsigned(); 
            $table->timestamp('resist_date');
            $table->integer('user_id')->unsigned();
            $table->char('delete_flag', 1);

            $table->foreign('sale_status_id')->references('id')->on('m_sales_statuses')->onDelete('cascade');
            $table->foreign('product_status_id')->references('id')->on('m_products_statuses')->onDelete('cascade');
        });
    }

データ型について

上記のファイル内の記載のうち、string integer timestamp などがデータ型にあたる。
そのデータはどういう形式のデータなのか?文字なのか?数字なのか?その時の時間なのか?
を指定することができる。詳しくはGoogle先生に。(他の記事にも記載あります)

php artisan migration の実行

このコマンドを実行すると、作成したマイグレーションファイルがテーブルとして作成される。
エラーになる場合は、他のエラーに関するメモを参照。

migrationエラーについて

多かったエラーは
①データ型があっていない場合
②migrationする順番が違い、外部キー制約がつかないために起こるエラー

シーディングとは

ザックリい言うと、テストデータを一気に作成することができる機能で、ファイルを作成してシーディングを行うことテーブル内にデータが挿入される。

テストデータの決定

シーディングを行うテーブルに適したデータを考える。
例えば、商品(肉とか魚)のテストデータを考える時は、それぞれのカラムに適応した具体的なデータ(商品名や値段)を考える。
何個でも一気に登録できるが、まぁ、テストなので5~10個程度あれば十分。

シーディングファイルの作成

php artisan make:seeder ファイル名

上記コマンドでシーディングファイルが作成される。
中身には前項で決定したテストデータを記載していく。

実際の内容としてはこんな感じ

public function run()
    {
        DB::table('m_products')->insert([
           'id' => 1,
           'product_name' => '黒毛和牛サーロイン',
           'category_id' => 1,
           'price' => 8000,
           'description' => 'なめらかでとろける食感と、甘くコクがある上品な香りが特徴です' ,
           'sale_status_id' => 1,
           'product_status_id' => 1,
           'resist_date' => date('Y-m-d H:i:s'),
           'user_id' => 1,
           'delete_flag' => ''
        ]);
        DB::table('m_products')->insert([
            'id' => 2,
            'product_name' => 'A5ランク松坂牛',
            'category_id' => 1,
            'price' => 12000,
            'description' => '松坂牛は濃厚で上品な甘みが絶品!' ,
            'sale_status_id' => 1,
            'product_status_id' => 1,
            'resist_date' => date('Y-m-d H:i:s'),
            'user_id' => 1,
            'delete_flag' => ''
         ]);

例として2つだけここに書いたが、実際は何個でも良い。

php artisan db seed  の実行

このコマンドを実行することで、作成したシーディングファイル(テストデータ込み)がシーディングされて、当該テーブルにデータが挿入される。
このコマンドの実行時にエラーとしてよく起こるのは
①子テーブルと親テーブルの関係
②外部キー関連
が多いが、詳しくは別のメモにまとめてあるものを参照。

・viewの実装手順(一応ここでは新規ユーザ登録画面)

実装の流れ

①作成していたモックアップ画面のhtml要素をコピーして、lara-d(プロジェクト)の当該ファイルに貼り付ける
②laravelcollectiveを導入する
③formタグ部分をlaravelcollectiveで書き換える。この時のformタグ内でのデータ型はデータベースのものとは違うので気を付ける(例:stringやintegerは使えない。ルーティングエラーになる。form内はtext型を使う)
④バリデーションをcontrollerファイル内で定義する。この時の指定はintegerやstringを使って行う。
⑤rink_to_routeの実装を行なって、挙動がしっかりしていることを確認する

ブラウザ上でviewファイル(ルーティング)を確認する方法

URLに何を打ち込めばviewファイルが表示されるのか??

URLに打ち込むのは…

192.168.33.11/signup

192.168.33.11は自分で決めたローカルホストのIPアドレスのこと。
と打ち込めばローカルホストに繋がり、「signup」というルーティングから行き着く先のviewファイルを表示することができる。この「signup」はweb.phpに記述したルーティングの内容のこと。

これを打ち込めば「signup」というルーティング名で設定した先のviewが表示される

・新規ユーザ登録機能の実装

実装手順

1.画面(新規ユーザ登録画面フォーム)を作る

・LaravelCollectiveを導入
・フォームに情報を持たせてLaravelCollectiveで実装していく
→Form open/close 送信するルーティング名を指定
→カラム名、old()関数、クラス(bootstrap)
password確認のフォーム実装は’confirmation’が必要
→型は基本的にtextで必要に応じてemail型やpassword型を記述する
・今回はcssが崩れていたのでcssも編集

2.ルーティングの実装

・「ユーザ新規登録画面を表示させる」ルーティングを書く(signup)
・「ユーザ新規登録させる」ルーティングを書く(signup.post)

Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');

RegisterControllerは元々存在し、Registerアクションも元々存在する!(トレイトに入っている)
→なので、コントローラを新しく生成する必要はない。

3.Userモデルを作成

・Userモデル自体は元々存在する
・$fillableに作成したいユーザの情報要素を書いていく
・どのテーブルにそのモデルが作成されるのかを指定する

protected $table = 'm_users';

protected $fillable = [
        'last_name',
        'first_name',
        'zipcode',
        'prefecture',
        'municipality',
        'address',
        'apartments',
        'email',
        'phone_number',
        'password',
        'company_name',
    ];

4.バリデーションの実装

・RegisterControllerにバリデーション記載していく
・Userモデルで定義したカラム(フォームから飛んでくる項目)に対して記載する
・integerよりもnumericを使用する

return Validator::make($data, [
        'last_name' => ['required', 'string', 'max:10'],
        'first_name' => ['required', 'string', 'max:10'],
        'zipcode' => ['required', 'numeric', 'digits:7'],
        'prefecture' => ['required', 'string', 'max:5'],
        'municipality' => ['required', 'string', 'max:10'],
        'address' => ['required', 'string', 'max:191'],
        'apartments' => ['required', 'string', 'max:191'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'phone_number' => ['required', 'numeric', 'max:15'],
        'password' => ['required', 'string', 'min:6', 'max:15', 'confirmed'],
    ]);

5.createアクションの中身の記載

・RegisterControllerのcreateアクションに作成したいデータを変数に入れていく
・Userモデルで定義したカラム(フォームから飛んでくる項目)に対して記載する

return User::create([
            'last_name' => $data['last_name'],
            'first_name' => $data['first_name'],
            'zipcode' => $data['zipcode'],
            'prefecture' => $data['prefecture'],
            'municipality' => $data['municipality'],
            'address' => $data['address'],
            'apartments' => $data['apartments'],
            'email' => $data['email'],
            'phone_number' => $data['phone_number'],
            'password' => bcrypt($data['password']),
        ]);

6.リダイレクト先の変更

・RegisterControllerの上の方にある$redirectTo=の部分を”/”に変える(新規登録後に飛ぶページにリンクするように)

protected $redirectTo = '/';

7.リンク先の指定(ページ内のボタン及びヘッダーからの画面遷移)

・ページ内のボタンのリンクをページ遷移に従って変更
・ヘッダーからの画面遷移に従ってリンク先を変更

その他(注意点)

user_classification_idに先にテストデータを流し込んでおくこと
ブラウザからフォームを送信してデータベースに登録する際にSQL1364エラーが起こることがある
→このエラーが一番引っかかった。。
これは今回で言うと、user_classification_idにデータが入っていないことで起こる。また、マイグレーションファイルにデフォルト値が指定されていないことが原因である。詳しくは別のエラーメモを参照(SQL1364エラー)

->nullable()と->default()について
なるべく  ->nullable()  よりも  ->default()  を用いるほうがいい。
また、default値を指定するときについて
今回の場合は、登録されるユーザはどんなタイプのユーザなのか?(user_classification_id)を意識してデフォルト値を設定すること。

・商品詳細画面の実装(セッションの保存まで)

web.php
Route::resource('cartitem', 'CartController', ['only' => ['index']]);
Route::group(["prefix" => 'iteminfo'], function() {
    Route::get('/{id}', 'CartController@show');
    Route::post('/add', 'CartController@addCart')->name('addcart');
});
CartController.php (showアクション)
public function show($id)
    {
        //変数の初期化
        $ProductInfo = array();
        $ProductCategory = array();
        $UserId = '';
        //urlパラメータから飛んできたユーザidを元にモデルからそれぞれ商品、カテゴリーを特定
        $ProductInfo = Product::findOrFail($id);
        $ProductCategory = Category::findOrFail($ProductInfo -> category_id);
        $UserId = Auth::user()->id;

        return view('iteminfo', 
        [
            'ProductInfo' => $ProductInfo,
            'ProductCategory' => $ProductCategory,
            'UserId' => $UserId,
        ]);
    }

〜iteminfo(商品詳細)画面の表示〜


{!! Form::open(['route' => 'addcart']) !!}
    {{ Form::hidden('user_id', $UserId) }}

    {{ Form::hidden('ProductId', $ProductInfo->id) }}

        <div class="form-group row justify-content-center">
            <label class="col-form-label">購入個数</label>
           <div class="">

           <input type="number" class="inputNumber form-control" value="0" name="Quantity" >
           </div>
            <lavel class="col-form-label"></lavel>
                <div class="col-sm-auto">
                 {!! Form::submit('カートへ', ['class' => 'btn btn-primary']) !!}
          </div>
        </div>
{!! Form::close() !!}

ここで

{{ Form::hidden('user_id', $UserId) }}

{{ Form::hidden('ProductId', $ProductInfo->id) }}

この3つのname属性で送信する

web.phpを介して

CartController(addCartアクション)へ

CartController(addCartアクション)
public function addCart(Request $request)
    {
        //セッションに保存したい変数を定義する(ここでは商品idと注文個数)
        //飛んできた$requestの中のname属性をそれぞれ指定
        $SessionProductId = $request->ProductId;
        $SessionProductQuantity = $request->Quantity;
        //配列の入れ物を作る(初期化)
        $SessionData = array();

        //作った配列に、compact関数を用いてidと個数の変数をまとめる(”” を使っているが変数の意味)
        $SessionData = compact("SessionProductId", "SessionProductQuantity");

        //session_dataというキーで、$SessionDataをセッションに登録
        $request->session()->push('session_data', $SessionData);

        return redirect('cartitem');

    }

このアクションでセッションにIDと個数を保存する。

$SessionProductId = $request->ProductId;
$SessionProductQuantity = $request->Quantity;

この二つの記述でIDと個数のそれぞれを変数と置く。

データをひとまとめにするために

$SessionData = array();

として、空の配列を定義する。

配列に二つの変数を統合する

$SessionData = compact("SessionProductId", "SessionProductQuantity");

この時ダブルクォーテーションがついているが"SessionProductId"は変数を意味している

そしてセッションにpushして配列に次々と値を加えていく形をとる

$request->session()->push('session_data', $SessionData);


web.phpのcartitemにリダイレクトする

CartControllerのindexアクションに繋がる

CartController(indexアクション)
public function index(Request $request)
    {
        //セッションに保存していた値を取得し、変数として定義
        $SessionData = $request->session()->get('session_data');
        //セッションデータのなかのそれぞれのデータを抽出
        $SessionProductId = array_column($SessionData, 'SessionProductId');
        $SessionProductQuantity = array_column($SessionData, 'SessionProductQuantity');
        dd($SessionData);


ここから下はまだ不明

セッションに保存していた値を取得し、変数として定義

$SessionData = $request->session()->get('session_data');

array_column関数を用いてIDと個数のデータをそれぞれ抽出

$SessionProductId = array_column($SessionData, 'SessionProductId');

あとはこのデータを用いてカート内商品一覧画面に表示できればそれで完了のはず。。

・バック商品修正画面の実装

ちらECサイトの中で、管理者(出品者)側の画面で、出品する商品を修正する画面の実装です

実装する内容

1.画面遷移時の商品情報のpre表示
2.商品修正機能
3.商品情報の削除機能

1.画面遷移時の商品情報のpre表示

BackProductController
public function edit($id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);

        //category関連の定義
        $categories = MCategory::getLists();
        $categoryName = MCategory::find($product->category_id)->category_name;
        $categoryId = $product->category_id;

        //sale_status関連の定義
        $saleStatuses = MSalesStatus::getLists();
        $saleStatusName = MSalesStatus::find($product->sale_status_id)->sale_status_name;
        $saleStatusId = $product->sale_status_id;

        //product_status関連の定義
        $productStatuses = MProductStatus::getLists();
        $productStatusName = MProductStatus::find($product->product_status_id)->product_status_name;
        $productStatusId = $product->product_status_id;

        return view('seller.back_product_edit',[
                'product' => $product,
                'categories' => $categories,
                'categoryName' => $categoryName,
                'categoryId' => $categoryId,
                'saleStatuses' => $saleStatuses,
                'saleStatusName' => $saleStatusName,
                'saleStatusId' => $saleStatusId,
                'productStatuses' => $productStatuses,
                'productStatusName' => $productStatusName,
                'productStatusId' => $productStatusId,
            ]
        );
    }

editアクションでその$idを持つ商品の修正画面に飛ばす。
MProductモデルから$idを持つ商品を探すときに、リレーション先のデータまで取得できるようにしなければならない。
MProductモデルに記載されているリレーションを貼った関数名をwith関数の引数に入力する。
その後、viewに返す。

例) 下記のような内容の関数名

MProduct.php(モデル)
public function saleStatus()
    {
        return $this->belongsTo(MSaleStatus::class);
    }
@extends('layouts.seller_app')
@section('content')
<main>

    <div class="page-header mt-5 text-center">
        <h4>商品情報修正</h4>
    </div>


    <div class="row mt-5 mb-5">
        <div class="col-sm-5 mx-auto">

            {!! Form::open(['route' => ['back_product_update', $product->id]]) !!}
            {{ method_field('PUT') }}


                <div class="form-group-sm">
                    {!! Form::label('productName', '商品名', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::text('productName', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productName'))
                            {{ $errors->first('productName') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('categoryName', '商品カテゴリ', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        <select class="form-control d-inline w-100" name="categoryId">
                        <option value={{ $categoryId }}>{{ $categoryName }}</option>
                            @foreach($categories as $id => $category_Name)
                                <option value={{ $id }}>
                                    {{ $category_Name }}
                                </option>  
                            @endforeach, 
                        </select>
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('categoryId'))
                            {{ $errors->first('categoryId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('price', '販売単価', ['class' => 'd-block mt-2 mb-0']) !!}
                    {!! Form::text('price', $product->price, ['class' => 'ml-3 mr-2 form-control col-sm-8 d-inline'])."円" !!}
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('price'))
                            {{ $errors->first('price') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('saleStatusName', '販売状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="saleStatusId">
                        <option value="{{ $saleStatusId }}">{{ $saleStatusName }}</option>
                            @foreach($saleStatuses as $id => $sale_StatusName)
                                <option value="{{ $id }}">
                                    {{ $sale_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('saleStatusId'))
                            {{ $errors->first('saleStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('productStatusName', '商品状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="productStatusId">
                        <option value="{{ $productStatusId }}">{{$productStatusName}}</option>
                            @foreach($productStatuses as $id => $product_StatusName)
                                <option value="{{ $id }}">
                                    {{ $product_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productStatusId'))
                            {{ $errors->first('productStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('description', '商品説明', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::textarea('description', $product->description, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('description'))
                            {{ $errors->first('description') }}
                        @endif
                    </div>
                </div>

                <div>
                    <div class="w-50 float-left">
                        <div class="text-center mt-5"> 
                            {!! Form::submit('修正', ['class' => 'button btn btn-primary mt-2']) !!}
                        </div>
                    </div>
                    {!! Form::close() !!}

                    {!! Form::open(['route' => ['back_product_delete', $product->id]]) !!}
                    {{ method_field('DELETE') }}
                    <div class="w-50 float-right">
                        <div class="text-center mt-5">
                            {!! Form::submit('削除', ['class' => 'button btn btn-danger mt-2']) !!}
                        </div>
                    </div>
                </div> 
            {!! Form::close() !!}
        </div>
    </div>
</main>
@endsection


------------申し訳ありません-----------------
メモ作成後にコードを編集したので以下、少し変わります。
ですが、知識としては使えますので参考までにお願いいたします。

--このView画面で$idの商品情報をpre表示させる--
例えばこれ

{!! Form::text('product_name', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}

(重要ポイント!!)
text型のformで
●第一引数:HTMLフォームの name属性
ここにはデータベースのカラム名を入れておくと良い
●第二引数:HTMLフォームの value属性
ここには予め表示したい内容を入れておく
●第三引数:追加属性(連想配列形式)
ここにはクラス(CSS要素)を記入

この第二引数が重要!!

$product->product_name

これは特定した $product のproduct_nameというカラムに格納されているデータを呼び出しており、入力欄にはそれが表示される。

ではこれはどうか

 {!! Form::text('category_name', $product->category->category_name, ['class' => 'form-control d-inline w-100']) !!}

第二引数は

$product->category->category_name

となっているが
これは
特定した$productからリレーションを貼っている関数名categoryを呼び出し、そのリレーション先にあるcategory_nameを呼び出している
という形。
こう書くことで、リレーション先のデータを取得することができる。

上記を全てのフォームに記載することで商品情報のpre表示が完成する。


-------ここまで、viewと比べてコードが違いますm(__)m---------

2.商品修正機能

BackProductController
public function update(CreateProductRequest $request, $id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);

        $product->product_name = $request->productName;
        $product->category_id = $request->categoryId;
        $product->price = $request->price;
        $product->sale_status_id = $request->saleStatusId;
        $product->product_status_id = $request->productStatusId;
        $product->description = $request->description;
        $product->save();

        return redirect('seller/items');
    }

updateアクションで商品情報を修正する

大まかな流れ

商品修正画面(back_product_edit.blade.php)のフォームから書き換えられた内容が送信された後、このアクションにくる。

飛んできた$requestを頼りに、それぞれのカラムデータに格納していく。

格納した情報を保存する
という流れ。

実装の解説

まず

$product = MProduct::with(['category', 'saleStatus' ,'productStatus'])->find($id);

修正する商品である$productを特定する。このときリクエストから飛んできたidを元にモデルから探し出す。さらにリレーション先のテーブルも取得できるようにwith関数を使用。
特定ができたら

$product->product_name = $request->productName;

その商品が持つproduct_nameを新しく$request->productNameと定義し直す。
また
リレーション先のデータを書き換えたい場合は

$product->category_id = $request->categoryId;

のようにリレーション先に指定している関数名(MProductモデルに記載)をアロー関数でつなぐことで、リレーション先のテーブルにあるcategory_nameカラムを指定することができる。

2.削除機能

割愛!!!!!!

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

【PHP Apache】ソースコードが表示される

LAMP環境構築の際、Apacheによりブラウザにindex.phpを表示させようとすると、ソースコードが表示されたので解決した方法をメモ。

下記ファイルをエディタで編集。


sudo vi /etc/apache2/httpd.conf

下記コメントアウト(#)を外す。

#LoadModule php7_module libexec/apache2/libphp7.so
↓
LoadModule php7_module libexec/apache2/libphp7.so

無事PHPとしてブラウザ表示成功。

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

静的htmlをwordpress化する方法

静的htmlをwordpress化する方法※編集中

固定ページ

phpファイルの記載内容

<?php get_header(); ?>
<!--header-->
~中略、ここにhtmlを記載する~

<?php get_footer(); ?>

ドメイン直下

wp-content/themes/使用しているテーマ/index.php

ショートコードを埋め込みたい

■元ファイル(固定ページや投稿)
[ショートコード]

■方法
○○.phpに以下記載
<?php echo do_shortcode( '[ショートコード]' ); ?>

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

オブジェクトのテクスチャを画像として保存する

ホワイトボードのようなもの作ってた時に使ってた。

大まかな流れ

1.対象のGameObjectを取得。
2.保存したいTextureを取得。
3.取得したTextureをバイト配列に変換。
4.サーバ上のphpに送信する。
5.phpで受信したバイト配列を画像に戻して保存する。
→受信側のphpは以前書いた記事そのまんまなので以下参照
pythonで画像を送信、phpで保存する
以上。

全体のソースコード

SendPic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using UnityEngine.Networking;

public class SavePic : MonoBehaviour
{
    Texture2D texture;
    byte[] picData;

    public void SendPic()
    {
    //1.対象のGameObjectを取得。
    //Find()だったり、FindGameObjectWithTagだったり、インスペクターから直接指定したりなどなど
        GameObject _wb = GameObject.FindGameObjectWithTag("Whiteboard");

    //2.保存したいTextureをGameObjectから取得し、
    //3.取得したTextureをバイト配列に変換。
        texture = (Texture2D)_wb.GetComponent<Renderer>().material.mainTexture;

    //SendDataを呼び出す(コルーチンというらしい)
        StartCoroutine(SendData(picData));
    }
    IEnumerator SendData(byte[] postData)
    {
    //4.サーバ上のphpに送信する
        String url = "https://hagehoge/pic_save.php";//httpにandroid端末から送る場合はパーミッション関連で何か必要だったような・・・
        var request = new UnityWebRequest(url, "POST");
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "image/png");

        yield return request.Send();
    }
}

実際やるとどんな感じなの

お絵かきして送信する。
image.png

WinSCP(FTPツール)で見てみるとこんな感じ。ちょっと細くなってるのは落書きに使ったPlaneは1.5倍に引き伸ばされてたため。
image.png

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

オブジェクトのテクスチャを送信して、画像として保存する

Unityでホワイトボードのようなもの作ってた時に使ってた。

大まかな流れ

1.対象のGameObjectを取得。
2.保存したいTextureを取得。
3.取得したTextureをバイト配列に変換。
4.サーバ上のphpに送信する。
5.phpで受信したバイト配列を画像に戻して保存する。
→受信側のphpは以前書いた記事そのまんまなので以下参照
pythonで画像を送信、phpで保存する
以上。

全体のソースコード

SendPic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using UnityEngine.Networking;

public class SavePic : MonoBehaviour
{
    Texture2D texture;
    byte[] picData;

    public void SendPic()
    {
    //1.対象のGameObjectを取得。
    //Find()だったり、FindGameObjectWithTagだったり、インスペクターから直接指定したりなどなど
        GameObject _wb = GameObject.FindGameObjectWithTag("Whiteboard");

    //2.保存したいTextureをGameObjectから取得し、
    //3.取得したTextureをバイト配列に変換。
        texture = (Texture2D)_wb.GetComponent<Renderer>().material.mainTexture;

    //SendDataを呼び出す(コルーチンというらしい)
        StartCoroutine(SendData(picData));
    }
    IEnumerator SendData(byte[] postData)
    {
    //4.サーバ上のphpに送信する
        String url = "https://hagehoge/pic_save.php";//httpにandroid端末から送る場合はパーミッション関連で何か必要だったような・・・
        var request = new UnityWebRequest(url, "POST");
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "image/png");

        yield return request.Send();
    }
}

実際やるとどんな感じなの

お絵かきして送信する。
image.png

WinSCP(FTPツール)で見てみるとこんな感じ。ちょっと細くなってるのは落書きに使ったPlaneは1.5倍に引き伸ばされてたため。
image.png

参考

・Photon Unity Networking 2 (PUN2) のRPCとRaiseEventを使ってテクスチャデータを送信する
https://nabla-tech-lab.hatenablog.com/entry/2019/05/15/180000

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

Codeigniterを使ってレスポンスで画像を返す

サーバ側で何かしらの処理を実行し、その結果に基づいてクライアント側に画像を返したい場合、Codeigniterに用意されている出力クラスを使うと便利です。
※画像以外にも任意のファイル形式を返す場合に有効です

imgタグのsrc属性に直接URLを指定したり、JavaScriptのImageクラスを動的に生成したりするときに使用することで、画像を簡単に表示することができます。
画像のURLのみを返すような処理であれば、画像そのものを返してしまった方が実装コストが低くて済むと思います。

コントローラーのサンプル

※画像($file_path)は適当に用意してパスを合わせてください。

Image_responder.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Image_responder extends CI_Controller {

    public function get_image()
    {
        $file_path = '/work/sample.png';
        $this->output
        ->set_content_type('jpeg')
        ->set_output(file_get_contents($file_path));
    }
}

HTMLのサンプル

※URLの部分は環境に合わせてください

TEST.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Codeigniter Test</title>
    <style>
        #request {
            display: block;
            padding: 10px;
            margin: auto;
            margin-top: 10px;
        }
        #results {
            display: flex;
            justify-content: center;
            background-color: rgba(0,0,0,.5);
            margin-top: 10px;
            min-height: 64px;
        }
        #results > img {
            width: 64px;
            box-sizing: border-box;
            padding: 5px;
        }
    </style>

    <script type="text/javascript">

        function getImage() {
            const img = new Image();
            img.onload = function (e) {
                document.getElementById('results').appendChild(img);
            };
            img.src = 'http://xxxxx.yyyy.zzz/index.php/Image_responder/get_image';
        }
    </script>

  </head>
  <body>
      <input type="button" value="request" id="request" onclick="getImage();">
      <div id="results"></div>
  </body>
</html>

実行結果イメージ

image.png

image.png

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

Slackでの勤怠入力でOutlookの予定表にイベントを追加するメモ

できたことダイジェスト

image.png
image.png

ここまでのお話

前回といってもかなり前の話ですが、Slackで勤怠管理して可視化するということをやって、会社のグループ単位でYellowfin(BIツール)のダッシュボードを確認して勤怠を確認できるようにしました。(もし下記にあるコードを参照するならこの記事も確認してもらえると読みやすいと思います。)
その時スケジュール管理ツールにaipoを使っていたので、休日や半休等の場合、aipoのDBに直接アクセスして予定をinsert,update,deleteしていたのですが、オープンソースでの提供が終わりセキュリティ的にも懸念があることからOutlookへ移行しました。
一覧でグループの人の予定が見れないとか色々ありましたが、先述したSlack投稿での勤怠管理での休み登録で予定表に追加されないため手動でOutlookの予定表に休みを登録しなければならずめんどい、という声が多数でどうにかならないかなと感じでいたところでした。
そこでOutlookにもなにかAPIがあるはずで予定の登録くらいはできるだろうと調べ始めましたが、ドキュメントが見分けられずかなり大変でした。
実際には予定の登録ができたのですが、未検証の部分も多いので個人用のメモとして残しておきます。

Outlook予定表にイベントを登録するまでの課題(めんどかったこと)

大体一覧にすると、こんな感じです。
・Microsoftの関連ページがありすぎてどれを参考にしていいかわからない。
・認証方式やAPIの種類が豊富すぎて何を使えばいいかわからない。
・公式のページが多すぎてQiitaの記事や個人のBlogなどの情報が見つかりづらい
と、やる前から萎える感じの状態なのですが、AccessToken取得してPOSTするだけ(だと思う)なのになんでこんな分かりづらいんだと、たくさんの公式ドキュメントで関係ありそうなところを見まくりました。

必要だったこと

前置きが長くなりましたが、ここからAPIを通じてイベントを予定表に追加するための準備です。

Azure Active Directoryでアプリを登録する(Microsoft Azure Portalにログイン後) 。

ここでアプリの登録→新規登録を選択します。
image.png
名前をつけて登録ボタンを押下します。ここではリダイレクトURIはいらないと思います。
image.png

アプリの登録画面に戻ると追加したアプリケーションが表示されているので選択する。

ここで重要なのはアプリケーション(クライアントID)とディレクトリ(テナント)です。後に使うのでコピーしておきます。
image.png

証明書とシークレットを押下してクライアントシークレットを作成する。

新しいクライアントシークレットを押下して説明を入力し、有効期限を選択して追加を行う。
この時に生成されるクライアントシークレットの値も後に使うのでコピーしておきます。
image.png

APIのアクセス許可を押下して今回必要なカレンダー(予定表)へのアクセスができるようにする。

ここでアクセス許可の追加を押下してCalendars.ReadWriteを追加する。この時Azure,Office365の管理者ではない場合、管理者にこのアクセス許可の内容について同意が必要になるので適宜申請なりして許可をとってください。
※他にもCalendars.ReadやMail.Send、User.Readを追加していますが今回はおそらくいりません。
image.png
アクセス許可の追加押下後、Microsoft Graphを選択する。その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
image.png
その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
ここで必要な権限Calendars.ReadWriteにチェックを入れアクセス許可の追加をします。
image.png

認証を押下してトークンの種類を選択する。

アクセストークン、IDトークンにチェックを入れます。また、パブリッククライアント フローを許可するではいを選択します。
※実際はアクセストークンのチェックだけでよいかもしれません。。またパブリッククライアントフローの許可も厳密に検証していません。すみません。
image.png

ここまできたらアプリ自体の登録は完了です!

実際のコーディング

ここまでの準備でAccessTokenの取得や、イベントの追加をAPI経由でできるようになっているので、そこにアクセスするためのコーディングを行います。
逐次説明が大変なので、コメントでフォローしました。クライアントID、クライアントシークレット、テナントとDB情報は置き換えてください。
途中でDBに接続してSlackの名前から、メールアドレス(ログインID)とOffice365の表示名を取得しています。
イベント追加時のURLですが、公式ドキュメントでは「POST /users/{id | userPrincipalName}/events」となっており、userPrincipalNameというのがログインID(メールアドレス)であるため、それをイベントを追加する人ごとに分ける感じです。

addEvent.php
/**
 * Outlookへの休日登録
 * @param  string  $text        確認する文字列
 * @param  string  $user        Slackユーザー名
 * @param  date $date      対象日付(Y-m-d)
 * @param  time $hhdstart  対象日時(H:i:00)
 * @param  time $hhdend    対象日時(H:i:00)
 *
 * @return boolean
 */

function toOutlook($text,$user,$date,$hhdstart,$hhdend){
    global $wdsn,$wdbuser,$wpass;//PDOを使ったDBアクセスをしているので設定を適宜置き換えてください
    $CURLERR = NULL;

    $data = array(
        'client_id' => 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX',//クライアントID
        'scope' => 'https://graph.microsoft.com/.default',
        'client_secret' => 'XXXXXXXXXXXXXXXXXXXXXXXXX',//クライアントシークレット
        'grant_type' => 'client_credentials',
    );

    $url = 'https://login.microsoftonline.com/XXXXXXXXXXXXXXXXXXXXXXX/oauth2/v2.0/token';//XXXをテナントで置換する

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));    //データをセット
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    $html = curl_exec($ch);

    if(curl_errno($ch)){        //curlでエラー発生
        $CURLERR .= 'curl_errno:' . curl_errno($ch) . "\n";
        $CURLERR .= 'curl_error:' . curl_error($ch) . "\n";
        $CURLERR .= '▼curl_getinfo' . "\n";
        foreach(curl_getinfo($ch) as $key => $val){
            $CURLERR .= '■' . $key . ':' . $val . "\n";
        }
        echo nl2br($CURLERR);
    }
    curl_close($ch);

    $res = json_decode($html,true);//レスポンスJsonにアクセスできるようにする。$res['access_token']がアクセストークン

    $name = $user;
    $loginId = '';
    $fullname = '';

    try {
        $upsert_pdo = new PDO(
            $wdsn,
            $wdbuser,
            $wpass,
            array(
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            )
        );

        $sql = 'SELECT mailaddress,ename FROM ユーザーテーブル名 WHERE nname = \''.$name.'\'';//Slackの登録名からmailaddressとOffice365の登録名を取得する
        $result = $upsert_pdo->query($sql);
        foreach ($result as $row) {

            $loginId = $row['mailaddress'];
            $fullname = $row['ename'];

            $CURLERR = NULL;

            $data = array(
                'subject' => "${text}",
                'body' => array('contentType' => 'HTML','content' => "${text}です。"),
                'start' => array('dateTime' => "${date}T${hhdstart}",'timeZone' => 'Asia/Tokyo'),
                'end' => array('dateTime' => "${date}T${hhdend}",'timeZone' => 'Asia/Tokyo'),
                'attendees' => array(array('emailAddress' => array('address' => $loginId,'name' => $fullname)),array('type' => 'required')),
            );
            $data = json_encode($data,JSON_UNESCAPED_UNICODE);
            $url = 'https://graph.microsoft.com/v1.0/users/'.$loginId.'/events';//登録するユーザーのメールアドレスがURLに必要になります。

            $ch = curl_init($url);

            curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);    //データをセット
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Authorization: Bearer '.$res['access_token']));
            $html = curl_exec($ch);//ここでAPIを通じてイベントが登録されます。

            if(curl_errno($ch)){        //curlでエラー発生
                $CURLERR .= 'curl_errno:' . curl_errno($ch) . "\n";
                $CURLERR .= 'curl_error:' . curl_error($ch) . "\n";
                $CURLERR .= '▼curl_getinfo' . "\n";
                foreach(curl_getinfo($ch) as $key => $val){
                    $CURLERR .= '■' . $key . ':' . $val . "\n";
                }
                error_log(nl2br($CURLERR),0);
            }
            curl_close($ch);

        }
    } catch (PDOException $e) {
        // 外側のTryブロックに対してスロー
        throw $e;
        return FALSE;
    }

}

感想

最初はとてつもなく大きなものに立ち向かっていく感があったのですが、こうして書いてみると、わかっていればすぐなんでしょうね。。。もうちょっとドキュメントの読解力を磨きます。

参考にしたサイト様

Outlook カレンダー API の概要
イベントを作成する
dateTimeTimeZone リソースの種類
event リソースの種類
ユーザーなしでアクセスを取得
Microsoft ID プラットフォーム エンドポイントでのアクセス許可と同意
Microsoft ID プラットフォームのアプリケーションの種類
Microsoft Outlook API で遊ぶ
PostMan を使った Graph API の始め方
AzureでAccessTokenを取得する方法

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

Slackでの勤怠入力でAPI経由でOutlookの予定表にイベントを追加するメモ

できたことダイジェスト

image.png
image.png

ここまでのお話

前回といってもかなり前の話ですが、Slackで勤怠管理して可視化するということをやって、会社のグループ単位でYellowfin(BIツール)のダッシュボードを確認して勤怠を確認できるようにしました。(もし下記にあるコードを参照するならこの記事も確認してもらえると読みやすいと思います。)
その時スケジュール管理ツールにaipoを使っていたので、休日や半休等の場合、aipoのDBに直接アクセスして予定をinsert,update,deleteしていたのですが、オープンソースでの提供が終わりセキュリティ的にも懸念があることからOutlookへ移行しました。
一覧でグループの人の予定が見れないとか色々ありましたが、先述したSlack投稿での勤怠管理での休み登録で予定表に追加されないため手動でOutlookの予定表に休みを登録しなければならずめんどい、という声が多数でどうにかならないかなと感じでいたところでした。
そこでOutlookにもなにかAPIがあるはずで予定の登録くらいはできるだろうと調べ始めましたが、ドキュメントが見分けられずかなり大変でした。
実際には予定の登録ができたのですが、未検証の部分も多いので個人用のメモとして残しておきます。

Outlook予定表にイベントを登録するまでの課題(めんどかったこと)

大体一覧にすると、こんな感じです。
・Microsoftの関連ページがありすぎてどれを参考にしていいかわからない。
・認証方式やAPIの種類が豊富すぎて何を使えばいいかわからない。
・公式のページが多すぎてQiitaの記事や個人のBlogなどの情報が見つかりづらい
と、やる前から萎える感じの状態なのですが、AccessToken取得してPOSTするだけ(だと思う)なのになんでこんな分かりづらいんだと、たくさんの公式ドキュメントで関係ありそうなところを見まくりました。

必要だったこと

前置きが長くなりましたが、ここからAPIを通じてイベントを予定表に追加するための準備です。

Azure Active Directoryでアプリを登録する(Microsoft Azure Portalにログイン後) 。

ここでアプリの登録→新規登録を選択します。
image.png
名前をつけて登録ボタンを押下します。ここではリダイレクトURIはいらないと思います。
image.png

アプリの登録画面に戻ると追加したアプリケーションが表示されているので選択する。

ここで重要なのはアプリケーション(クライアントID)とディレクトリ(テナント)です。後に使うのでコピーしておきます。
image.png

証明書とシークレットを押下してクライアントシークレットを作成する。

新しいクライアントシークレットを押下して説明を入力し、有効期限を選択して追加を行う。
この時に生成されるクライアントシークレットの値も後に使うのでコピーしておきます。
image.png

APIのアクセス許可を押下して今回必要なカレンダー(予定表)へのアクセスができるようにする。

ここでアクセス許可の追加を押下してCalendars.ReadWriteを追加する。この時Azure,Office365の管理者ではない場合、管理者にこのアクセス許可の内容について同意が必要になるので適宜申請なりして許可をとってください。
※他にもCalendars.ReadやMail.Send、User.Readを追加していますが今回はおそらくいりません。
image.png
アクセス許可の追加押下後、Microsoft Graphを選択する。その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
image.png
その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
ここで必要な権限Calendars.ReadWriteにチェックを入れアクセス許可の追加をします。
image.png

認証を押下してトークンの種類を選択する。

アクセストークン、IDトークンにチェックを入れます。また、パブリッククライアント フローを許可するではいを選択します。
※実際はアクセストークンのチェックだけでよいかもしれません。。またパブリッククライアントフローの許可も厳密に検証していません。すみません。
image.png

ここまできたらアプリ自体の登録は完了です!

実際のコーディング

ここまでの準備でAccessTokenの取得や、イベントの追加をAPI経由でできるようになっているので、そこにアクセスするためのコーディングを行います。
逐次説明が大変なので、コメントでフォローしました。クライアントID、クライアントシークレット、テナントとDB情報は置き換えてください。
途中でDBに接続してSlackの名前から、メールアドレス(ログインID)とOffice365の表示名を取得しています。
イベント追加時のURLですが、公式ドキュメントでは「POST /users/{id | userPrincipalName}/events」となっており、userPrincipalNameというのがログインID(メールアドレス)であるため、それをイベントを追加する人ごとに分ける感じです。

addEvent.php
/**
 * Outlookへの休日登録
 * @param  string  $text        確認する文字列
 * @param  string  $user        Slackユーザー名
 * @param  date $date      対象日付(Y-m-d)
 * @param  time $hhdstart  対象日時(H:i:00)
 * @param  time $hhdend    対象日時(H:i:00)
 *
 * @return boolean
 */

function toOutlook($text,$user,$date,$hhdstart,$hhdend){
    global $wdsn,$wdbuser,$wpass;//PDOを使ったDBアクセスをしているので設定を適宜置き換えてください
    $CURLERR = NULL;

    $data = array(
        'client_id' => 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX',//クライアントID
        'scope' => 'https://graph.microsoft.com/.default',
        'client_secret' => 'XXXXXXXXXXXXXXXXXXXXXXXXX',//クライアントシークレット
        'grant_type' => 'client_credentials',
    );

    $url = 'https://login.microsoftonline.com/XXXXXXXXXXXXXXXXXXXXXXX/oauth2/v2.0/token';//XXXをテナントで置換する

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));    //データをセット
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    $html = curl_exec($ch);

    if(curl_errno($ch)){        //curlでエラー発生
        $CURLERR .= 'curl_errno:' . curl_errno($ch) . "\n";
        $CURLERR .= 'curl_error:' . curl_error($ch) . "\n";
        $CURLERR .= '▼curl_getinfo' . "\n";
        foreach(curl_getinfo($ch) as $key => $val){
            $CURLERR .= '■' . $key . ':' . $val . "\n";
        }
        echo nl2br($CURLERR);
    }
    curl_close($ch);

    $res = json_decode($html,true);//レスポンスJsonにアクセスできるようにする。$res['access_token']がアクセストークン

    $name = $user;
    $loginId = '';
    $fullname = '';

    try {
        $upsert_pdo = new PDO(
            $wdsn,
            $wdbuser,
            $wpass,
            array(
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            )
        );

        $sql = 'SELECT mailaddress,ename FROM ユーザーテーブル名 WHERE nname = \''.$name.'\'';//Slackの登録名からmailaddressとOffice365の登録名を取得する
        $result = $upsert_pdo->query($sql);
        foreach ($result as $row) {

            $loginId = $row['mailaddress'];
            $fullname = $row['ename'];

            $CURLERR = NULL;

            $data = array(
                'subject' => "${text}",
                'body' => array('contentType' => 'HTML','content' => "${text}です。"),
                'start' => array('dateTime' => "${date}T${hhdstart}",'timeZone' => 'Asia/Tokyo'),
                'end' => array('dateTime' => "${date}T${hhdend}",'timeZone' => 'Asia/Tokyo'),
                'attendees' => array(array('emailAddress' => array('address' => $loginId,'name' => $fullname)),array('type' => 'required')),
            );
            $data = json_encode($data,JSON_UNESCAPED_UNICODE);
            $url = 'https://graph.microsoft.com/v1.0/users/'.$loginId.'/events';//登録するユーザーのメールアドレスがURLに必要になります。

            $ch = curl_init($url);

            curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);    //データをセット
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Authorization: Bearer '.$res['access_token']));
            $html = curl_exec($ch);//ここでAPIを通じてイベントが登録されます。

            if(curl_errno($ch)){        //curlでエラー発生
                $CURLERR .= 'curl_errno:' . curl_errno($ch) . "\n";
                $CURLERR .= 'curl_error:' . curl_error($ch) . "\n";
                $CURLERR .= '▼curl_getinfo' . "\n";
                foreach(curl_getinfo($ch) as $key => $val){
                    $CURLERR .= '■' . $key . ':' . $val . "\n";
                }
                error_log(nl2br($CURLERR),0);
            }
            curl_close($ch);

        }
    } catch (PDOException $e) {
        // 外側のTryブロックに対してスロー
        throw $e;
        return FALSE;
    }

}

感想

最初はとてつもなく大きなものに立ち向かっていく感があったのですが、こうして書いてみると、わかっていればすぐなんでしょうね。。。もうちょっとドキュメントの読解力を磨きます。

参考にしたサイト様

Outlook カレンダー API の概要
イベントを作成する
dateTimeTimeZone リソースの種類
event リソースの種類
ユーザーなしでアクセスを取得
Microsoft ID プラットフォーム エンドポイントでのアクセス許可と同意
Microsoft ID プラットフォームのアプリケーションの種類
Microsoft Outlook API で遊ぶ
PostMan を使った Graph API の始め方
AzureでAccessTokenを取得する方法

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

Wordpress REST APIで画像投稿する場合はMIME typeを指定するようになってる

post_image.php
function post_image($image){
    $WP_USERNAME = 'user';
    $WP_PASSWORD = 'pw';
    $url = 'http://example.com/wp-json/wp/v2/media';
    $file = $image;

    $data = wp_remote_get($file);

    $image_response = wp_remote_post( $url, array(
    'headers' => array(
        'Authorization' => 'Basic ' . base64_encode($WP_USERNAME.':'.$WP_PASSWORD),
        'Content-Disposition' => 'attachment;filename=' . basename($file),
        'Content-Type' => 'image/jpeg' 
        //ここを指定しないとendpointが403を返す。Wordpress5.4.4まではなくてもいけた
        //mime_content_type()はネットワーク越しだと動かなかったのでとりあえず手打ち
    ),
    'body' => $data["body"]
    ));

    $data = json_decode($image_response['body']);
    $media_id=$data->id;
    return $media_id;   
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Dockerコンテナ内でのユーザー認証導入

初めまして!
スクールではRuby on Railsを学習。
就活の関係でPHPの学習始めたばかりです。
初めて1からLaravelで作成するので記録を残します。

DockerでLaravelの開発環境を作成

これに関してはこちらの記事を参考にというかもうそのまま使わせて頂きました!
大変わかりやすかったです。
ありがとうございました!

ユーザー認証導入

ユーザー登録やログイン機能の実装です。
Railsだとdeviseで簡単にできるイメージでしたが結構つまりました。
Dockerというのが理解が追いついていないので難しかったです。

目標物
Laravelの標準のユーザー認証機能の導入
スクリーンショット 2021-01-25 13.56.50.png

まずコンテナ内に入ります

ターミナル
% docker-compose exec app bash

コンテナ内でユーザー認証導入のコマンドを入力します

コンテナ
composer require laravel/ui

ここで下記のエラーが発生しました。

Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes)

調べると「メモリ不足なのでインストールできないよ」とのこと。
なので

コンテナ
COMPOSER_MEMORY_LIMIT=-1 composer require laravel/ui

上記のコマンドは一時的に容量を無制限にするコマンドのようです。
こちらの記事を参考にさせていただきました!ありがとうございます。

vue.jsを元にしたテンプレートの認証機能を追加します

コンテナ
php artisan ui vue --auth

下記のように出れば成功です。

Vue scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
Authentication scaffolding generated successfully.

これでvue.jsを書く初期状態が完成。
app/Http/Controllers/Authにはバックエンドでの認証機能関係のファイルが追加されます。
resources/views/authにはフロントエンド関係のviewファイルが追加されます。

ここまでで一旦は認証機能の導入が終わりましたが、まだ表示が崩れたままです。

画面を整えるためNode.jsのパッケージ管理ツールをインストールします。

コンテナ
npm install && npm run dev

そうすると

npm: command not found

とエラー。
Node.jsのインストールしてなかったからみたいです(それはそうですね)
公式からインストールし再実行するも変わらず。
インストールの確認のためコンテナから出て
which nodeと入力すると/usr/local/bin/nodeと出力され
npm -vと入力すると6.14.10と出力されるがコンテナ内で行うと出てこないのでコンテナ内にNodeが存在しないことが原因と分かりました。

コンテナ内に反映させるため,php/DockerfileのWORKDIRの前に以下を追加します。

COPY --from=node:12.20 /usr/local/bin /usr/local/bin
COPY --from=node:12.20 /usr/local/lib /usr/local/lib

コンテナから抜けてビルドし直します。

docker-compose up -d --build

このあと再度コンテナ内に入りnpm install && npm run devで無事完了。
public/jspublic/cssに新しいファイルが生成されます。

リアルタイムでコンパイルさせる

phpのコンテナ内で

npm run watch-poll

を実行することでコードを書きながらでもリアルタイムでjsがコンパイルされ画面が反映されます。

完成

以上です!
ご指摘などございましたらよろしくお願い致します!

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

【PHP初心者】Laravelを2週間触ってみて理解したこと③【blade】【継承】

こんにちは、nakaです?
最近在宅が増えて早起きができなくなってしまいました・・・
通勤がないとぎりぎりまで寝れてしまう&お布団があったかい誘惑に負けてしまいそうになります。こういう場合私は朝に楽しみをもってきて早く起きる作戦を実行します。今日の夜はホームベーカーでも仕込もうかな。

さて前回はLaravelのルーティングについてお話しました。
前回の記事:【PHP初心者】Laravelを2週間触ってみて理解したこと②【ルーティング】

今回はLaravelのテンプレートエンジンである、bladeについてまとめようと思います!

bladeとは

初回の記事でも触れましたが、bladeはLaravelのビューの部分です。
・ビューに直接PHPが記述できる
・ファイル名は「~.blade.php」という拡張子
という特徴があります。

まず環境を確認

まずは環境を確認しましょう。
今使っているLaravelのバージョンを確認してください。
(バージョンでbladeの書き方が違ってくる可能性があるので確認が必要)

Laravelのバージョン確認コマンド

php artisan -v

今回はLaravel5.5の環境で動かしてみます。
もし違うバージョンの場合は動作確認してくださいね。

さてここからは公式ドキュメントにそってbladeについてまとめます。

テンプレートの継承

まずbladeのテンプレートの継承について。
ビューに親子関係があり、親のレイアウトを子が継承するかたちになります。
親を「resources/views/layouts/app.blade.php」
子を「resources/views/child.blade.php」とします。
※共通レイアウトを親にして、各ページ独自にもつものを子に書きます。

その前に、ディレクティブについて。ディレクティブとはblade内で使える命令のことで、@if @foreachなど様々なものがあります。
今回テンテンプレートの継承で使うでは以下の3つです。

@extends
@section
@yield

実際に親子のビューを見ていきましょう。
➀まず親のビューを作成し、子に継承する
➁子独自のパーツを作成
➂親側で呼び出す
親ビューをベースにして独自の子ビューを組み込みページを表示することができます!

child.blade.php
// ➀親を継承
@extends('layouts.app') 

// ➁子固有の箇所を@section ~ @endsectionで囲み、名前をつける(今回はcontent)
@section('content')
    <p>ここが本文のコンテンツ</p>
@endsection
app.blade.php
<html>
    <head>
        <title>サンプル</title>
    </head>
    <body>
        <div class="container">
            // ➂ 子で指定した「content」を呼び出す
            @yield('content')
        </div>
    </body>
</html>
結果
<html>
    <head>
        <title>サンプル</title>
    </head>
    <body>
        <div class="container">
            // 結果
            <p>ここが本文のコンテンツ</p>
        </div>
    </body>
</html>

ちょっと短いですが区切りが悪いので次回にします。
blade続けます、次回はbladeの「コンポーネントとスロット」

参考記事

Laravel 5.5 Bladeテンプレート
【Laravel入門】ビューとBladeと継承
【Laravel】Viewのbladeの書き方

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

【Laravel】バリデーションのrequiredとビューの必須マークを連動させたい

概要

こういう必須マーク、よく付け忘れや消し忘れが目立つ。
test.png
なのでバリデーションルールのrequiredに沿ってviewに表示されるような仕組みを考えてみました。

考え方

  • コントローラーでバリデーション配列を呼び出し、ビューにセットする
  • ビューの入力フォームで入力フォームの名前をセットし、入力フォームの該当のバリデーション配列内にrequiredがあれば必須マークを付ける

実装例

  • コントローラー
  • バリデーション
  • ビュー(入力フォーム部分)
  • ビュー(必須マーク部分)

を記載します。
やりたいことが何となく伝わればよいかなということで、バリデーションルールとかビューの入力項目1個1個の中身は適当です。

コントローラー

サンプルコード
UserController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\UserCreateRequest;

class UserController extends Controller
{
    public function create()
    {
        $validation = new UserCreateRequest();
        return view('user.create', [
            'rules' => $validation->rules(),
        ]);
    }
}

バリデーション

サンプルコード
UserCreateRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserCreateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:40',
            'name_kana' => 'required|max:40|regex:/^[ァ-ヶー]+$/u',
            'post_code' => 'nullable|numeric|digits:7',
            'pref_code' => 'nullable',
            'address1' => 'nullable|max:80',
            'address2' => 'nullable|max:80',
            'email' => 'required|email|confirmed|max:255',
            'login_id' => 'required|min:8|max:32|unique:users,login_id',
            'password' => 'required|confirmed|min:8|max:32',
        ];
    }
}

ビュー(入力フォーム部分)

サンプルコード
create.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>ユーザー登録</title>
    <meta name="robots" content="noindex">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.1/css/bootstrap.min.css"
          integrity="sha384-VCmXjywReHh4PwowAiWNagnWcLhlEJLA5buUprzK8rxFgeH0kww/aWY76TfkUoSX" crossorigin="anonymous">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
            integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
            integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
            crossorigin="anonymous"></script>
</head>
<body class="bg-light environment_image">
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
    <a class="navbar-brand" href="/">ユーザー管理画面</a>
</nav>
<div class="container mb-5">
    <div class="card">
        <h5 class="card-header">ユーザー登録</h5>
        <div class="card-body">
            <div class="mb-3 collapse show">
                @if (!empty(session('messages')))
                    <div class="alert alert-success" role="alert">
                        @foreach (session('messages') as $message)
                            {{ $message }}<br>
                        @endforeach
                    </div>
                @endif
                <form action="" method="POST" id="input_form">
                    @csrf

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="last_name">名前
                                @include('common.required',['rules' => $rules['name'] ?? ''])
                            </label>
                            <input type="text" name="name" id="name" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="name_kana">名前(フリガナ)
                                @include('common.required',['rules' => $rules['name_kana'] ?? ''])
                            </label>
                            <input type="text" name="name_kana" id="name_kana" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="post_code">郵便番号
                                @include('common.required',['rules' => $rules['post_code'] ?? ''])
                            </label>
                            <input type="text" name="post_code" id="post_code"
                                   class="form-control"
                                   placeholder="9999999"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="pref_code">都道府県
                                @include('common.required',['rules' => $rules['pref_code'] ?? ''])
                            </label>
                            <select id="pref_code" class="form-control" name="pref_code">
                                <option value="">選択してください</option>
                                <option value="13">東京都</option>
                            </select>
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address1">市区町村
                                @include('common.required',['rules' => $rules['address1'] ?? ''])
                            </label>
                            <input type="text" name="address1" id="address1"
                                   class="form-control" placeholder="〇〇市××町"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address2">番地、マンション名、部屋番号
                                @include('common.required',['rules' => $rules['address2'] ?? ''])
                            </label>
                            <input type="text" name="address2" id="address2"
                                   class="form-control" placeholder="1-2-3"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email">メールアドレス
                                @include('common.required',['rules' => $rules['email'] ?? ''])
                            </label>
                            <input type="text" name="email" id="email"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email_confirmation">メールアドレス(確認)
                                @include('common.required',['rules' => $rules['email_confirmation'] ?? ''])
                            </label>
                            <input type="text" name="email_confirmation" id="email_confirmation"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="login_id">ログインID
                                @include('common.required',['rules' => $rules['login_id'] ?? ''])
                            </label>
                            <input type="text" name="login_id" id="login_id"
                                   class="form-control"
                                   placeholder="英数字で入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password">パスワード
                                @include('common.required',['rules' => $rules['password'] ?? ''])
                            </label>
                            <input type="password" name="password" id="password"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password_confirmation">パスワード(確認)
                                @include('common.required',['rules' => $rules['password_confirmation'] ?? ''])
                            </label>
                            <input type="password" name="password_confirmation" id="password_confirmation"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <button type="button" class="btn btn-primary m-2 disabled_button">登録</button>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

ビュー(必須マーク部分)

サンプルコード
required.blade.php
@php
    // バリデーションルールが配列の形では無い場合、配列にする
    if(isset($rules) && is_array($rules) === false) {
        $rules = explode('|', $rules);
    }

    // 必須表示フラグを初期化
    $is_required = false;

    // バリデーションルール配列の中にrequiredがあれば必須マークを使用する
    if(isset($rules) && is_array($rules) && in_array('required', $rules, true)) {
        $is_required = true;
    }
@endphp

@if ($is_required)
    <span class="badge badge-danger">必須</span>
@endif

あとがき

includeが多くなったりin_arrayを使用してたりするので、フロント側の表示など速度を求められる部分には不向きかなと思います。
入力項目が多くなりがちな管理画面とかには良いかもです。

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

GAEで原始的なPHP7のプロジェクトを動かすのに苦労した話

動機

Google App EngineでPHP7のサンプル的なプロジェクトを動かそうとしてえらく苦労したのでその顛末をここに記す。
構成としてはルートに複数のPHPファイルと背景画像、その下のフォルダにPHPファイルやhtmlファイル、画像などがあるようなものだ。HerokuやAWS ElasticBeanstalk、Azureではローカルで動いていたPHPのプロジェクトをそのままデプロイすればそのまま動いた、実に簡単だった。しかしGoogle App Engineではそうはいかなかった。

app.yaml

まず、アプリの設定情報を記述するapp.yamlを書かないといけない。どのバージョンを使うのかとか、アクセスされたパス、拡張子に応じてどのような動作するのかとか。いちいち言わんでもわかるやろと思ってしまうが。
で、app.yaml、phpという単語でググるとこのPHP5用のページがトップに表示されるのだが、PHP5とPHP7とでは動作が異なるので、PHP7のときはこれは使えない。上の方に「PHP7の使用を強くお勧めする」と書いてあるが、字が小さいので気が付かなかった。
PHP7のときはどんなふうに書けばいいのかというとこちら
違いは拡張子phpの場合で、PHP5のときはphpのファイルをスクリプトとして処理せよという形になっているが、PHP7のときは拡張子の条件は消え、全てのファイル(.*)に対し、autoになっている。

php5
# Serve php scripts.
- url: /(.+\.php)$
  script: \1
php7
# Serve your app through a front controller at index.php or public/index.php.
- url: .*
  script: auto

コメントには「index.phpのフロントコントローラを介してappを提供しろ」と書いてある。
はてどういうことだろうと思いながら、とりあえず動かそうとすると、
ルートに置いたindex.phpのスクリプトは正常に動作するが、同じ階層にある別のphpファイルや下の階層のphpファイルにアクセスしようとしても、index.phpにリダイレクトされる。なんでや。

フロントコントローラ

いろいろ調べてみるとどうやらindex.phpを入り口(フロント)として、そこから他のファイルへのアクセスを振り分けてやる(コントロールする)のがお作法らしいということがわかる。元々あったindex.phpはそのままにしておきたいので、ここではcontroller.phpという名前で新たにファイルを作り、そこを入り口にしてやる。入り口の名前はapp.yamlにentrypointとして書けば良い。

app.yaml
entrypoint: serve controller.php

そしてcontroller.phpはどんなふうに書いたらいいのか。いろいろさまよってここにたどり着いた。
なんだここに大事なことが大体書いてあるじゃないか。Googleのマニュアルは広大すぎてなかなか必要なところにたどり着けない。
そしてサンプルコードがGitHubに上がっている。
https://github.com/GoogleCloudPlatform/php-docs-samples/tree/master/appengine/php72/front-controller

ここでは

  • Slim Frameworkを使用する場合
  • WordPressを使用する場合
  • 正規表現でやる場合

の3パターンを上げてくれてある。迷わず正規表現の例を見る。
正規表現は何回やっても覚えられないので、調べながら格闘、このサンプルコードがやっているのは
spanner.php、monitoring.php、speech.phpにアクセスしてきた時はそのファイルを応答として返すということのようだと理解する。

controller.php
// Static list provides security against URL injection by default.
$routes = [
    'spanner',
    'monitoring',
    'speech',
];
// Keeping things fast for a small number of routes.
$regex = '/\/(' . join($routes, '|') . ')\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}

とりあえず拡張子phpのファイルは全部同じように処理して欲しいので、
正規表現の式を以下のように書き換えた。
先頭の「/\/」は意味がわからなかったので/にした。

controller.php
$regex = '/.+\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}

app.yaml再び

これでとりあえずphpのアプリはみんな動いた。だが普通のhtmlファイルが表示されない。
拡張子がhtmlのときの動作がapp.yamlで定義がされていないからである。
再びapp.yamlを修正。
Googleのマニュアルで例として書かれていたものは、stylesheet以下のファイルと拡張子gif
、png、jpgの場合は静的リソースとしてそのまま配信しろ、それ以外はscript: autoという内容なので、htmlはscript: autoで処理されてしまうようである。何故だ。

app.yaml
handlers:
# Serve a directory as a static resource.
- url: /stylesheets
  static_dir: stylesheets

# Serve images as static resources.
- url: /(.+\.(gif|png|jpg))$
  static_files: \1
  upload: .+\.(gif|png|jpg)$

# Serve your app through a front controller at index.php or public/index.php.
- url: .*
  script: auto

やりたいのは拡張子がphpの時だけスクリプトとして処理して、それ以外は全部そのまま配信して欲しいので、以下のようにした。上に書いてあるものが優先的に適用されるようである。

app.yaml
handlers:
- url: /.+\.php
  script: auto

- url: /(.+)$
  static_files: \1
  upload: .+$

.*(任意の0文字以上)ではなく.+(任意の1文字以上)にしたのはルートにアクセスされた際にindex.phpに飛んで欲しいから。.*だとルートにアクセスされた際もそのまま静的ファイルで応答しようとしてエラーになった。

結論

controller.phpの方にもルートにアクセスされた場合にindex.phpに飛ばす処理を追加し、最終的にapp.yaml、controller.phpは以下の形になった。これでやっと思い通りの動きをするようになった。
こんな簡単な記述でいいのに、ここまでたどり着くのに3日かかった。Googleのマニュアル難しすぎ。なんでみんなあれで理解できるんだろう。

app.yaml
runtime: php73
entrypoint: serve controller.php

handlers:
- url: /.+\.php
  script: auto

- url: /(.+)$
  static_files: \1
  upload: .+$
controller.php
if($_SERVER['REQUEST_URI'] == '/'){
    require('index.php');
    return;
}

$regex = '/.+\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}

http_response_code(404);
exit('Not Found');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】シーダーの複数あるテストデータをforeachでスマートに書く

はじめに

Laravelでシーディングのテストデータが複数ある場合、コードが冗長になってしまいます。
今回はforeachを使ってスマートにまとめる書き方をお伝えします。

今回の例ではCarbonを使っておりますが、Carbonについて詳しくは以下の記事が参考になります。
PHPで日付時刻処理を書くならCarbonを使うべき - Qiita

対象

LaravelでWebアプリを作っている初学者向けの内容となっております。
本記事はLaravel青本をベースに書いております。
PHPフレームワークLaravel入門 第2版 | 津耶乃, 掌田 |本 | 通販 | Amazon

環境

Laravel:6系
PHP:7.4.13


それではまず通常のシーダーファイルの書き方の一例です。

PostsTableSeeder.php
class PostsTableSeeder extends Seeder
{
    public function run()
    {
        $param = [
            'name' => '竈門 炭治郎',
            'body' => '水の呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('sizes')->insert($param);

        $param = [
            'name' => '我妻 善逸',
            'body' => '雷の呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('sizes')->insert($param);

        $param = [
            'name' => '嘴平 伊之助',
            'body' => 'ケダモノの呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('posts')->insert($param);        
    }
}

created_atupdated_atが同じなのに何度も書いているのはあまりスマートではありません。
foreachを使って以下のように書くことができます。

PostsTableSeeder.php
class PostsTableSeeder extends Seeder
{
    public function run()
    {
        $params = 
        [
            [
                'name' => '竈門 炭治郎',
                'body' => '水の呼吸'
            ],
            [
                'name' => '我妻 善逸',
                'body' => '雷の呼吸'
            ],
            [
                'name' => '嘴平 伊之助',
                'body' => 'ケダモノの呼吸'
            ]
        ];

        $now = Carbon::now();
        foreach ($params as $param) {
            $param['created_at'] = $now;
            $param['updated_at'] = $now;
            DB::table('posts')->insert($param);
        }
    }
}

内容が変わるテストデータ(今回は'name''body')を$paramsの配列に入れます。
そして共通項目('created_at''updated_at')をforeachの中で書きます。

こうすることで無駄な記述を減らすことができ、見事スマートなシーダーファイルの完成です!

いかがでしたでしょうか?
今回はテストデータが少ないのであまり変わりないですが、共通する項目が多いほどすっきりと可読性の高いコードになります。

是非お試しいただければと思います。

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

setup IIS + PHP + Oracle

Outline

今回、windows server上でOracleに接続するWEBアプリを作る機会がありました。
Oracleに接続するにあたり、多少設定などに苦労したことがあるため、自分の構築メモふくめて、手順を残しておく

Environment

Item Version Memo
OS Windows Server 2019 Standard
DB Oracle 12.1.0.2.0
PHP 7.4.14 (32bit)
Instant Client 19.8.0.0.0

Instant Client

Download

ここから、Basicパッケージをdownloadする

https://www.oracle.com/jp/topics/technologies/download-instant-client.html

なお、bit数(x86=32bit)は後述するPHPのbit数と合わせる必要がある

今回、Microsoft Windows 32ビット用 19.8.0.0.0 を使用した
image.png

Setup

zipを解凍後、C:\instantclientに配置した

image.png

PHP setup

Download

https://www.php.net/downloads.php

ここから、環境等に合わせて、phpをdownloadする
今回、stable、performanceを考慮して、VC15 x86 Non Thread Safe (2021-Jan-05 18:10:14)を使用した。
image.png

set file

zipファイルを解凍し、任意のパスに置く
今回は "C:\php"に配置した

image.png

iniの設定

php.ini-development もしくは php.ini-productionを php.iniに変更orコピーする
IIS環境が、開発向けか公開向けかによってどっちのtemplateを使用するか決める。
今回、社内向けツールのため、DEBUGのしやすさを考慮してphp.ini-developmentを用いた

image.png

php.iniを開き、必要個所を編集する

extensionのpathの設定

extension_dir がdefaultコメントアウトされている。
こちらを有効にし、適切なパスを設定する

image.png

oracle系のextension

oci8_12c , pdo_oci が oci_connection等、oracle DBに接続するためのextension
こちらを、有効にする

image.png

Instant Client dll 配置

先ほど設定した、instant clientのC:\instantclient 配下にある、dllを"C:\php"以下にコピーする

image.png

その後、extensionが正しく稼働するかをコマンドで確認する。
oci8 , PDO_OCIがmodule listに表示され、かつエラーが表示されないことを確認する。
ただしくdllが読み込まれないと、エラーが表示される

php.exe -m

image.png

IIS setup

Add Role in Server Manager

Server Managerを起動し、"Add roles and features" を選択する

image.png

Server Rolesにて、Web Server(IIS)を選択する
image.png

Role Servicesにて、CGIを少なくとも追加する。
それ以外に、ASP等必要に応じて追加する
image.png

Configration

IIS Managerを起動する
対象のserverを選択し、"Handler Mapping"を選択する

image.png

Add Module Mappingを選択し、PHPの定義を追加する

image.png

※ Module にてFastCgiModuleが表示されない場合、Server Roleの Role Servicesで CGIを追加していない可能性がある。改めて、Server Roleの設定を見直す必要がある

PHP 動作確認
下記、phpinfo.phpを作成し、IISのroot directoryに配置する (Default C:\inetpub\wwwroot)
そして、アクセスして確認する

<?php
phpinfo();
?>

http://localhost/phpinfo.php

動作確認例
image.png

Oracle 接続サンプルphp

今回、tnsnames.oraを直接phpに記載した例。

oracleの環境によって$ORAの必要項目はことなるため、カスタマイズする必要がある。
今回、SERVER、INSTANCE_NAMEに関して設定が不要のため、コメントアウトしている。

<?php

    $dbhost = "oracle-host";
    $dbport = "1521";
    $dbname = "dbname";

    $user = 'USER-NAME';
    $pass = 'PASSWORD';

    $ORA = "(DESCRIPTION= "
         ." (ADDRESS=(PROTOCOL=tcp)(HOST=$dbhost)(PORT=$dbport)) "
         ."  (CONNECT_DATA= "
         ." (SERVICE_NAME=$dbname) "
//         ." (SERVER=server) "
//         ." (INSTANCE_NAME=instance_name)"
         ." )) "
         ;

    $conn = oci_connect($user, $pass, $ORA );

    if (!$conn) {
        $e = oci_error();
        trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
    }

    $stid = oci_parse($conn, "SELECT * FROM XXX WHERE ID = '12345' ");
    oci_execute($stid);


    echo "<table border='1'>\n";
    while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
        echo "<tr>\n";
        foreach ($row as $item) {
            echo "    <td>" . ($item !== null ? htmlentities($item, ENT_QUOTES) : "&nbsp;") . "</td>\n";
        }
        echo "</tr>\n";
    }
    echo "</table>\n";

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