20190530のPHPに関する記事は9件です。

Laravel-Eloquent(Mysql)で、select結果に固定値カラムを追加する方法(使用例)

mysql

select '1'  as sample;

Laravel

$list = SampleModel::select('id', DB::raw("'1'  as sample"))->get();

真偽値なんかを扱いたいときは

$list =SampleModel::select('id', DB::raw("1 = status  as sample"))->get();

trueは1が入り、falseの場合は0が入る。

どういったときに便利か

Sampleテーブルのカラムにstatusカラムが存在し、そのstatusの中身が1,2,3の場合だった場合、1の時はこのボタンを表示したいんだけどなぁと思ったときに、カラムを足さない場合は、

blade
@foreach($list as $item)
@if((int)$item->status === 1)

といったような文を書くことがあるとします。この形は、Viewのほうには、なるべくロジックを加えたくないし、1は何を示しているのかわかりません。そのために、SampleModelに定数を定義します。

SampleModel.php
const STATUS_HAPPY = 1;
const STATUS_SAD   = 2;

bladeを修正します。

blade
@if((int)$item->status === \App\Models\SampleModel::STATUS_HAPPY)

これで、statusがHappyの時は、なにかするんだなとわかりやすくなりましたが、if分の中が長くなってしまいました。そのため、データにstatusがhappyがどうかのフラグを持たせる処理を作ってみます。

$list = SampleModel::select('id', 'status')->map(function ($row){
            $row->is_happy = ((int)$row->status === SampleModel::STATUS_HAPPY);
        });
blade
@if($item->is_happy)

先ほどより、bladeのほうの処理は、短くなりよくなりました。ただ、phpの処理のほうをis_happyだけのために、mapを回したく無く感じますので、selectでとっちゃいます。

$list = SampleModel::select('id', 'status', DB::raw(SampleModel::STATUS_HAPPY. ' = status as is_happy'))->get();

これで、statusカラムの中身とSampleModel::STATUS_HAPPYが一致した場合はis_happyは1で、一致しない場合は0が入りますので、
bladeでそのまま使えます。

blade
@if($item->is_happy)

フラグ用にmapをまわすメソッドを別で作ったりするより、いい気がする。次からつかお

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

Laravel 2つの異なるテーブルを合わせて、ページネーションをかける

Apple・Lemonテーブル

public function test(Request $request)
{
    $subQuery = Apple::from('apples as A')
        ->select('A.id', 'A.name');

    $list = Lemon::from('lemons as L')
        ->select('L.id', 'L.name')
        ->UnionAll($subQuery)
        ->orderBy('name', 'asc')
        ->paginate(10);

    return view('test',
        [
            'paginate' => $list,
        ]
    );
}

unionは、重複を削除します。UnionAllは重複を削除しません。
union allコストについて参考記事

異なるDBを見たい場合

public function test(Request $request)
{
    $subQuery = Apple::from('sample1.apples as A')
        ->select('A.id', 'A.name');

    $list = Lemon::from('sample2.lemons as L')
        ->select('L.id', 'L.name')
        ->UnionAll($subQuery)
        ->orderBy('name', 'asc')
        ->paginate(10);

    return view('test',
        [
            'paginate' => $list,
        ]
    );
}

テーブルの前にDB名.で指定すると別々のDBを見に行ってくれます。

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

PHPスクラッチでECサイト構築

カート機能 備忘録


カート機能利用の流れ

商品一覧画面 → 商品詳細画面 → 商品の個数を入力し、カートに商品を入れる

カートに必要な機能

  • クライアントが選んだ商品IDの保持
  • 商品数量の保持
  • 複数の商品をカートに格納することができること

PHPで重要となる構文

スーパーグローバル変数SESSIONの利用
→ クライアントが選んだ商品ID商品数量を保持するために多次元配列をSESSIONに保存する

解説の流れ

1.商品IDの受け渡し
2.多次元配列をSESSIONに格納
3.var_dumpを利用した出力の確認
4.カートに商品を複数保持する

1.商品一覧画面(product_list.php)→商品詳細画面(product_detail.php)における商品IDの受け渡し
product_list.php
<form action = "product_detail.php" method = "post">
 <input type = "submit" value = "詳細" />
 <input type = "hidden" name = "id" value = "<?php echo $val['id'];?>">
</form>
product_detail.php
$id = $_POST['id'];
2.多次元配列をSESSIONに格納

下記のように記述することで、商品IDと商品個数を多次元配列でSESSIONに格納することができる

