20200116のPHPに関する記事は16件です。

PHPでURL正規表現

PHPでURL正規表現

プロトコル + ドメイン

$urls = [
  'a' => 'http://localhost:81/test.php?test_param=johi',
  'b' => 'http://localhost:81',
  'c' => 'http://localhost',
  'd' => 'http://localhost:8080',
  'e' => 'http://www.xn--e--testjoidiu.jp/',
  'f' => 'http://egegbfwork.blagebog10f2.fc2.com/',
  'g' => 'http://blogs.yahoo.co.jp/medidgecalg919e',
  'h' => 'http://227ebdw.net/kango/1/index.html',
  'i' => 'http://gebfa.test-search.net',
];
foreach ($urls as $url) {
  $result = preg_match('/(https?:\/\/(www\.)?[0-9a-z\-\.]+:?[0-9]{0,5})/', $url, $matches);
  echo $matches[0];
}

// 結果(普通見ないようなドメインもありますが、、、)
a: http://localhost:81
b: http://localhost:81
c: http://localhost
d: http://localhost:8080
e: http://www.xn--e--testjoidiu.jp
f: http://egegbfwork.blagebog10f2.fc2.com
g: http://blogs.yahoo.co.jp
h: http://227ebdw.net
i: http://gebfa.test-search.net
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでWebアプリを作ったのでまとめました

はじめに

初投稿になります。
プログラミング学習のアウトプット(ポートフォリオ)として、PHPでのWebアプリを制作しました。

制作したWebアプリの概要や機能をユーザーや採用担当者に知ってもらう為、どのような経緯で制作を行ったのか、どのように開発をしたのかをまとめました。

この記事を書いた目的

制作したWebサービスの機能、構成を伝えるため。

ポートフォリオで目指したもの

  • フルスクラッチでのPHPを利用したWebアプリ制作を行う
  • CRUD処理を利用する
  • 主にスマートフォンユーザーが使用することを意識したUI/UX作り(PC・スマホ対応)
  • CSS設計を意識する(BEMを使用)
  • WebAPIの活用をする(Google Map APIを使用)
  • インフラにはAWSを活用する(EC2・RDS)

開発環境

使用言語・データベース・インフラ

  • PHP 7.3.11
  • Javascript
  • CSS
  • MySQL
  • AWS(VPC、EC2、RDS)

使用ツール・ライブラリ

  • jQuery
  • SASS
  • Ajax
  • Github

アプリケーションの概要

アプリケーション名:『Phototavi』
旅行先で撮った写真を投稿して、みんなで共有するWebアプリケーションです。
top.PNG

このアプリを制作しようとした経緯
  • 一度撮った写真を見返す事がほとんどないので有効活用したい。
  • どの都道府県に訪れたのかを整理したい。
  • ユーザーがどんな場所に行ってどのような写真を撮っているのか知りたい。
  • 私の推測ですが、大抵の人はスマートフォンに景色などの写真が少なからずあるのではないか。(多くの人に使ってもらえるものを制作したい。)

以上の理由からアプリ制作に至りました。

主な機能

◆ユーザー関連

  • ユーザー登録
  • ログイン・ログアウト
  • ゲストユーザーでの観覧(登録・パスワードなし)
  • プロフィール編集
  • マイページ
  • ユーザーページ

◆データ関連

  • 旅データの詳細ページ閲覧
  • 詳細ページの作成・編集・削除
  • 位置情報表示(Google MAP API/Geocoding API)
  • 掲示板による詳細ページの書き込み
  • お気に入り登録
  • 検索機能

デモ

スマートフォン用詳細画面
mainsp1.PNG

ログイン画面→メイン画面(PC)
output-palette.gif

投稿画面(PC)
output-palette.gif

検索機能とユーザーページ(SP)
output.gif

制作手順

準備

1.必要な機能の洗い出し

まず、実装したい機能の整理をしました。また、なぜその機能が必要なのか簡単に表にまとめました。
youken.PNG

2.全体図の構想

それぞれのページを大雑把に把握する為、手書きで関係図を書き内容を整理しました。
image1.JPG

3.テーブル設計

テーブルは、usersテーブルを元に最初に必要となりそうなものを大雑把に決めました。
開発途中で随時追加をして行った部分が多く、最終的には以下の様になりました。
tablestracutuer.PNG

機能実装

機能実装では、各ページ毎にHTMLで骨格を作り、PHP内部設計を行った後に、CSSを追加していく形で行いました。

HTMLでの骨格づくり

手書きの構想を具体化するため、HTMLで骨格を作りました。

PHPでの内部設計

今回の開発ではフレームワークを使用しませんでした。
使用していない理由は、

  • フルスクラッチでしっかりとしたものを作った経験が無かった
  • 現在もlaravelを勉強中の為、実装したいものができる自信が無かった (フレームワークを使ったものはFuelPHPを使用した掲示板作成ぐらい)

実際の開発現場では、フレームワークを使用するのが通常であるという認識なので、今後も課題であると考えています。

CSS設計

CSSでは、PCとスマートフォンでの観覧を想定して、SASSを使用しました。
また、CSS設計としてBEMを意識した命名をしていますが、中途半端なところがあり次回以降の課題と考えています。
メディアクエリを使用して、横幅に合わせてレイアウトを調整しました。
BootStrap等のフレームワークを使用していない為、タブレットサイズではレイアウトが崩れてしまう部分があるので修正していかないといけない部分です。

インフラ設計

AWSを使用したインフラ構築を行いました。
PHPをEC2、DBをRDSに設置したシンプルな構成です。
AWS.jpg

テスト

実装が完了した段階で、簡単なテストを行いました。

  • 実際に投稿する文や写真を想定したものをインプットして、動きをチェックした。
  • 身近な人に実際に使ってもらい、思わぬバグが発生しないか・使ってみた感想を聞いたりフィードバックを行った。

具体的なフィードバックの内容は以下のものがでました。

  • 投稿する際のタイトルの部分をどう書いたいいのか分からない。
  • ユーザーページ一覧の都道府県別の投稿をソートで見れるようになったら便利。
  • 画面はスマホでも見やすいと思う。

課題

今回の開発では、実現したい機能をある程度実装することができた反面、課題も出てきた制作となりました。
主な課題点をまとめました。

開発全体

  • テストが不十分

    • 自分自身で確認して問題がない事を確認した事と、ユーザーに試しに使ってもらう程度で終わってしまった。
    • テストツールを使用していなかった→PHPUnit等のテストツールを使用する
    • Gitを使用したバージョン管理の活用が不十分であった。
  • 表示が遅い時がある

    • キャッシュされていないと表示が遅いと感じる。
    • CloudFront等の高速化やS3を使用した画像の保存場所を留意する

フロント側

  • 投稿時の画面選択が一つしかできない。

    • ドラックアンドドロップでPCユーザーには扱いやすいが、スマホユーザーにとってはいちいち画面を選択しないといけないので煩わしい。
  • CSS(BEM)が曖昧な所がある。
    -入れ子になった時に、正しく記述できていない部分があるので、随時修正していく。

インフラ側

  • DB設計時にAWSを使用すると決めていなかった為、画像をストレージ(S3)に保存をせずMySQLに直接保存している。

終わりに

今回の制作では、実際に使ってもらうにはどうしたらいいのかと考えながら制作に取り組みました。
制作後、実際に使って協力して頂きフィードバックを行った際には、自分自身で開発しているだけでは気づきにくい点も把握することができました。
ユーザーにとって使いやすくするものを意識して、制作が行えるように今後の経験にも活かしていきたいと考えています。

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

【PHP8.0】マイナススタートの配列インデックスが使えるようになる

PHP7.4
    $arr = [];
    $arr[-10] = 1;

    $arr[] = 1;

どうなるかというと[-10=>1, 0=>1]です。

これはマニュアルにも明記されています。

1.png

しかしこの動作はPHP8.0で変更になります。
Arrays starting with a negative indexというRFCにおいて変更が決定しました。

Arrays starting with a negative index

Introduction

array_fillのドキュメントには、『start_index が負の場合は、 返される配列の最初のインデックスが start_index となり、それ以降のインデックスはゼロから始まります』とあります。

明示的に負のキーを使用し、その後キーを省略した場合は常にこのような動作になります。

Proposal

現在の動作は、最後の数値キーがnであった場合、次の暗黙キーはn>=0であればn+1n<0であれば0ということになります。

このRFCでは、nの符号に関係なく常にn+1とすることによって、この動作を一貫させることを目指しています。

配列のドキュメントにもわざわざ注釈として書かれているように、これは例外的な動作であり、学習コストとなります。

このプロポーザルはPHP8.0を対象としています。
この変更により問題が発生する可能性がある場合、移行を容易にするために非推奨の通知を出すことも提案します。

以下のコードは全て同じ結果になります。

    $a = array_fill(-2, 3, true);
    $b = [-2 => true, true, true];
    $c = ["string" => true, -2 => true, true, true];
    unset($c["string"]);
    $d[-2] = true;
    $d[] = true;
    $d[] = true;

現在は全て以下のようになります。

~PHP7.4
array(3) {
  [-2]=>
  bool(true)
  [0]=>
  bool(true)
  [1]=>
  bool(true)
}

このRFCにより、以下の動作に変更になります。

PHP8.0~
array(3) {
  [-2]=>
  bool(true)
  [-1]=>
  bool(true)
  [0]=>
  bool(true)
}

非推奨を出力する投票が受理された場合、移行フェーズ中は以下のE_DEPRECATEDを発生させます。

Deprecated: In the next major version of PHP the implicit keys of this array will start from -1 instead of 0 in ...

Backward Incompatible Changes

配列を負のインデックスから開始し、その後暗黙のキーを使い、そして明示的なキーで配列要素にアクセスした際に影響が発生します。

$a[-2] = true; // 現行:キーは-2 / RFC:キーは-2
$a[] = true; // 現行:キーは0 / RFC:キーは-1
$a[] = true; // 現行:キーは1 / RFC:キーは-0

if ($a[1] === true) {
    echo 'Accessing key 1 explicitly';
}

PHP8.0以降、'Accessing key 1 explicitly'が出力されなくなります。

Proposed PHP Version(s)

PHP8.0。

Unaffected PHP Functionality

明示的なキー、文字列のキーには影響しません。
また、-1以上の数値インデックスを持つ配列も影響を受けません。
最初から暗黙のキーを使う配列のインデックスは0から始まります。

キーを使用しないforeachも影響を受けません。

投票

このRFCは賛成17反対2で可決されました。

E_DEPRECATEDを出す提案は賛成8反対14で却下されました。

すなわち、PHP8.0で前触れなく突然動作が変更になります。

感想

PHP4.2から長い時を経て、元の鞘に戻ることになりました。
さすがに当時のことは知らないので、どうして変更されたのかは全くわかりません。

しかし、この仕様に依存する実装をしている人なんていないでしょう。
すなわち実質的な影響はほぼゼロです。
そもそもネガティブスタートの配列って何に使うんだろうか?

ただ、MySQLのAUTO_INCREMENTマイナスは使えないですし、あえて変更する必要があるのかは少し疑問です。

あと配列をコピーしたら何かおきそうで少し楽しみですね。

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

PHPセッション

セッションとは

  • サーバ側に一時的にデータを保存する仕組み

セッションの保持方法

ブラウザがセッションを維持する方法は以下の2つ

  • セッションIDをURLパラメータで渡す
  • ブラウザのCookieに保存する

前者では画面を見ただけでセッションIDを盗まれてしまい、ユーザのなりすましに遭うため、非推奨。
後者がデフォルトで有効になっている。
php.iniでいうと、項目名はsession.use_cookiesで、値はデフォルトで1(有効)

ブラウザのCookieに保存する

ログインの機能で使われる。
イメージでいうと

  1. ログインすることで、ユーザの情報がサーバ側に送られる <- セッションでユーザのデータを保存する
  2. サーバから整理番号を付与される <- セッションIDがクッキーに保存される
  3. ページを遷移してもログインしている状態が続く <- ページにアクセスする度に整理番号(クッキーに保存されたセッションID)をサーバに見せている

セッションIDがクッキーに保存されるとき通常はkeyがPHPSESSIDでvalueはランダムな英数字
このkey名を変更するにはphp.iniに以下の設定をする
session.name=hoge1234
ブラウザに保存されているセッションのkey名を取得するにはsession_name()を使う。
ブラウザに保存されているセッションの情報を取得するにはsession_get_cookie_paramsを使う。

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

PHPでの簡易掲示板の作成の際の考え方

登録画面(register.php)

  • 入力欄が空の場合
    • 入力値($_POST)をチェックする
    • エラーメッセージ用の連想配列を用意
  • 重複登録を防ぐ(メールアドレス)
    • SELECT文で入力されたメールアドレスでSELECT文発行
    • 値が1件以上あればエラーメッセージを出す
  • 入力の手間を減らす
    • 入力値($_POST)をエスケープして出力
  • 確認画面への遷移
    • エラーメッセージ用の連想配列が空だったら実行する
    • 入力値($_POST)をセッション変数に格納する
    • header関数で確認画面へ遷移する
  • 書き直し
    • URLパラメータで書き直しかどうかを判断
    • セッション変数の値を$_POSTに戻す

確認画面(confirm.php)

  • 直に確認画面へアクセスされた場合
    • セッション変数が空だったら、登録画面へリダイレクト
  • 入力値を出力する
    • セッション変数から入力値を出力する(画像の場合はセッション変数から画像のパス名を出力)