product_detail.php
$_SESSION['cart'][$id]=array(
  'id'=>$_POST['id'],
  'number'=>$_POST['num'],  //$_POST['num']はクライアントが選んだ商品数量
);
3.var_dumpを利用した出力の確認
var_dump($_SESSION['cart']);

出力結果

array(1) { [1]=> array(2) { ["id"]=> string(1) "1" ["number"]=> string(1) "5" } }

この出力結果はSESSION['cart']に商品IDが1の商品が5個挿入されたことを表している

4.カートに商品を複数保持する

if文でカート内に既に商品が格納されているかどうか判断し、商品が存在すれば商品個数を足し、商品が存在しなければ、SESSIONに商品を格納するというプログラムの内容になっている

product_detail.php
if(isset($_SESSION['cart'][$id])){
    $_SESSION['cart'][$id]['number'] +=$_POST['num'];
    header('Location:product_list.php');exit();
  }else{
    $_SESSION['cart'][$id]=array(
      'id'=>$_POST['id'],
      'number'=>$_POST['num'],
    );
    header('Location:product_list.php');exit();
  }

出力結果

array(3) {
  [3]=> array(2) { ["id"]=> string(1) "3" ["number"]=> int(6) }
  [1]=> array(2) { ["id"]=> string(1) "1" ["number"]=> string(1) "3" }
  [2]=> array(2) { ["id"]=> string(1) "2" ["number"]=> string(1) "4" }
 }

カートに商品を複数格納することが可能になった。

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

Selenium で web サーバ上のファイルの中身を取得する

はじめに

Selenium を使用して, web サーバ上のファイルの中身を取得する方法です.

ダウンロードボタンを Selenium に押させて, ファイルを実際にダウンロードした後に中身を取得することも可能ですが,
その場合, ダウンロードしたファイル名を特定する必要があり, それが困難だったりします.

そこで, JavaScript の XMLHttpRequest() を使いファイルの中身1を取得する方法を, ケース毎のサンプルコードで紹介します.

ケース1: href の場合

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="https://localhost/files/hoge.csv">download</a>
</body>
</html>
main.php
$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub');

$webDriver->get('https://localhost/index.html');


$url = $webDriver->findElement(WebDriverBy::tagName('a'))->getAttribute('href');


$responses = $webDriver->executeScript(implode('', ['var xhr = new XMLHttpRequest();',
                                                    'xhr.open("GET", "' . $url . '", false);',
                                                    'xhr.overrideMimeType("text/plain; charset=Shift_JIS");',
                                                    'xhr.send();',
                                                    'return xhr.responseText;']));

var_dump($responses);


$webDriver->quit();

ケース2: form method="get" の場合

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="https://localhost/files" method="get">
    ...
    <input type="submit" value="download">
</form>
</body>
</html>
main.php
$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub');

$webDriver->get('https://localhost/index.html');


$responses = $webDriver->executeScript(implode('', ['var form = document.querySelector("form");',
                                                    'var xhr = new XMLHttpRequest();',
                                                    'xhr.open("GET", form.action + "?" + new URLSearchParams(new FormData(form)).toString(), false);', // form.method => get, form.action => 'https://localhost/files/'
                                                    'xhr.overrideMimeType("text/plain; charset=Shift_JIS");',
                                                    'xhr.send();',
                                                    'return xhr.responseText;']));

var_dump($responses);


$webDriver->quit();

ケース3: form method="post" の場合

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="https://localhost/files" method="post">
    ...
    <input type="submit" value="download">
</form>
</body>
</html>
main.php
$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub');

$webDriver->get('https://localhost/index.html');


$responses = $webDriver->executeScript(implode('', ['var form = document.querySelector("form");',
                                                    'var xhr = new XMLHttpRequest();',
                                                    'xhr.open(form.method, form.action, false);', // form.method => post, form.action => 'https://localhost/files'
                                                    'xhr.overrideMimeType("text/plain; charset=Shift_JIS");',
                                                    'xhr.send(new FormData(form));',
                                                    'return xhr.responseText;']));

var_dump($responses);


$webDriver->quit();

注意事項

スクレイピングを禁止しているサイトもありますのでご利用の際はお気をつけくださいませ.


  1. 今回, web サーバ上のファイル = CSV ファイル を想定 

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

WordPressを使ったサイト制作標準フロー

WordPressを使ったサイト制作案件が多く手がけるようになって、この1年だけで10サイト以上WordPress案件に携わってきました。
色々と試行錯誤しながらやってきたなかで、ある程度WordPressをつかったサイト制作のフローが標準化してきたのでまとめていきたいと思います。