画像のアップロード(register.php)

  • $POSTは使わず、$FILESを使う
  • HTML側
    • formタグのenctype属性にmultipart/form-dataを指定
    • inputタグのtype属性にfileを指定
  • 画像以外はアップロードできないようにする
    • ファイル名を取得($_FILE['image']['name'])
    • ファイル名の後ろ3文字(拡張子)を切り取って文字列チェック
  • アップロード
    • ファイル名を取得($_FILE['image']['name'])
    • move_upload_file関数でアップロードされたファイルを新しい位置に移動する
      • move_uploaded_file($_FILES['image']['tmp_name'],'保存先のファイル名含むパス);
    • セッション変数に画像のファイル名を格納する

確認画面(登録処理)(confirm.php)

  • prepareメソッドでSQL文の雛形を用意
  • executeメソッドで実際に格納する値をセッション変数から指定する
    • (パスワードはsha1関数でハッシュ化)
  • INSERT文発行後はセッション変数をunsetメソッドで破棄する
  • 完了画面へ遷移

ログイン機能(login.php)

  • ユーザからの入力値(メールアドレスとパスワード)をSELECT文に埋め込む
  • セッション変数にデータベースからの値(idや氏名など)を格納し、ページ遷移
  • 入力値が空の場合とデータベースとのミスマッチとでエラーメッセージを分ける。

次回からは自動でログインする機能(login.php)

  • セッションはブラウザを閉じた時に情報が消えてしまうのでクッキーを使用する
  • プログラムを書く場所はログインのチェックをしてメンバーの情報がヒットした後に記述する
  • 「次回からは自動でログインする機能」をONにした場合
    • メールアドレスの入力欄には\$_COOKIEか$_POSTのメールアドレスを出力する

メッセージ一覧画面(index.php)

  • 前提としてログインしている状態でないとアクセスできないようにする
    • セッション変数のチェック、セッションが有効期限かどうかをチェック
    • 上記の条件を満たしていない場合はログイン画面へリダイレクト
  • セッションの値から名前を取り出す
  • 投稿したメッセージは投稿者のidとメッセージ本体をDBに格納する
  • DBに格納した後はheader関数で同じページを呼び出す
    • これをしないと$_POSTに値が残ったままとなるため、入力欄が空であってもDBに値が格納されてしまう
  • メッセージ一覧を表示する
    • 必要な情報(名前、画像、メッセージ等)をSQLのSELECT文に組み込む
    • 取ってきた情報をループでHTML出力する

返信機能(index.php?res=メッセージID)

  • 表示するファイルはメッセージ一覧画面と同一で、パラメータが付与される
  • メッセージ一覧画面のURLパラメータに、返信先メッセージのidを付与する
  • 上記のパラメータがセットされていたらそのidをフィルターに、SELECT文を発行し、入力欄にそのメッセージと投稿者を入力欄に出力する
  • hiddenタイプで返信先メッセージid(=URLパラメータと同じもの)を仕込んでおく
  • 返信メッセージを投稿する時に、上記idもINSERT文に組み込む

メッセージ詳細画面(view.php?id=メッセージID)

  • 日付部分に詳細へのリンクを貼る(twitterでも同様)
  • URLパラメータにメッセージのidを付与し、それをSELECT文のフィルターとする
  • データがある場合はデータを出力、データがない場合はそれ用の文言を表示

メッセージの削除機能(delete.php?id=メッセージID)

  • $_SESSIONのidとメッセージ投稿者のidが一致したら削除のリンクを出す
  • 削除するプログラムに書く内容
    • URLパラメータをフィルターにしてDBから取得した投稿者のidとセッションのidが一致していたらDELETE文を発行する
    • DELETE文発行後は一覧画面へ遷移

ページネーション(index.php?page=ページ数)

  • URLパラメータにページ数を付与
  • SELECT文にLIMIT句を追記
  • いくつかの制約を設ける
    • ページ数が指定されていないとき
    • ページ数が1より小さいとき
    • ページ数の上限を超えたとき
  • リンクを貼る
    • 前ページ
      • href属性:現在指定されているパラメータからマイナス1
      • 条件:現在指定されているパラメータが1より大きい場合に出力する
    • 次ページ
      • href属性:現在指定されているパラメータからプラス1
      • 条件:マックスページ数に収まる場合に出力する

ログアウト機能(logout.php)

  • セッション変数を空の配列で上書き
  • session_destroy関数を実行
  • クッキーに保存していたメールアドレスも削除する
  • セッションで使用しているクッキーの値を空にする、有効期限を切る
if (ini_set('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(session_name() . '',time()-3600,$params['path'],$params['domain'],$params['secure'],$params['httponly']);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel のログイン機能name へ変更

以下の手順で上手く行きました。

1

【2014_10_12_000000_create_users_table.php】
$table->string('name')->unique();

2
【LoginController.php】
public function username()

{

return 'name';

}

3
【login.blade.php】

Name
<div class="col-md-6">                                      
    <input id="name" type="name" class="form-control" name="name" value="{{ old('name') }}" required autofocus                                      
    @if ($errors->has('name'))  

        <span class="help-block">   

            <strong>{{ $errors->first('name') }}</strong>   

        </span> 

    @endif  

</div>

拍手!

分かる人は分かる

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

laravel のログイン機能をnameでログインへ変更する機能

以下の手順で上手く行きました。

1

【2014_10_12_000000_create_users_table.php】
$table->string('name')->unique();

2
【LoginController.php】
public function username()

{

return 'name';

}

3
【login.blade.php】

Name
<div class="col-md-6">                                      
    <input id="name" type="name" class="form-control" name="name" value="{{ old('name') }}" required autofocus                                      
    @if ($errors->has('name'))  

        <span class="help-block">   

            <strong>{{ $errors->first('name') }}</strong>   

        </span> 

    @endif  

</div>

動作確認OK

拍手!

以上

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

Laravel で S3 にファイルを上げるタイミングで ACL を設定する

はじめに

Laravel のファイルストレージでは AWS の S3 を指定して保存することができます。
S3を指定する方法は公式ドキュメントを参照してもらうとして、保存は下記のように store メソッドを利用します。

<?php

namespace App\Http\Controllers;

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

class UserAvatarController extends Controller
{
    public function update(Request $request)
    {
        $path = $request->file('avatar')->store('avatars');

        return $path;
    }
}

重要なポイント

保存をするとき、上記の例では Storage ファサードを使っていません。
もちろんファサードを使って保存することも可能で、

$path = Storage::putFile('avatars', $request->file('avatar'));

このように公式ドキュメントには書いてあります。
で、やりたいのはドキュメントに ファイル視認性 と書かれていますが、

$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public')

ファイルが既に保存されている場合、上記の設定でズバッと設定をしてしまいたい。デフォルトで private になっているところを公開の public にしたい場合はどうしたら良いのか、です。
つまり store の段階で最初からできてくれよってことですね。

で、例の如く...

ないんですよね、ドキュメント上に正解が。

ヒントはあって、第二引数で他のディスク(この例だとローカルストレージを利用中)に保存したい場合などは下記のように書かれています。

$path = $request->file('avatar')->store(
    'avatars/'.$request->user()->id, 's3'
);

となると、もしかすると第3や第4の引数でなにかあるかもしれない。
ということでコアを追いかけることになります。

この場合の $requestIlluminate\Http\Requestです。
追跡していくと、

Illuminate\Http\Request
 ↓
Illuminate\Http\Concerns\InteractsWithInput
 ↓
Illuminate\Http\UploadedFile

と、辿れます。どうやって辿るかは正直、勘です。
エスパー並の感度で遡った結果、

store メソッドを見つけました。

Illuminate\Http\UploadedFile
    public function store($path, $options = [])
    {
        return $this->storeAs($path, $this->hashName(), $this->parseOptions($options));
    }

    public function storeAs($path, $name, $options = [])
    {
        $options = $this->parseOptions($options);

        $disk = Arr::pull($options, 'disk');

        return Container::getInstance()->make(FilesystemFactory::class)->disk($disk)->putFileAs(
            $path, $this, $name, $options
        );
    }

    protected function parseOptions($options)
    {
        if (is_string($options)) {
            $options = ['disk' => $options];
        }

        return $options;
    }

parseOptions を見ると、文字列を与えると自動的に配列で disk の key/val 型になるように整形して storeAs に渡しているのがみて取れますね。

で、最終的には putFileAs で保存しているのが分かるわけですが、FilesystemFactory でいよいよ Factory の文字が見えてまいりました。

これの実体は Illuminate\Contracts\Filesystem\Factory です。
こいつは interface なので、基底にもつクラスがいるはずで、それは何かというと Illuminate\Filesystem\FilesystemAdapter になります。
このなかに putFileAs のメソッドも登場してくるわけですが、

Illuminate\Filesystem\FilesystemAdapter
    public function putFileAs($path, $file, $name, $options = [])
    {
        $stream = fopen($file->getRealPath(), 'r');

        $result = $this->put(
            $path = trim($path.'/'.$name, '/'), $stream, $options
        );

        if (is_resource($stream)) {
            fclose($stream);
        }

        return $result ? $path : false;
    }

    public function put($path, $contents, $options = [])
    {
        $options = is_string($options)
                     ? ['visibility' => $options]
                     : (array) $options;

        if ($contents instanceof File ||
            $contents instanceof UploadedFile) {
            return $this->putFile($path, $contents, $options);
        }

        return is_resource($contents)
                ? $this->driver->putStream($path, $contents, $options)
                : $this->driver->put($path, $contents, $options);
    }

putFileAs は put への橋渡しをしていますね。
で、put のところで visibility が登場しており、この辺りがかなりそれっぽい。
で、最終的に $this->driver で保存してそうだということが return からわかります。

ここで使われている drive は当然ながら s3 のはずなので、driver を探します。
このファイルの先頭付近で

use League\Flysystem\AwsS3v3\AwsS3Adapter;

と書かれているので、この辺りがとても怪しい。
本体は Laravel のコアから外れて /vendor/league の下までいくとようやく出てきます。
このクラスを眺めていると、ようやく AWS SDK のオプションが出てきました。

この辺は AWS SDK for PHP のドキュメントを見ると、ファイルアップロード関数の putObject と一致しているのがわかります。

League\Flysystem\AwsS3v3\AwsS3Adapter
    protected static $metaOptions = [
        'ACL',
        'CacheControl',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLength',
        'ContentType',
        'Expires',
        'GrantFullControl',
        'GrantRead',
        'GrantReadACP',
        'GrantWriteACP',
        'Metadata',
        'RequestPayer',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'SSEKMSKeyId',
        'ServerSideEncryption',
        'StorageClass',
        'Tagging',
        'WebsiteRedirectLocation',
    ];

この場合、ACL が今回の対象になるポイントで、Laravelがファイル視認性とか言ってますが、最終的に渡したいのは
private|public-read|public-read-write|authenticated-read|aws-exec-read|bucket-owner-read|bucket-owner-full-control のどれかをこのアダプターの ACL というキーに突っ込みたいわけです。
というわけで、それっぽい関数をみてみると、

League\Flysystem\AwsS3v3\AwsS3Adapter
    protected function upload($path, $body, Config $config)
    {
        $key = $this->applyPathPrefix($path);
        $options = $this->getOptionsFromConfig($config);
        $acl = array_key_exists('ACL', $options) ? $options['ACL'] : 'private';

        if (!$this->isOnlyDir($path)) {
            if ( ! isset($options['ContentType'])) {
                $options['ContentType'] = Util::guessMimeType($path, $body);
            }

            if ( ! isset($options['ContentLength'])) {
                $options['ContentLength'] = is_resource($body) ? Util::getStreamSize($body) : Util::contentSize($body);
            }

            if ($options['ContentLength'] === null) {
                unset($options['ContentLength']);
            }
        }

        try {
            $this->s3Client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);
        } catch (S3MultipartUploadException $multipartUploadException) {
            return false;
        }

        return $this->normalizeResponse($options, $path);
    }

upload のメソッドに $acl = array_key_exists('ACL', $options) ? $options['ACL'] : 'private';が出てきますね。
ACLという配列キーが出てこなければ private にせよ、と書いてあるので逆に ACL のキーで渡せば良いことがわかります。

というわけで解決方法

長くなりましたが $options に配列で渡していけば良いことがわかりました。

$path = $request->file('avatar')->store('avatars', ['disk' => 's3', 'ACL' => 'public-read']);

ということで、このように第二引数に配列で渡しましょう。その際に disk のキーを渡すのもお忘れなく。

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

composer 実行時に Allowed memory size エラーで困ったとき

composer update を実行したら、いきなり怒られるようになりました。

# composer update

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 16777216 bytes) in phar:///usr/local/bin/composer/src/Composer/DependencyResolver/Solver.php on line 220

そんな時は、実行時のメモリ制限を切って対応しましょう。

php -d memory_limit=-1 /usr/local/bin/composer update

composerコマンドの配備先については、各環境で合わせて下さい。

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

PHP 変数のデータ型を出力しよう

目的

  • 変数のデータ型を確認する命令var_dumpを知ったので簡単にまとめる

書き方の例

  • 下記にvar_dumpの使用方法をまとめる。
var_dump($変数名);
  • 下記にプレビューイメージを記載する。
データ型(格納されている文字列の文字数) "格納されている文字列"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

master以外のブランチからherokuにpushする方法

railsアプリを作って、herokuをデプロイする(masterブランチ以外からデプロイする方法も同時に載せています。)

1 コミット

$ git add .
$ git commit -m "heroku"
$ git push origin ブランチ名

2 herokuにアプリケーションを新規作成する

$ heroku create

3-1 herokuにデプロイする(masterブランチにいる時)

$ git push heroku master

3-2 herokuにデプロイする(master以外のブランチにいる時)

$ git push heroku <現在いるブランチ名>:master

※3-1 or 3-2 どちらかを実行してください

4 herokuにデータベースを作成する

$ heroku run rails db:migrate

5 アプリケーションを開く

$ heroku open

以上で、完成です。

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

PHPでシンプルなToDoアプリ(掲示板)を作る

データベース不要のToDoアプリ

はじめに

今回、シンプルなToDoアプリ(掲示板)を作りました。
言語はPHPのみで、MySQLなどのデータベースは一切使いません。
ToDoアプリ

今回作ったToDoアプリの特徴

  • 投稿内容はボタンで削除することができます。
  • 送信したときに画面遷移しません。

この記事の対象者

PHPの基礎を勉強している人,PHPやサーバーサイド言語に興味がある人などなど。

さっそく作きましょう!

わからない部分は一つ一つ調べ、進めていきましょう。

PHP(32行),HTML

スクリプトを3分割していますが、実際は全て繋げて一つのファイルに記述します。まずはPHP前半です。(主に変数の準備)

todo.php
<?php

//HTMLタグにお入力を無効にし、文字コードをutf-8にする
//(PHPのおまじないのようなもの)
function h($v){
    return htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
}

//変数の準備
$FILE = 'todo.txt'; //保存ファイル名

$id = uniqid(); //ユニークなIDを自動生成

//タイムゾーン設定
date_default_timezone_set('Japan');
$date = date('Y年m月d日H時i分'); //日時(年/月/日/ 時:分)

$text = ''; //入力テキスト

$DATA = []; //一回分の投稿の情報を入れる

$BOARD = []; //全ての投稿の情報を入れる

//$FILEというファイルが存在しているとき
if(file_exists($FILE)) {
    //ファイルを読み込む
    $BOARD = json_decode(file_get_contents($FILE));
}

☞ 今回の変数を保存する配列について

  • \$BOARD[] 全体の配列
  • \$DATA[\$id, \$date, \$text] 一回分の投稿(これを\$BOARD[]へ挿入していく)

結局、下記のような形で保存されます。
\$BOARD[\$DATA[\$id, \$date, \$text], \$DATA[省略], \$DATA[省略],...]

☞ 保存データの読み取り

詳細は後々説明しますが、今はなんとなくデータを持ってきて、変数に保存したんだなと思っておいてください。(最初の時点ではファイルもデータもないのでスルーでOK!
また、今回は全てのデータをJSON形式(今回はただの配列だと思ってください)で、todo.txtというファイルに保存していきます。

  • file_get_contents() ファイルを読み込む
  • json_decode() JSON形式をPHPで扱える形式に変換する

ここからのPHP後半が、ファイルを生成し、フォーム内容を保存していく手順になります。

todo.php
//$_SERVERは送信されたサーバーの情報を得る
//REQUEST_METHODはフォームからのリクエストのメソッドがPOSTかGETか判断する
if($_SERVER['REQUEST_METHOD'] === 'POST'){
    //$_POSTはHTTPリクエストで渡された値を取得する
    //リクエストパラメーターが空でなければ
    if(!empty($_POST['txt'])){
        //投稿ボタンが押された場合

        //$textに送信されたテキストを代入
        $text = $_POST['txt'];

        //新規データ
        $DATA = [$id, $date, $text];
        //新規データを全体配列に代入する
        $BOARD[] = $DATA;

        //全体配列をファイルに保存する
        file_put_contents($FILE, json_encode($BOARD));

    }else if(isset($_POST['del'])){
        //削除ボタンが押された場合

        //新しい全体配列を作る
        $NEWBOARD = [];

        //削除ボタンが押されるとき、すでに$BOARDは存在している
        foreach($BOARD as $DATA){
            //$_POST['del']には各々のidが入っている
            //保存しようとしている$DATA[0]が送信されてきたidと等しくないときだけ配列に入れる
            if($DATA[0] !== $_POST['del']){
                $NEWBOARD[] = $DATA;
            }
        }
        //全体配列をファイルに保存する
        file_put_contents($FILE, json_encode($NEWBOARD));
    }

    //header()で指定したページにリダイレクト
    //今回は今と同じ場所にリダイレクト(つまりWebページを更新)
    header('Location: '.$_SERVER['SCRIPT_NAME']);
    //プログラム終了
    exit;
}
?>

☞ 保存場所を作る

  • json_encode() メッセージの配列をJSON形式に変換する
  • file_put_contents(a,b) bの内容をファイルaに保存する(もしファイルaが存在しなければ自動で生成)

☞ 保存場所

今回は、最初にテキストが送信されたとき、投稿した情報である\$DATA(idと日時、入力文字)を、file_put_contents()を用いて自動生成したtodo.txtというファイルに保存します。

ToDoApp
├── todo.php
├── todo.txt(自動で生成される)

ここからがPHPの後に記述していくHTMLです

todo.php
<!DOCTYPE html>
<html lang= "ja">
<head>
    <meta name= "viewport" content= "width=device-width, initial-scale= 1.0">
    <meta http-equiv= "content-type" charset= "utf-8">
    <title>ToDoApp</title>
</head>
<body>
    <h1>ToDoアプリ</h1>

    <section class= "main">
        <h2>ToDoAppに投稿する</h2>

        <!--投稿-->
        <form method= "post">
            <input type= "text" name= "txt">
            <input type= "submit" value= "投稿">
        </form>    

        <table style= "border-collapse: collapse">
        <!--tableの中でtr部分をループ-->
        <?php foreach((array)$BOARD as $DATA): ?>
        <tr>
        <form method= "post">
            <td>
                <!--テキスト-->
                <?php echo h($DATA[2]); ?>
            </td>
            <td>
                <!--日時-->
                <?php echo $DATA[1]; ?>
            </td>
            <td>
                <!--削除-->
         <!--この時その投稿のidがサーバーに送信される-->
                <input type= "hidden" name= "del" value= "<?php echo $DATA[0]; ?>">
                <input type= "submit" value= "削除">
            </td>
        </form>
        </tr>
        <?php endforeach; ?>
        </table>
    </section>

☞ 今回のformについて

今回はフォームから入力した内容をPOST送信でサーバーに送信します。

☞ 投稿

テキストを入力し投稿ボタンを押したとき、そのテキスト内容がサーバーに送信されます。それと同時にPHPで\$idと\$dateを呼び出し\$textとともに\$DATAに格納します。この\$DATAを全体配列\$BOARD挿入し、保存します。

☞ 削除

削除ボタンを押したとき、その投稿のidがサーバーに送信されます。そのidが、すでに保存されている投稿のidと同じであれば保存せず、それ以外を保存しなおします。

CSS,JavaScript

CSSやJavaScriptを使うと、よりUIを整えることができます。

おわりに

今回はデータベースを使わずに、シンプルなToDoリストを作ってみました。もちろんデータが膨大になったり、複雑になったときはMySQLやFirebaseを使うといいでしょう。データが軽量かつシンプルに完結できる方法を模索した結果、このようなアプリを作ることができました。
何かアドバイス等あれば教えていただければと思います。
そして、この記事が皆様にお役に立てれば幸いです。

参考サイト

https://code-notes.com/lesson/4 ユニークなidを用いてデータを削除
確かな力が身につくPHP「超」入門 p.159 ファイル入出力

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

【Laravel】リレーション先で検索する方法

Laravelでリレーション先で検索することがあったため備忘録。

例えば下記のようなモデルがあったとします。

User.php
class User extends Model
{
    public function project()
    {
        return $this->belongsTo('App\Project');
    }
}
Project.php
class Project extends Model
{
    public function organization()
    {
        return $this->belongsTo('App\Organization');
    }
}

※主キーはidとします。

リレーション先で検索

Userからリレーション先であるProjectのorganization_idで絞り込みたいとします。

  • whereHasを使用する場合
UserController.php
$Users = User::whereHas('project', function ($query) use ($request) {
    $query->where('organization_id', $request->organization_id);
})->get();

  • whereHasを使用しない場合
UserController.php
$Users = User::whereIn('project_id', function ($query) use ($request) {
    $query->from('projects')
        ->select('id')
        ->where('organization_id', $request->organization_id);
})->get();

リレーション先のリレーションで検索

Userからリレーション先であるProjectのリレーション先のOrganizationのnameで絞り込みたいとします。

  • whereHasを使用する場合
UserController.php
$Users = User::whereHas('project', function ($query) use ($request) {
    $query->whereHas('organization',  function ($query) use ($request) {
        $query->where('name', $request->organization_name);
    }
})->get();

  • whereHasを使用しない場合
UserController.php
$Users = User::whereIn('project_id', function ($query) use ($request) {
    $query->from('projects')
        ->select('id')
        ->whereIn('organization_id', function ($query) use ($request) {
            $query->from('organizations')
                ->select('id')
                ->where('name', $request->organization_name);
        });
})->get();

他により良い方法などありましたらご教示いただけますと幸いです。

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

【PHP】stripe API で定額課金を実装する

はじめに stripeとは

stripeは、クレジットカードなどの決算処理を代行してくれるサービスです。
シンプルな実装で決済処理を実現でき、カード情報を自社サーバで持たなくても良いなどの特徴があります。

大まかな流れとして、

  1. サービスの請求モデルを作成
  2. フロントサイドでカード情報を取得
  3. バックエンドで顧客登録、決済処理

と進んでいきます。

サービスの請求モデルを作成する

1. stripeに登録する

1-1.はじめに、stripeの公式ページにて、今すぐ始めるをクリック。

スクリーンショット 2020-01-15 20.24.58.png

1-2メールアドレス、名前、パスワードを入力登録。

なお、テスト用に登録する際には、メールアドレスの存在確認は求められません。
この時点で、登録が完了いたしました。
スクリーンショット 2020-01-15 20.27.48.png

1-3.ダッシュボードが表示される

登録が完了したら、いくつかの質問がされるので答えていきます。
質問が完了したら、次のようなダッシュボードが表示されます。
スクリーンショット 2020-01-15 20.34.07.png

2.テストAPIキーを取得する

ダッシュボードが表示されたら、まずはAPIキーを取得してみましょう。
今回はテスト用のAPIキーを取得します。
ダッシュボードのテストAPIキーの取得をクリックしてください。スクリーンショット 2020-01-15 20.39.48.png

公開可能キーシークレットキーの2つが表示されています。
スクリーンショット 2020-01-15 20.43.27.png

公開可能キー

公開可能キーは、不特定多数に表示されてもかまわないキーです。
JavaScriptなど、フロントサイドで使用されるキーです。

シークレットキー

一方シークレットキーは秘密にしなければなりません。
PHPなど、サーバーサイドで使用されます。

3.商品(Product)を登録する

3-1.Billing->商品をクリック

はじめに、提供するサービスである、商品(Product)を登録します。
画面左側のBillingをクリックしてから、商品が表示されるのでもう一度クリックしてください。

スクリーンショット 2020-01-15 21.24.24.png

テスト商品を追加をクリックします。
スクリーンショット 2020-01-15 21.31.18.png

3-2.商品を作成

商品名を入力して、商品を作成をクリックします。
スクリーンショット 2020-01-15 21.33.31.png

なお、ここでは商品の価格などの情報は入力しません。
商品の配下に、プラン(Plan)を作成して個別に料金体系(価格、通貨、支払い間隔など)を設定します。
一つの商品に対して、複数のプランが設定できます。
例えば、有料会員という製品には以下のプランが紐付けられていると考えられます。

  • 有料会員
    • ブロンズ会員 月額500円
    • シルバー会員 月額1,000円
    • ゴールド会員 月額3,000円

4.プランを作成する

4-1.プランの作成

それでは実際にプランを作成してみます。
商品を作成したら、そのままプランを作成するページへ遷移します。
スクリーンショット 2020-01-15 21.41.54.png
スクリーンショット 2020-01-15 21.43.12.png

必要な項目を入力して、料金プランを追加をクリックすると、プランが追加されます。

4-2.プランIDの取得

後ほど会員をプランに追加する際に、プランIDが必要になるのでここで取得します。
prod_から始まる商品IDではないことに注意してください
先程作成したプランクリックして詳細を表示します。
スクリーンショット 2020-01-16 0.17.41.png

plan_から始まる英数字がプランIDです。
スクリーンショット 2020-01-15 22.25.39.png

コードを実装する。

5.ライブラリをインストールする

いよいよコードの実装にはいります。
はじめに、Composerを利用して、PHPのライブラリを入手します。

Composerのインストール

composer require stripe/stripe-php

6.フロントサイドの実装(JavaScript)

6-1.カード情報を入手する

カード情報を入手するための入力フォームを、JavaScritpで実装します。
カード情報といっても、実際に得られるのはAPI通信によって得られたトークンの情報です。

index.html
<form id="form_payment" action="/add.php" method="post">
  Name:<input id="name" type="text" name="name">
  Email:<input id="email" type="text" name="email">
  <!-- ここのdivタブがカード入力フォームに置き換わります。 -->
  <div id="card-element" class="MyCardElement"></div>
  <!-- ここにエラーメッセージが表示されます。 -->
  <div id="card-errors" role="alert"></div>
  <button id="button">Submit</button>
</form>
<!-- stripAPIを読み込みます -->
<script src="https://js.stripe.com/v3/"></script>
<script>
  // 公開可能なAPIキーです。
  const stripe = Stripe('pk_test_aGucPIXrlDAHQuflip6RqErD00CqmOnlKK');
  // 入力フォームを生成します。スタイルを指定することもできます。
  const elements = stripe.elements();
  const cardElement = elements.create("card");

  // 先程のdivタブにマウントします。
  cardElement.mount("#card-element");

  // クレジットカード番号や有効期限の入力に合わせてエラーメッセージを出力します。
  cardElement.addEventListener('change', ({error}) => {
      const displayError = document.getElementById('card-errors');
      if (error) {
        displayError.textContent = error.message;
      } else {
        displayError.textContent = '';
      }
  });

  const submit = document.getElementById('button');
  const name = document.getElementById('name');
  const email = document.getElementById('email');

  // 登録ボタンがクリックされたら、API通信をおこなう
  submit.addEventListener('click', async(e) => {
    e.preventDefault();
    const {paymentMethod, error} = await stripe.createPaymentMethod({
      type: 'card',
      card: cardElement,
        billing_details: {
          // 顧客名emailアドレスはなくてもOK
          name: name.value,
          email: email.value,
      },
    });
    // 通信エラー時
    if (error) {
      console.error(error)
    } else {
        // 成功したらトークンが返されるので、hiddenに埋め込む
        const form = document.getElementById('form_payment');
        const hiddenToken = document.createElement('input');
        hiddenToken.setAttribute('type', 'hidden');
        hiddenToken.setAttribute('value', paymentMethod.id);
        hiddenToken.setAttribute('name', 'token');
        form.appendChild(hiddenToken);
        form.submit();
      }
    });
</script>

下記画像のように、カード番号・有効期限・確認番号・郵便番号を入力するフォームが生成されました。
スクリーンショット 2020-01-16 11.09.10.png

これらの項目はすべて必須項目です。
もし、郵便番号の入力をなくしたいなら、入力フォーム生成時にhidePostalCodetrueにします。

    const cardElement = elements.create('card', {hidePostalCode: true});

テスト用として、4242 4242 4242 4242の番号が用意されているので、そちらを利用します。
その他の値はなんでもかまわないようです。

7.サーバーサイド(PHP)

7-1.顧客情報を登録する。

フロントサイドから受け取ったトークンをもとに、顧客情報を登録します。

<?php
require '/vendor/autoload.php';
// シークレットキーをセットする
\Stripe\Stripe::setApiKey(YOUR_SECRET_KEY);

if($_SERVER['REQUEST_METHOD'] == 'POST') {
    // ここではバリデーションは省略しています
    $name = $_POST['name'];
    $email = $_POST['email'];
    $token = $_POST['token'];

    // 顧客情報を登録
    $customer = \Stripe\Customer::create([
        'payment_method' => $token, // 登録する支払い方法
        'name' => $name,
        'email' => $email,
        'invoice_settings' => [
            'default_payment_method' => $token, // デフォルトで使用する支払い方法。必須。
        ],
    ]);

7-2.顧客をプランに登録する

さらに、先程登録した顧客情報をもとに、プランへの登録処理を行います。

    // 顧客をプランに登録する
    $subscription = \Stripe\Subscription::create([
        // 先程登録した顧客情報のID
        'customer' => $customer->id,
        'items' => [
            [
              // 4-2で取得したプランID
              'plan' => 'plan_GYHnkoAjA7PFtG',
            ],
        ],
        'trial_end' => strtotime('+3 month'),
        // トライアル(無料)期間。UNIX秒で指定する。
    ]);
    // subsctiprionIDは、解約時に必要
    $sub_id = $subscription->id; // sub_GYJXuOff81PbZy


    // DBを準備
    try {
        $db = new PDO('mysql:dbname=php;host=localhost', 'root', '');
    } catch (PDOException $e) {
        print "Coudn't connet to the database: " . $e->getMessage();
        exit();
    }

    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 顧客情報を登録
    try {
        $stmt = $db->prepare('INSERT INTO Users (name, email, sub_id)
                                VAlUES (?,?,?)');
        $stmt->execute($name, $email, $sub_id);

    } catch (PDOException $e) {
        print "Coudn't connet to the database: " . $e->getMessage();
        exit();
    }
}

trial_endを指定すると、トライアル(無料期間)を定めることができます。
音楽ストリーミングサイトによくある初回3ヶ月無料!みたいなやつです。

UNIX秒で指定するので、strtotimeを使用すればある程度簡単に指定することができます。

プラン登録時の返却値のSubscriptionオブジェクトのIDは、解約時に必要になります。
なので、顧客情報とともにDBに登録します。

8.登録を確認する

ダッシュボードに戻り、左側の顧客をクリックすると、先程登録した顧客情報が表示されているかと思います。
スクリーンショット 2020-01-15 23.34.23.png

無事顧客が登録されていましたね。
さらに、詳細情報をみると顧客の定期支払を確認することができます。
スクリーンショット 2020-01-15 23.33.40.png

トライアル期間を設定したので、請求書をみると初回の支払いは0円で、次回請求日は3ヶ月後に設定されていることが確認できます。(今回登録した日付は01/15)
スクリーンショット 2020-01-15 23.34.58.png

9.プランを解約する

最後に、顧客が有料会員プランを解約する流れを説明します。

<?php
// 契約を解除するためにログインしている想定
session_start();
require 'vendor/autoload.php';

$name = $_SESSION['name'];
// DBを準備
try {
    $db = new PDO('mysql:dbname=php;host=localhost', 'root', '');
} catch (PDOException $e) {
    print "Coudn't connet to the database: " . $e->getMessage();
    exit();
}

// ログインユーザーからsub_idを取得
$stmt = $db->prepare('SELECT sub_id FROM Users WHERE name = ?');
$stmt->execute($name);
$row = $stmt->fetch('assoc');
if ($row) {
  $sub_id = $row->sub_id;
} else {
  exit();
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// シークレットキーをセットする
\Stripe\Stripe::setApiKey(YOUR_API_KEY);

// 解約したあとも有効期限までは、プランに入会したまま
\Stripe\Subscription::update(
  $sub_id,
  [
    'cancel_at_period_end' => true,
  ]
);

上記の例のようにcancel_at_period_endtrueにしてプランをSubscription::updateで更新することで、解約後も有効期限内まではプランが有効になります。

もし、解約後はすぐにプランを終了したいときには、以下のようなコードを書きます。

// 解約後はすぐにプランが終了する。
$subscription = \Stripe\Subscription::retrieve('sub_49ty4767H20z6a');
$subscription->cancel();

8-2.解約を確認

ダッシュボードを確認すると、先程の会員が有効期限である04/15にプランがキャンセルされることが確認できました。
スクリーンショット 2020-01-16 0.06.57.png

おわりに

定額課金の決済の簡単な流れを説明しました。
他にも期間中のアップグレード、ダウングレードや途中解約に対する比例配分、更新時にカードが利用できなかった場合など複雑な状況にも対応できます。

ぜひ、ドキュメントも参照してみてください。

参考サイト

公式ドキュメント
API リファレンス
Stripe Billing 101
Stripe.js & Elements を利用して決済フローを理解する

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

WindowsにphpMyFaq(sqlite)をインストール

インストール環境

今回は以下の環境にphpMyFaqをインストールする手順を紹介します。

  • OS:Windows10
  • IIS:10.0
  • PHP:7.3
  • phpMyFaq:2.9
  • SQLite:3.30

事前作業

以下の作業は完了しているものとして説明を割愛します。
調べればいくらでも手順が見つかるので…

  • IISのインストール、およびCGIの有効化
  • PHPのインストール、php.iniの作成
  • SQLiteのインストール、パスの追加

手順

php.iniの設定変更

phpMyFaqを使用するために設定を変更します。
拡張モジュールの追加と、phpMyFaqインストール時に実行するセットアップスクリプトがタイムアウトしないようにするための設定を変更します。
処理がタイムアウトしてしまうとインストールが中途半端な状態で終わってしまうため、作業をやり直す羽目になります…注意してください!

■既存設定の変更(10時間に変更)
max_execution_time = 36000

■コメントアウトしている箇所を有効化
extension_dir = "ext"
extension=curl
extension=fileinfo
extension=gd2
extension=pdo_sqlite
extension=sqlite3

phpMyFaqをダウンロードして、IISの公開ディレクトリに展開する

公式サイトからzipファイルをダウンロードします。
zipファイルは解凍してリネームします。以下の手順では"faq"というフォルダ名に変更したことにします。

IISの公開ファイルを保存しているフォルダ(デフォルトではC:\inetpub\wwwroot)にリネームしたフォルダを移動します。
phpMyFaqのセットアップスクリプトがフォルダ内のファイルを変更するので、アクセス許可の設定は緩めにしておくとトラブルがなくて済むと思います。

faqフォルダ内に空のフォルダを追加する

faqフォルダ配下に下記のフォルダを作成します。

  • images
  • attachments
  • data

IISマネージャからphpのモジュールマップの追加

IISマネージャを起動します。
すでにIISでPHPが実行可能な状態であれば、この項目はスキップしてください。
左ペインにあるツリーから、サーバ名 > サイト > faqの項目をクリックして、中央ペインにある「ハンドラーマッピング」をダブルクリックします。
右ペインにある「モジュールマップの追加」をクリックして、以下のように設定します。

image.png
実行可能ファイルのパスはphpをインストールしたフォルダを指定してください。
名前は適当でOKです。

IISマネージャからFastCGIの設定を変更

左ペインのサーバ名の項目をクリックして、中央ペインの「FastCGIの設定」をダブルクリックします。
先程追加したphpの項目が追加されているのでダブルクリックして、"アクティビティタイムアウト"と"要求タイムアウト"の設定を36000(10時間に変更)にします。
image.png

SQLiteのデータベース作成

コマンドプロンプトを起動して、データベースを作成するフォルダに移動します。
sqliteコマンドを実行して、データベースファイルを作成します。

管理が面倒だったので、phpMyFaqのアプリケーション配下に"phpmyfaq.sqlite3"というファイル名で作成しています。
ファイルの置き場所としてはセキュリティ上あまり良くないと思います…
インターネットに公開するサイトにする場合は注意してください。

cd C:\inetpub\wwwroot\faq
sqlite3 phpmyfaq.sqlite3

ここで作成したデータベースファイルはphpMyFaqのセットアップ時に入力するので、フルパス(C:\inetpub\wwwroot\faq\phpmyfaq.sqlite3)をメモしておいてください。

初回セットアップスクリプトの実行

phpMyFaqのトップページ(http://localhost/faq)にアクセスして、セットアップスクリプトを実行します。
SQLite database fileのデータベースファイルは先程作成したものを指定します。
phpMyFAQの設定は適宜入力してください。
また、LDAPのモジュールがphpで読み込まれているとLDAPの設定項目が表示されます。

image.png

ここまでできたらあとはインストールボタンを押すだけです。お疲れ様でした!

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

?【CakePHP2】処理の中で別のDB設定ファイルを読み、その内容にてDB再接続をする

環境

PHP 7.2.21
CakePHP 2.10.18
MySQL 5.7.27

やりたいこと

CakePHPの処理中にて別のDBからデータを取得するためにDBの再接続を行いたい
前の記事にて別のDB設定ファイル読込み関数を用意したのでそれと合わせてControllerからDB切替えを行う

やったこと

ビヘイビアに別のDB情報を読み込む関数を用意し
ModelでactAsでビヘイビアを指定

Modelには更にDB再接続の関数を追加しておいてControllerで呼び出す

Model

Model/AppModel.php
<?php
App::uses('AppBaseModel', 'Model');

/**
 * AppModel
 */
class AppModel extends AppBaseModel {
    /**
     * Hogeビヘイビアを使うためactAs追加
     */
    public $actsAs = [
        'Hoge',
    ];

    /**
     * 指定した設定ファイルを読込んでdataSourceを変更しDB再接続
     * @param $dbFileName 読み込むdatabase.phpファイル名
     * @param $dBConfig   database.php内DBの参照情報
     */
    public function connectAnotherDatabase($dbFileName, $dBConfig = 'default') {
        if (!empty($dbFileName)) {
            // $dbFileNameからDB情報呼び出し
            $dbc = $this->readDatabaseValue($dbFileName);
            // 前の処理でインスタンス化したClassを破棄
            ConnectionManager::drop($dBConfig);
            // DB再接続
            $db = ConnectionManager::create($dBConfig, $dbc);
            $db->reconnect($dbc);
            $this->setDataSource($dBConfig);
            return true;
        } else {
            return false;
        }
    }
}

Behavior

https://qiita.com/eltociear/items/55d45a35a8891c52c126
設定ファイル読み込み関数は↑記事と同じものを使用

Model/Behavior/HogeBehavior.php
<?php
/**
 * Hoge behavior
 */
class HogeBehavior extends ModelBehavior {
    /**
     * 別のDB情報を環境database.phpから読込み
     *
     *@param Model  $model
     *@param string $dbFileName 読み込みたいdatabase.phpファイル名
     *@return array 取得情報
     */
    public function readDatabaseValue(Model $model, string $dbFileName) {
        $cnt = 0;
        $flg = false;

        // 読み込みたいdatabase.phpのフルパス
        $filePath = ROOT . DS . 'app' . DS . 'Config' . DS . $dbFileName;
        if (file_exists($filePath)) {
            $fdata = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            foreach ($fdata as $line) {
                $line = preg_replace("/( |,|')/", "", $line);
                $data = explode('=>', $line);
                if (strpos($line, 'public') !== false) {
                    $cnt++;
                    $flg = false;
                }
                if ($cnt == 1 && $flg == true) {
                    $result[$data[0]] = $data[1];
                }
                $flg = true;
            }
        }

        return [
            'datasource' => $result['datasource'],
            'persistent' => $result['persistent'] === 'true' ? true : false,
            'host'       => $result['host'],
            'login'      => $result['login'],
            'password'   => $result['password'],
            'database'   => $result['database'],
            'prefix'     => $result['prefix'],
            'encoding'   => $result['encoding'],
        ];
    }
}

Controller

別DBに再接続したい処理のあるControllerに設定ファイル名と上記で指定した関数呼び出しを記述

Controller/HogeController.php
// 再接続対象のDB情報がかかれた設定ファイル
$dbFileName = 'Neko.php';
// AppModelをextendsしたModelを使い別のDBに再接続
$this->Users->connectAnotherDatabase($dbFileName);

結果

変数で対象のDB設定ファイルを指定できるので再接続対象を動的にも指定できます

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