前提事項

  • Web制作会社などでいちからサイトを制作することを想定
    (汎用テーマなどを使って簡易に構築するとかではなく、ガッツリとそのサイト専用のテーマを作成する)
  • それぞれのツールのインストール方法などは記載しない
    (Qiita内で丁寧に手順を記載している記事があるので検索してみてください)

Web制作にWordPressを導入した際の課題

  1. 環境構築が手間。
  2. 新規テーマ作成時の初期テンプレートの準備が大変。
  3. サイトの引っ越しが生じる都度、DBの書き換えが必要となる。
  4. 各操作がWeb画面のGUIベースで作業を自動化やコード化しにくい

対応策

1. 環境構築が手間。

VCCWで環境構築の手間を解決する

LAMP環境をホストOSに構築したり、VirtualBoxでゲストOSに構築したりしてWordPressをインストールしている時期もありましたが、VCCWの存在を知ってからはWordPress案件はすべてVCCWを使用しています。

VCCWを使えばコマンドひとつでWordPress開発環境をVagrant上に構築可能。
PHP_CodeSnifferやWP-CLI、WordMoveなどの便利なWordPress関連ツールもついてきて非常に便利です。

2. 新規テーマ作成時の初期テンプレートの準備が大変。

Iemotoで骨組みとなるテーマファイルを一括生成

WordPressのオリジナルテーマをつくる際は、既存のデフォルトテーマを元にコピーして作成する方法などもありますが、コード内の名前を置換したり不要な記述を削除するが手間です。

Iemotoを使用するといくつかの質問に答えるだけで、WordPressの基本的なテーマファイル構成とSassのコンパイルなどを行なうタスクランナーの設定まで自動でやってくれます。
(後述する、WP-CLIでもテーマの雛形を作成できますが、タスクランナーがついている分、Iemotoのほうが便利です。)

Use gulp? (y/N)y でGruntではなく、Gulpのほうが動作が早いのでオススメ。
タスクランナーの実行はゲストOS側でも良いですが、ホストOSから実行したほうが楽だし早いので私はホスト側から実行しています。

3. サイトの引っ越しが生じる都度、DBの書き換えが必要となる。

WordMoveでデプロイや他環境との同期を効率化

WordPressはパスをすべて絶対パスで記述するような方針のため、ローカルの内容をテスト環境へアップしたり、テスト環境の内容を本番に反映したりする際にDB書き換えが必要となり非常に手間です。

以前は自分でドメイン部分を変換して適用するようなコマンドラインツールを作成していましたが、やはり自分が困っていることは他の人も困っていて、すでに WordMoveという一般的な解決策がありました。

WordMoveを使えば、簡単にプラグイン、テーマ、DB、メディアなどを指定しそれぞれの環境と同期することが可能です。

よくあるケース

途中で参加したプロジェクトメンバーがステージング環境のデータを開発環境に同期する。
wordmove pull -e staging -t -d -u -p

DBやメディア(アップロード画像)はGitなどで管理しないことが多いと思いますが、VCCWの設定ファイルなどをGitで管理している場合、新しくプロジェクトに参加する人はリポジトリをクローンした後はVagarntを立ち上げて、WordMoveで上記のようにステージングのデータを取ってくるだけですぐに作業を開始できます。

本番環境に修正したテーマを適用する。
wordmove push -e production -t

テーマファイルの修正をステージングにアップ後に本番環境へ反映するには上記のようにコマンドひとつで簡単に実行できます。

4. 各操作がWeb画面のGUIベースで作業を自動化やコード化しにくい

WP-CLIで作業をショートカット、シェルスクリプト化など

WP-CLIを使うとWordPressに関わる様々な作業がコマンドラインから簡単に行えます。
コマンドラインから行えるということは社内wikiで手順を共有しやすく、シェルスクリプト化も容易です。

よく使うコマンド例:

# WordPressのアップデート
wp core update
# プラグインをインストールして有効化
wp plugin install classic-editor --activate
# DBバックアップ
wp db export ~/backup.sql
# DBをリセットしてからインポート
wp db reset && wp db import ~/backup.sql

パーミッションや設定の問題で管理画面からプラグイン追加や更新が出来ない場合でも、SSHでサーバー接続可能な場合はWP-CLIから対応できますし、ログインアカウントが不明な場合もWP-CLIからアカウントを追加することが出来ます。

カテゴリ情報や固定ページなど初期データが大量にある場合もWP-CLIをつかったシェルスクリプトで一括で登録できるようにすると便利です。

まとめ

  • WordPressを使ったサイト制作ではまずはVCCWを使う
  • テーマ作成はコピーではなく、Iemotoをつかって生成するのが吉
  • 各環境間のデータ連携はWordMoveを使う
  • 必要な作業はコマンドライン(WP-CLI)から行えないか検討する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】PHPからApacheのバージョンを取得する

PHP から Apache のバージョンを取得する方法についての記事です。


コード

function apacheVersion($pShell = null)
{
    $shell = (isset($pShell)) ? $pShell
                              : 'apachectl -v';

    $regex = '/Apache\/(\d{1,2}\.\d{1,2}\.\d{1,2})/';

    switch(true) {
        case (preg_match($regex, getenv('SERVER_SOFTWARE'), $matchAry)):
        case (function_exists('apache_get_version') && preg_match($regex, apache_get_version(), $matchAry)):
        case (preg_match($regex, shell_exec($shell), $matchAry)):
            return $matchAry[1];

        default:
            return false;
    }
}

使い方

$version = apacheVersion();    // string(6) "2.4.39"

//-- Apacheをソースからビルドした環境やWindows環境等で
//-- Apacheのバージョンを取得するコマンドを指定したい時
apacheVersion('C:\apache\bin\httpd.exe -v');

ソースの解説

function apacheVersion($pShell = null)
{
    //-- Apacheのバージョンを取得するためのコマンド
    $shell = (isset($pShell)) ? $pShell
                              : 'apachectl -v';

    //-- Apacheのバージョンを取得するための正規表現
    $regex = '/Apache\/(\d{1,2}\.\d{1,2}\.\d{1,2})/';

    switch(true) {
        //-- 1.環境変数からの取得を試みる
        case (preg_match($regex, getenv('SERVER_SOFTWARE'), $matchAry)):

        //-- 2.apache_get_version()での取得を試みる
        case (function_exists('apache_get_version') && preg_match($regex, apache_get_version(), $matchAry)):

        //-- 3.コマンドでの取得を試みる
        case (preg_match($regex, shell_exec($shell), $matchAry)):
            return $matchAry[1];

        //-- バージョン取得失敗
        default:
            return false;
    }
}

なぜ三段構えになっているか

1.環境変数からの取得を試みる

この方法では、例えば PHP を CLI 版で動作させている時は取得できません。('SERVER_SOFTWARE' は定義されません。)

また、この方法で取得できるのは、Apache の ServerTokens の設定が、Full OS Minimal の時のみです。

公開環境では Prod を設定し、HTTP ヘッダに Apache のバージョンが公開されないようにするのが普通です。

ServerTokens Prod

この設定の場合、getenv('SERVER_SOFTWARE') には 'Apache' という文字列しか入りません。

2.apache_get_version()での取得を試みる

基本的に 1 と同じですが、せっかく用意されている関数ですので…。

3.コマンドでの取得を試みる

…というわけで、最終手段として Apache に直接聞いちゃえ!って事です。

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

Heroku logs に var_dump()

Heroku上のPHPアプリでprintデバッグする場合
error_log()で出力するのがお手軽

https://devcenter.heroku.com/articles/php-logging

問題点

けれども、配列の中身が見たくて

 $a = array(1, 2, array("a", "b", "c"));
 error_log('$a:');
 var_dump($a);

 error_log('$a:' . var_dump($a));

としても
↓のようになって、配列の中身はHeroku logsに出力されない

$a:

原因

var_dump()は返り値が無いので、error_log()に出力内容が渡されていないから

対策

var_dumpの出力を変数に格納し、その変数をerror_log()に渡す

// var_dumpを文字列に変換する関数を作成
function get_str_var_dump($var){
    ob_start(); // バッファリングON
    var_dump($var);
    $bar = ob_get_contents(); // バッファの内容を変数に格納
    ob_end_clean(); // バッファを消去してバッファリングOFF
    return $bar;
}

 $a = array(1, 2, array("a", "b", "c"));

 // 配列$aをget_str_var_dump()で文字列に変換してからerorr_log()に渡す
 error_log('$a:' . get_str_var_dump($a));

↓ のように出力できた

$a:array(3) {  
[0]=>  
int(1)  
[1]=>  
int(2)
[2]=>
array(3) {
    [0]=>
    string(1) "a
    [1]=>
    string(1) "b
    [2]=>
    string(1) "c
    }
}

参考

https://macocci7.exblog.jp/18510693/
https://www.php.net/manual/ja/function.var-dump.php
https://www.php.net/manual/ja/function.ob-start.php

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

php-master-changes 2019-05-29

今日は JIT で optimization_level を考慮するようにする修正、多数のブロックがある関数を JIT コンパイルしないようにする修正、FFI の特定テストでの JIT 無効化、travis での JIT 有効化、opcache のメモリリークの修正と func_info の更新、幾つかの局所最適化、run-tests.php で実行許可ビットが外れていたことの修正、マクロの定義間違いの修正、無効化指定の関数で ZEND_ACC_HAS_RETURN_TYPE を保持しないようにする修正、不使用変数の削除、DatePeriod のプロパティアクセスのテスト追加と get_property_ptr_ptr ハンドラの追加、set オブジェクトハンドラの削除、API バージョンの更新、escapeshellarg() と escapeshellcmd() へ NUL 文字含んだ引数を与えた際 TypeError を投げるようにする修正があった!

2019-05-29

nikic: Respect optimization_level when running JIT inference

dstogov: Replace ZVAL_COPY() and ZVAL_COPY_VALUE() for IS_OBJECT by cheaper macros

duncan3dc: Restore the execute bit for run-tests.php

dstogov: Avoid cold code duplication

weltling: These macros should not expect any argument

weltling: Fix conditional compilation

krakjoe: disabled functions must not have return type

carusogabriel: Remove unused variable

dstogov: zend_do_fcall_overloaded() doesn't have to be exported

duncan3dc: Add tests for DatePeriod properties

nikic: Specify get_property_ptr_ptr handler for DatePeriod

nikic: Remove set() object handler

derickr: Bump API numbers

dstogov: Mark "cold" functions

nikic: Don't JIT functions with many blocks

nikic: Disable JIT for ext/ffi/tests/200.phpt

dstogov: zend_assign_to_variable() optimization

nikic: Enable JIT in Travis build

nikic: Update more func info information for ext/standard

nikic: SCCP: Fix leak when determining TYPE_CHECK from type info

nikic: escapeshellarg/escapeshellcmd: Throw TypeError instead of E_ERROR

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

laravelでURLを作成する便利な関数(route()・action())いいなーと思っていたら、はまった話

route()・action()はどちらもURLを作成するときにすごく便利です。

route('sample.index');  //こちらは、routes/web.phpでnameを指定する必要があります。
action('SampleController@index');

どちらも同様のURLを作成してくれます。第二引数で、パラメータを渡すことができます。
Bladeのほうで、利用するときは、旧型の書き方と比べると可読性が全然違います。

旧型

{{Form::open(['url' => 'http://sample/index?id='. $data['id']. '&name='. $data['name'])}}

route()・action()利用時

url属性を使用した場合

{{Form::open(['url' => route('sample.index', [$data])])}}
{{Form::open(['url' => action('SampleController@index', [$data])])}}

LaravelのBladeには、routeとactionの属性が用意されています(パラメータがない場合は利用する)
(こちらの形でも、input hiddenを使えば、パラメータを渡すことは可能ですが、個人的には、すべてurl属性を使って統一するほうが見やすい気がします。)

{{Form::open(['route' => 'sample.index'])}}
{{Form::open(['action' => 'SampleController@index])}}

Formにroute関数を使用して、特定の場合、思った挙動をしなくなる話

当然と言われれば、それまでの話なのですが、URLにidを埋め込んで、そのidに、/を入れたときに、思った通りの挙動をしません。例をあげてみます。

routes/web.php
Route::get('/test/{id}/index', 'TestController@index')->name('test.index');

idごとのデータのindexメソッドの処理がはしる想定のルートです。

TestController.php
public function index(Request $request, $id)
    {
        dd($id);
    }

引数で、idを受け取ります。それでは、bladeを編集します。

test.blade.php
{{Form::open(['url' => route('test.index', ['id' => '1']), 'method' => 'get'])}}
{{Form::submit('送信')}}
{{Form::close()}}

まずは、idに1をいれて、ボタンを押下してみます。

Screenshot_8.png

想定通り、1が表示されました。それでは、/をいれてみます。

test.blade.php
{{Form::open(['url' => route('test.index', ['id' => '/']), 'method' => 'get'])}}
{{Form::submit('送信')}}
{{Form::close()}}

/を入れて試してみます。404エラーがでました。URLをみると、///となっており、URLがおかしくなっています。

Screenshot_10.png

対処法

本来、/はURLの区切り、?はパラメータを渡すときにURLの大切な役を担っています。それを、URLのパラメータとして使用するためには、エンコードする必要があります。

routes/web.php
Route::get('/test/{id}/index', 'TestController@index')->name('test.index')->where('id', '(.*)');

Screenshot_11.png

できました。?をパラメータに使いたい場合は、エンコードされていたらそのまま使えますが、されていなかったら、urlencode('?')などでエンコードしてから、パラメータとして使用しましょう。

まとめ

パラメータに/や?を使わないようするのが一番いいと思います

参考にさせていただいた記事-スラッシュのエンコード

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