20200317のPHPに関する記事は20件です。

Wordpressでプラグインを使わずにパンくずリストを実装する方法。

一番簡単に実装するならYoastのパンくずリストを使おう。SEO対策ですでに入れてると思うのでついでに使うのが吉。

Yoast入れてるなら次のコードでパンくずリストを設置できる。

<?php
if ( function_exists('yoast_breadcrumb') ) {
  yoast_breadcrumb( '<div id="breadcrumbs">','</div>' );
}
?>

プラグイン使わずパンくずリストを実装する

以下のコードをfunctions.phpに追記する。

パラメータにはhtmlタグ用のclassを指定する配列を入れている。

if ( ! function_exists( 'my_custom_breadcrumb' ) ) {
    /**
     * Desiplay Breadcrumb
     * $class = ['ol' => 'ol-class','li' => 'li-class','a' => 'a-class','bar' => 'bar-class',];
    */
    function my_custom_breadcrumb($class) {
        global $post;
        if ( !is_front_page() ) {
            echo '<ol class="'.$class['ol'].'" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb">';

            echo '<li class="'.$class['li'].'" itemprop="title"><a class="'.$class['a'].'" href="'.home_url().'" itemprop="url">Home</a></li>';
            echo '<li class="'.$class['bar'].'">/</li>';

            if ( is_home() ) {
                $title = wp_title('',false,'');
                echo '<li class="'.$class['li'].'" itemprop="title">'.$title.'</li>';
            } elseif ( is_category() ) {
                $cat_id = get_query_var('cat');
                $cat_link = get_category_link( $cat_id );
                $cat_name = get_cat_name( $cat_id );

                echo '<li class="'.$class['li'].'" itemprop="title">';
                echo '<a class="'.$class['a'].'" href="'.$cat_link.'">'.$cat_name.'</a></li>';
            } elseif ( is_single() ) {
                $category = get_the_category();
                $cat_link = get_category_link( $category[0]->term_id );
                $cat_name = $category[0]->cat_name;

                echo '<li class="'.$class['li'].'" itemprop="title">';
                echo '<a class="'.$class['a'].'" href="'.$cat_link.'">'.$cat_name.'</a></li>';
                echo '<li class="'.$class['bar'].'">/</li>';
                echo '</li><li class="'.$class['li'].'" itemprop="title">';
                the_title();
                echo '</li>';
            } elseif ( is_page() ) {
                if($post->post_parent){
                    $anc = get_post_ancestors( $post->ID );
                    $title = get_the_title();
                    foreach ( $anc as $ancestor ) {
                        $output = '<li class="'.$class['li'].'" itemprop="title"><a class="'.$class['a'].'" href="'.get_permalink($ancestor).'" title="'.get_the_title($ancestor).'" itemprop="url">'.get_the_title($ancestor).'</a></li>';
                    }
                    echo $output;
                    echo '<li class="'.$class['li'].'" title="'.$title.'" itemprop="title"> '.$title.'</li>';
                } else {
                    echo '<li class="'.$class['li'].'">'.get_the_title().'</li>';
                }
            } elseif ( is_tag() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                single_tag_title();
                echo'</li>';
            } elseif ( is_day() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                echo get_the_date();
                echo'</li>';
            } elseif ( is_month() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                echo get_the_date('Y - M');
                echo'</li>';
            } elseif ( is_year() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                echo get_the_date('Y');
                echo'</li>';
            } elseif ( is_author() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                the_author_meta('display_name');
                echo'</li>';
            } elseif ( is_search() ) {
                echo'<li class="'.$class['li'].'" itemprop="title">';
                the_search_query();
                echo'</li>';
            }

            echo '</ol>';
        }
    }
}

パンくずリストをテンプレートで表示

一覧ページや個別記事ページの表示したい位置に設置。

htmlを出力するだけなので、navタグなどで囲んでもOK。

$classList = [
    'ol' => 'ol-class',
    'li' => 'li-class',
    'a' => 'a-class',
    'bar' => 'bar-class',
];
my_custom_breadcrumb($classList);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel DBクラスを利用(SQL文を使ってDB操作)

使い方

use Illuminate\Support\Facades\DB;
# 例  DB::select('select * from テーブル名')
$items = DB::select('select * from people'); #この例ではpeopleテーブルを全て呼び出し

パラメータ結合

# 例  id検索
$param = ['id' => $request->id];
$items = DB::select('select * from people where id = :id', $param);

インサート

DB::insert(クエリ文, パラメータ配列);

viewにidを渡す

下記のようにするとurlにidを含めることができる

HogeController.php
  public function edit(Request $request){
    $params = ['id' => $request->id];
    # select文でurlで指定したidデータを取得(今回はエラー処理していないのでidがなければコケる)
    $item = DB::select('select * from people where id = :id', $params);
    return view('hello.edit', ['form' => $item[0]]);
  }
web.php
Route::get('/hoge/edit/{id}', 'HogeController@edit');

コントローラで['form' => $item[0]をviewに渡しているので$formが使える

view.blade.php
  <table>
    <form action="/hoge/edit" method="post">
      {{ csrf_field() }}
      <input type="hidden" name="id" value="{{ $form->id }}">
      <tr>
        <th>name: </th>
        <td><input type="text" name="name" value="{{ $form->name }}"></td>
      </tr>
      <tr>
        <th>email: </th>
        <td><input type="text" name="email" value="{{ $form->email }}"></td>
      </tr>
      <tr>
        <th>age: </th>
        <td><input type="text" name="age" value="{{ $form->age }}"></td>
      </tr>
      <tr>
        <th></th>
        <td><input type="submit" value="送信"></td>
      </tr>
    </form>
  </table>

update

HogeController.php
  public function update(Request $request){
    $params = [
      'id' => $request->id,
      'name' => $request->name,
      'email' => $request->email,
      'age' => $request->age
    ];
    DB::update('update people set name = :name, email = :email, age = :age where id = :id', $params);
    return redirect('/hoge');
  }
web.php
Route::post('/hoge/edit', 'HelloController@update');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPによるデータベース操作

 
コメント 2020-03-17 214706.png

tableの中から選んだ職種を表示させます

変数を設定します

    $job = '';//selectの中の値
    $host = '';//データベースのホスト名またはIPアドレス
    $username = '';//ユーザー名
    $passwd = '';//パスワード
    $dbname = '';//データベース名
    //データベースの情報を渡して接続する値を$linkに代入する
    $link = mysqli_connect($host, $username, $passwd, $dbname);

myspli_connectの "()" 内がデータベースの接続に必要な情報で、これを
渡すことで接続できます。
返り値はリンクIDで、接続したデータベースを特定するIDとなっています。

  • emp_idは社員番号
  • emp_nameは名前
  • emp_tableはtable名
    if($link) {
        //文字化け防止
        mysqli_set_charset($link, 'utf8');
        //最初か全部選択を選んだ時
        if($job === '' || $job === '全部選択') {
    //実行する内容を$queryに代入する 
            $query = 'SELECT emp_id, emp_name, job, age FROM emp_table 
                    ORDER BY emp_id';
    //他の職種を選んだ時
        } else {
        //実行する内容を$queryに代入する    
     $query = 'SELECT emp_id, emp_name, job, age FROM emp_table 
                    WHERE job = "'.$job.'" ORDER BY emp_id';            
        }
//社員番号順にtableから3つのカラムを取得する
$query = 'SELECT emp_id, emp_name, job, age FROM emp_table 
         ORDER BY emp_id ASC';

ORDER BYは順序を決めます。

  • ASC・・・昇順
  • DESC・・・降順
//社員番号順にtableから3つのカラムの中から選んだ職種を取得
$query = 'SELECT emp_id, emp_name, job, age FROM emp_table 
          WHERE job = "'.$job.'" ORDER BY emp_id';  

WHERE 条件を決めます。
変数は文字列の中ではそのまま使えないので、このような記述となっています。
ちなみにこちらのほうが分かりやすいですね。

$query = "SELECT emp_id, emp_name, job, age FROM emp_table 
          WHERE job = '$job' ORDER BY emp_id";

クエリを実行する

        $result = mysqli_query($link, $query);
        //1行ずつ配列を取得する
        while($row = mysqli_fetch_array($result)) {
        //取得したデータを配列にする    
     $goods_data[] = $row;           
        }

mysqli_query()は、SELECTだった場合は選択されたデータが返ってくる。
UPDATE,DELETEだった場合はtrue,falseを返す。

mysqli_fetch_array()は、成功すると行の配列を、行がなくなればnull,
失敗するとfalseを返す。

        mysqli_free_result($result);
        mysqli_close($link);
     

mysqli_free_result()は、保存されているメモリを開放する
mysqli_query()で取得したデータを利用した場合すぐに開放しましょう。
mysqli_close()はデータベースから切断します。

全てのコード

<?php
    $job = '';
    $host = '';
    $username = '';
    $passwd = '';
    $dbname = '';
    //データベースの情報を渡して接続する値を$linkに代入する
    $link = mysqli_connect($host, $username, $passwd, $dbname);

    if(isset($_GET['job'])=== true) {
        $job = $_GET['job'];        
    }
    if($link) {
        //文字化け防止
        mysqli_set_charset($link, 'utf8');
        if($job === '' || $job === '全部選択') {
            $query = 'SELECT emp_id, emp_name, job, age FROM emp_table 
                    ORDER BY emp_id';
        } else {
            $query = "SELECT emp_id, emp_name, job, age FROM emp_table 
                    WHERE job = '$job' ORDER BY emp_id";

        }
        //クエリを実行
        $result = mysqli_query($link, $query);
        //1行ずつ配列を取得する
        while($row = mysqli_fetch_array($result)) {
            $goods_data[] = $row;
            // var_dump($row);
        }
        mysqli_free_result($result);
        mysqli_close($link);
    }
?>
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8">
   <title>サンプル</title>
   <style type="text/css">
       table, td, th {
           border: solid black 1px;
       }
       table {
           width: 200px;
       }
   </style>
</head>
<body>
    <p>表示する職種を選択してください。</p>
   <form>
       <select name="job">
           <option>全部選択</option>
           <option>manager</option>
           <option>analyst</option>
           <option>clerk</option>           
       </select>
       <input type="submit" value="表示">
   </form>
   <table>
       <tr>
           <th>社員番号</th>
           <th>名前</th>
           <th>職種</th>
           <th>年齢</th>
       </tr>
<?php
foreach ($goods_data as $value) {
?>
        <tr>
            <td><?php print htmlspecialchars($value['emp_id'], ENT_QUOTES, 'UTF-8'); ?></td>
            <td><?php print htmlspecialchars($value['emp_name'], ENT_QUOTES, 'UTF-8'); ?></td>
            <td><?php print htmlspecialchars($value['job'], ENT_QUOTES, 'UTF-8'); ?></td>
            <td><?php print htmlspecialchars($value['age'], ENT_QUOTES, 'UTF-8'); ?></td>
        </tr>
<?php
}
?>
   </table>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカル環境/Dockerコンテナ間でAPIを叩く�

はじめに

Laravelのプロジェクト間でAPI通信をしたい、ということでGazzleを使って実装してみました。
まずはローカルで動作確認をしようとしたのですが、エラーが出てしばらく詰まっていたので解決法を残しておきます。

最初に書いたコード

APIを叩く側のコードです。

$client = new Client();
$url = 'http://localhost:8080/api/user/update';
$data = [
    ‘id’ => ‘1111’,
    ‘name’ => ‘鈴木’
];
$response = $client->request('POST', $url, ['form_params' => $data]);
echo $response->getBody()->getContents();

修正が必要なところ

APIのURLを指定しているんだけどその書き方がまずかったみたいです。
API側にはlocalhost:8080で接続できていたのでそのまま書いてました。
修正後のコードがこちら。

$url = 'http://host.docker.internal:8080/api/user/update';

localhostの箇所をhost.docker.internalと指定することでコンテナ間でAPIを叩くことができます。
host.docker.internalはコンテナからホスト(macOS)に接続するときに使うみたいです。

以下、公式ドキュメントから引用

I WANT TO CONNECT FROM A CONTAINER TO A SERVICE ON THE HOST
The host has a changing IP address (or none if you have no network access). From 18.03 onwards our recommendation is to connect to the special DNS name host.docker.internal, which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Mac.

補足

やることはあまりないかもしれないですが、APIを実装したプロジェクト内でそのAPIを使う場合は下記のURLでいけました。

$url = 'http://localhost:80/api/user/update';
// 80はコンテナ側のポート番号
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ド文系でも分かる】PHPでFizzBuzz

なぜ今更FizzBuzz?

僕みたいなゴリゴリの文系卒の駆け出しエンジニアにとってサーバースクリプトに触れてからぶち当たる最初の難関がFizzBuzz問題ではないでしょうか。
ようやく理解できるレベルまで到達したので、文系の皆さんにもわかりやすいようにまとめてみたいと思います。

そもそもFizzBuzzとは

そもそもコードを書く前に誰もが思うはず。
FizzBuzzってなんだ!!と。
FizzBuzzとは、過去に流行った「世界のナ○アツ」的なやつみたいです。

プレイヤーは円状に座る。最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3の倍数では「Fizz」(Bizz Buzzの場合は「Bizz」)、5の倍数では「Buzz」、両者の公倍数(すなわち15の倍数)では「Fizz Buzz」(Bizz Buzzの場合は「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

引用元:Fizz Buzz

つまり、1から自然数をカウントしていき、3の倍数と5の倍数、その両方の公倍数のとき(15の倍数)に任意の文字をechoさせてあげればよろしい。
それ以外のときは単に数字をechoさせるロジックで実装すればよろしい。

実践

fizzbuzz.php
<?php

for ($i = 1; $i <= 100; $i++) {
    if ($i % 15 === 0) {
        echo 'FizzBuzz' . "\n";
        continue;
    } elseif ($i % 3 === 0) {
        echo 'fizz' . "\n";
        continue;
    } elseif ($i % 5 === 0) {
        echo 'buzz' . "\n";
        continue;
    }
    echo $i . "\n";
}

ざっくりこんな感じ。
(あくまで一例です。他にもっと簡単な記述あるよ!などあればご教授いただけると幸いです)

初見はちんぷんかんぷんと思うので、要点を順番に解説していきます。

①数字をひたすらカウントさせるコードを書く

fizzbuzz.php
for ($i = 1; $i <= 100; $i++) { //変数iが100になるまでechoを実行する
    echo $i . "\n"; //数字が呼び出される(\nは見やすいように改行処理)
}

$i = 1とは初期値1と定義。
$i++とは$i = $i+1の省略形でforが一回発動する毎に変数iに1を足してあげることです。

つまり、ここでは100になるまで変数$iを順にechoする。というコードが書かれています。

②3の倍数と5の倍数の時に処理を分岐させる

fizzbuzz.php
for ($i = 1; $i <= 100; $i++) { //変数iが100になるまで以下の処理をループ
  if ($i % 15 === 0) { //15の倍数のとき
        echo 'FizzBuzz' . "\n"; 
    } elseif ($i % 3 === 0) { //3の倍数のとき
        echo 'fizz' . "\n";
    } elseif ($i % 5 === 0) { //5の倍数のとき
        echo 'buzz' . "\n";
   echo $i . "\n"; //条件以外の数字が呼び出される
}

3の倍数の処理から書きがちですが、プログラミングは上から処理が実行されるのでFizzBuzzの処理(15の倍数)は一番最初の条件として記述しましょう。

イメージとしては、
15の倍数のときにFizzBuzz出力→3の倍数のときにfizz出力→5の倍数のときにbuzz出力→
それ以外はカウントしたときの数字を出力。

条件内の
$i % 15 === 0は変数$iを15で割った数が0ということ。
つまり$i % ○ === 0とするころで、○の倍数が取れるということです。

===は厳密に同じという意味。
条件式のイコールは曖昧な一致の==だとエラーのリスクが上がるので基本===で書きましょう。

③分岐に入ったらループ処理に戻してあげる

fizzbuzz.php
for ($i = 1; $i <= 100; $i++) { //変数iが100になるまで以下の処理をループ
  if ($i % 15 === 0) { //15の倍数のとき
        echo 'FizzBuzz' . "\n"; 
        continue; //ここでループ処理に復帰させる
    } elseif ($i % 3 === 0) { //3の倍数のとき
        echo 'fizz' . "\n";
        continue; //ここでループ処理に復帰させる
    } elseif ($i % 5 === 0) { //5の倍数のとき
        echo 'buzz' . "\n";
        continue; //ここでループ処理に復帰させる
   echo $i . "\n"; //条件以外の数字が呼び出される
}

②までの記述だと条件処理が行われた後に自然数を呼び出す処理も行われてしまうので、
各条件処理の最後にcontinueを使いループ処理をforに戻してあげましょう。

おわりに

プログラミングっぽい処理の代表格のFizzBuzzは基本中の基本と言われています。
ロジックで説明されるとよくわからないFizzBuzz問題ですがコードで見ると意外と大したことないかと思います。

他にも解き方はたくさんあるかと思いますがこのやり方が僕にとって一番わかりやすくシンプルだったので、駆け出しエンジニアの皆さんに役立てられるように共有させていただきます。

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

【PHP】Google Ads APIをサンプルプログラム無しで使いたい!!

はじめに

Google Ads APIは、Google広告の最新のプログラマティック インターフェースである。
概要はこちら

前身はGoogleの広告サービス=AdWordsのAPI(Adwords API)で、
2018年にAdWordsを「Google広告」に改称したことに伴い、
新APIとして登場した。らしい。いつの間に・・・

改称した詳しい経緯はこちら

最新ニュースなどを全然チェックしていなかったので
いきなり「AdwordsAPIが使えなくなる」って情報を見て「うそお!?」とググったら
AdwordsAPIのサイトから日本語版が消えてた・・・あからさまだよ、Googleさん。。

当方、現在AdwordsAPIを使用してレポートデータを取得したシステムを構築している。
今後AdwordsAPIがいつ使えなくなるか分からないので、大急ぎで引越し準備を始めた。

Google Ads APIに関しては、ドキュメントがしっかり準備されているせいか、
検索しても情報が極端に少なかった。
何度も心折れながら試行錯誤した成果を、ここに記録しておくことにする。

ちなみにGoogle Ads APIは2020年3月現在β版。
正式版はいつリリースされるのよ。。。

前提

・AdwordsAPIを既に利用している場合、認証情報はGoogleAdsAPIに引き継がれるため
 設定情報を変更する必要はない。
 ⇒ただし、そのまま使えるわけではなく、新たにGoogleAdsAPIを利用するためには
  Google広告の「ツールと設定」⇒「APIセンター」で、規約に同意する必要がある

・AdwordsAPIもAdsAPIも、サンプルプログラム(「クライアントライブラリ」)が用意されている。
 Java/.NET/PHP/Python/Ruby/Perlと幅広くカバーされている。さすが天下のGoogleさん。

・AdwordsAPIもAdsAPIも、サンプルプログラムはcomposerありきで作られている。
 composerについてはこちら
 ※ドキュメントが英語だったので翻訳機能使ったら
  「composer」が「作曲家」と変換されて「OH…ナニソレ」と心が折れた昼下がり

・当方、事情があってcomposerをインストールできない環境。
 何とかサンプルを使用せずに直接APIを叩きたい。

★注意!!★
公式はあくまで
「クライアントライブラリを使用してリクエストを送信することを強くおすすめします」
と言っているので、
もしここのやり方を試す方は、自己責任でお願いします!!

データ取得:oAuth2認証

最初にリフレッシュトークンとクライアントID、クライアントシークレットを使ってoAuth2認証を行う。
認証に成功するとアクセストークンが取得できる。

//認証用URL
$oAuth2_url = "https://oauth2.googleapis.com/token";

//リフレッシュトークン
$refresh_token = "REFRESH_TOKEN";

//クライアントID
$client_id = "CLIENT_ID";

//クライアントシークレット
$client_secret = "CLIENT_SECRET";


//curl START
$curl = curl_init();

//OPTIONをセット
curl_setopt_array($curl, [
    CURLOPT_URL => $oAuth2_url,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_POSTFIELDS => http_build_query([
        "refresh_token" => $refresh_token,
        "client_id" => $client_id,
        "client_secret" => $client_secret,
        "grant_type" => "refresh_token",
    ]),
]);

//curl EXEC(文字列で取得)
$resp =  curl_exec($curl);

//エラーハンドリング用
$_errno = curl_errno($curl);

//curl END
curl_close($curl);

//エラーハンドリング
if ($_errno !== CURLE_OK) {
    //エラー処理
}

//エラーでなければjsonを連想配列化        
$jsonresp = json_decode($resp, true);

//アクセストークンを取得
$access_token = $jsonresp["access_token"];

データ取得:レポートデータ取得

アクセストークンを取得できたら、いよいよ肝心のレポートデータを取得する。

oAuth2認証で取得したアクセストークンと、
API取得時に付与されるディベロッパートークン、MCCのログインカスタマーIDをヘッダーにセットする。

※ログインカスタマーIDは、Google広告の右上のアカウント名上に表示されている番号のこと。

//ディベロッパートークン
$developer_token = "DEVELOPER_TOKEN";

//ログインカスタマーID
$login_customer_id = "LOGIN_CUSTOMER_ID";   //XXX-XXX-XXXX形式なのでハイフンなし10桁で!


//ヘッダー情報をセット
$header = [
    "Content-Type: application/json", 
    "Accept: application/json",
    "Authorization: Bearer ".$access_token, 
    "developer-token: ".$developer_token,
    "login-customer-id: ".$login_customer_id,
];

次に、必要なレポートデータを設定する。

レポートを取るためのエンドポイントはhttps://googleads.googleapis.comで、
これにさらにバージョン、処理によって違う値を付与する。

今回はレポートデータ取得なので、googleAds:searchというリソースを使用する。

$url = "https://googleads.googleapis.com/v1/customers/{CLIENT_ID}/googleAds:search";

※{CLIENT_ID}にはデータ取得したいクライアントID(MCCのでも違うものでもOK)をセットする。
※クライアントIDはlogin-customer-idと同じくXXX-XXX-XXXX形式のもの。ハイフンなし10桁。

AdwordsAPIと同じくGoogleAdsAPIはクエリ言語でデータ取得ができる。
ただし新仕様はわかりやすいようで非常に!分かりにくい。
ドキュメントを一生懸命解読しないと理解できない・・・。

今回は広告グループのレポートデータを取得するクエリ言語を例として示す。
個人的にはこういう情報が欲しかったのよ。。

/**
*  GoogleAdsに渡すクエリ言語の項目[SELECT]
*/
private static $SELECT = [
     "ad_group.resource_name"
     ,"ad_group.id"
     ,"ad_group.name"
     ,"ad_group.status"
     ,"ad_group.campaign"
     ,"ad_group.type"
     ,"ad_group.cpc_bid_micros"
     ,"ad_group.cpm_bid_micros"
     ,"ad_group.cpv_bid_micros"
     ,"ad_group.base_ad_group"
     ,"ad_group.target_cpa_micros"
     ,"ad_group.effective_target_cpa_micros"
     ,"ad_group.effective_target_cpa_source"    //adgroup

    //以下のsegments/campaign/metricsは、どのフィールドからも参照できる項目ぽい。

     ,"segments.date"

     ,"campaign.id"

     ,"metrics.all_conversions"
     ,"metrics.average_cost"
     ,"metrics.average_cpc"
     ,"metrics.average_cpm"
     ,"metrics.clicks"
     ,"metrics.conversions"
     ,"metrics.cost_micros"
     ,"metrics.cost_per_conversion"
     ,"metrics.cross_device_conversions"
     ,"metrics.ctr"
     ,"metrics.engagements"
     ,"metrics.impressions"
     ,"metrics.phone_impressions"
     ,"metrics.search_impression_share"
     ,"metrics.value_per_all_conversions"
     ,"metrics.all_conversions_value"
     ,"metrics.conversions_from_interactions_rate"
     ,"metrics.conversions_value"
     ,"metrics.search_absolute_top_impression_share"
     ,"metrics.gmail_forwards"
     ,"metrics.gmail_saves"
     ,"metrics.gmail_secondary_clicks"
     ,"metrics.search_top_impression_share"
     ,"metrics.top_impression_percentage"
     ,"metrics.absolute_top_impression_percentage"
     ,"metrics.current_model_attributed_conversions"
     ,"metrics.current_model_attributed_conversions_value"

];

/**
*  GoogleAdsに渡すクエリ言語のフィールド[FROM]
*/
const FROM = "ad_group";

/**
*  GoogleAdsに渡すクエリ言語のWHERE条件:期間
*  今日とか昨日とか1ヶ月とか指定はいろいろできる。
*/
const WHERE_PERIOD = "segments.date BETWEEN 'YYYYMMDD' AND 'YYYYMMDD'";

/**
*  GoogleAdsに渡すクエリ言語のWHERE条件:キャンペーンID
*  ※これGoogle広告の画面上は見えないので探すの大変
*/
const WHERE_CAMPID = "campaign.id = 'XXXXXXXX'";

//クエリをセット:SQL文みたいな形式
$_query_str = "SELECT ".implode(",", self::$SELECT)." FROM ".self::FROM." WHERE ".self::WHERE_PERIOD." AND ".self::WHERE_CAMPID;

//GoogleAdsに渡すために配列化(実際にはjsonで渡す)
$query = ["query" => $_query_str];

クエリをセットしたら、いよいよレポートデータを要求!

//curl START
$curl = curl_init();

//OPTIONをセット
curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => $header,
    CURLOPT_POSTFIELDS => json_encode($query),  //ここにクエリをセット
]);

//curl EXEC(文字列で取得)
$resp = curl_exec($curl);

//エラーハンドリング用
$_errno = curl_errno($curl);

    //curl END
curl_close($curl);

//エラーハンドリング
if ($_errno !== CURLE_OK) {
    //エラー処理
}

//エラーでなければjsonを連想配列化        
$result = json_decode($resp, true); 

//取得されたデータはフィールドごとに格納されている
for ($i = 0; $i < count($result); $i++) {
    $_line = $result[$i];   //一行データ
    if (!is_array($_line)) continue;

    //日付:フィールドsegmentsのデータ
    $_date = $_line["segments"]["date"];

    //キャンペーンID:フィールドcampaignのデータ
    $_campid = $_line["campaign"]["id"];

    //広告グループID:フィールドadGroupのデータ
    $_adgroupid = $_line["adGroup"]["id"];
    //キャンペーン名
    $_campname = $_line["adGroup"]["name"];

    //表示回数:フィールドmetricsのデータ
    $_viewcnt = $_line["metrics"]["impressions"];
}

取れた!取れたよー!!

Googleさんの壁は厚く、oAuth2認証のためにドアを何度も何度も叩いてもスルーされまくり、
ようやく開けてくれたと思ったら不親切なエラーメッセージばっかり返ってきたりと
ショボーンと心折れて帰宅した日々が続いたもので、取れたときは嬉しくて仕事中だったけど叫んだな。。。

何度も言いますが、あくまでもGoogleAdsAPIはサンプルプログラムを利用したリクエストがベスト(推奨)です。

ただ、同じように苦しんでいる人の参考に少しでもなればいいと思います。

おしまい。

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

Laravelでよく使うコマンド集

概要

ここでは、Laravelの初心者向けによく使うコマンドの説明をする。
基本的にはphp artisan なんとかーが多い。

Laravelで使用するコマンド集(よく使うものだけ)

composer dump-autoload

composer dump-autoload

自身で作成したクラスのファイルをフレームワークに認識させる。
これを行うことでrequire('ファイル名')require_once('ファイル名')が不要になる。
詳しくは、ここをチェック。

php artisan migrate

php artisan migrate

このコマンド1発で、Laravelで使用する全テーブルのCREATE文が実行される感じ。
対象のテーブル定義は、php artisan make:migrationで作成する。

php artisan db:seed

php artisan db:seed

このコマンド1発で、空っぽのテーブルに初期データを入れられる。
初期データの定義は、php artisan make:seederで作成する。

php artisan make:migration

php artisan make:migration [テーブル名]

テーブル作成用のクラスを作成する。
DB:insertなどを使用してCREATE文となる基を記述する。
Laravelは基本的にSQLを書くことはない。
詳しくは、https://readouble.com/laravel/6.x/ja/migrations.htmlが参考になる。

php artisan make:seeder

php artisan make:seeder [テーブル名(アッパーキャメル記法)]Seeder
↓例(テーブル名「m_user_types」の場合)↓
php artisan make:seeder MUserTypesSeeder

初期データのINSERTとなるレコードを記述する。
詳しくは、https://readouble.com/laravel/6.x/ja/seeding.htmlが参考になる。
これをやると、マスターデータなどコマンド1発で全て入る。
環境セットアップに便利!

shutdown

shutdown -s -t [秒数]

「お疲れ様でした。お先に失礼します。」って言う直前によく打つコマンド
指定した秒数が経過すると、自動でシャットダウンされます。


あとは、気が向いたらまた続きを書きます。
コントローラや他のことについて。。。

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

composerコマンドメモ

composerでコマンド触るときによく忘れるのでメモ

Composer自体のバージョンを確認

composer -v

Composerによってインストール済みのパッケージ一覧を表示

composer show

インストールされているパッケージが、ローカルで変更していないか(変更があったパッケージをリストアップ)

composer status

packageの更新を行う際に、何が更新されるか更新対象を事前にチェックする

composer update --dry-run
※dry-runオプションをつけると、何が更新されるかを確認するだけで実際の変更は行われない。

オプション

オプションで-vvvをつけると詳細のログが確認できる

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

LaravelのQueryBuilderのfirst()で結果が無かった時の処理と空判定

はじめに: LaravelのQuerybuilderのfirst()がどんな動きをしているか確認

書きの記事を参考にしてLaravelのQuerybuilderのfirst()がどんな動きをしているか確認します。
QueryBuilderのfirst()の動きを調べてみた

結果が無い場合の返り値

結果が無かったら
5.1ではnull
5.3から collect([])が返ってくるようです

バージョンによって少し異なるようですがLaraevl6.4でlogger()で吐かせたところ
Objectの{}が返却されていました。

isEmpty()とかempty()で判定したい

colectionでもarrayでも無いのでできません。
さえ、どうしましょう。。。

if (!$res->count()) {
  // coentが0の場合の処理
}

今回はこんな感じにしました。
時間が無いので検証などできませんでしたが、今度時間がある時にもう少し理解を深めたいと思います。

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

AWS-SDKを使いたくてAPIを叩いたらエラーが出た。

概要

aws-sdkを使用してRoute53の操作をしたかった。

エラー内容

Error executing "ListResourceRecordSets" on "https://route53.amazonaws.com/2013-04-01/hostedzone/XXXXXXXX/rrset"; AWS HTTP error: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)

原因

http://nanoappli.com/blog/archives/7992

このエラーは、SSL暗号化されているhttpsのサイトにアクセスしようとした際にそのサーバが信頼できるか否かの証明書(ca証明書)が取得できなかった時に発生します。

ライブラリ自体はあまり関係ない?

解決策

とりあえず動かしたいのであれば、Gitの証明書を使用せよ。みたいな記事を見たので
C:\PASS\etc\pki\ca-trust\extracted\openssl\ca-bundle.trust.crt

上記パスをphp.iniのcurlの設定に適用

[curl]
; A default value for the CURLOPT_CAINFO option. This is required to be an
; absolute path.
#curl.cainfo = ここ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPMailerを使って、Gmailでメールを送信する方法

Gmail側でアプリケーションで使うパスワードを設定します。

スクリーンショット 2020-03-17 9.12.39.png

スクリーンショット 2020-03-17 9.12.49.png

PHPMailerで設定

<?php
/**
 * This example shows settings to use when sending via Google's Gmail servers.
 * This uses traditional id & password authentication - look at the gmail_xoauth.phps
 * example to see how to use XOAUTH2.
 * The IMAP section shows how to save this message to the 'Sent Mail' folder using IMAP commands.
 */

//Import PHPMailer classes into the global namespace
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;

require '../vendor/autoload.php';

//Create a new PHPMailer instance
$mail = new PHPMailer;

//Tell PHPMailer to use SMTP
$mail->isSMTP();

//Enable SMTP debugging
// SMTP::DEBUG_OFF = off (for production use)
// SMTP::DEBUG_CLIENT = client messages
// SMTP::DEBUG_SERVER = client and server messages
$mail->SMTPDebug = SMTP::DEBUG_SERVER;

//Set the hostname of the mail server
$mail->Host = 'smtp.gmail.com';
// use
// $mail->Host = gethostbyname('smtp.gmail.com');
// if your network does not support SMTP over IPv6

//Set the SMTP port number - 587 for authenticated TLS, a.k.a. RFC4409 SMTP submission
$mail->Port = 587;

//Set the encryption mechanism to use - STARTTLS or SMTPS
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;

//Whether to use SMTP authentication
$mail->SMTPAuth = true;

//Username to use for SMTP authentication - use full email address for gmail
$mail->Username = 'example@gmail.com';

//Password to use for SMTP authentication
$mail->Password = '生成したパスワード';

//Set who the message is to be sent from
$mail->setFrom('example@gmail.com', 'First Last');

//Set an alternative reply-to address
$mail->addReplyTo('example@gmail.com', 'First Last');

//Set who the message is to be sent to
$mail->addAddress('example@gmail.com', 'John Doe');

//Set the subject line
$mail->Subject = 'PHPMailer GMail SMTP test';

//Read an HTML message body from an external file, convert referenced images to embedded,
//convert HTML into a basic plain-text alternative body
$mail->msgHTML(file_get_contents('contents.html'), __DIR__);

//Replace the plain text body with one created manually
$mail->AltBody = 'This is a plain-text message body';

//Attach an image file
// $mail->addAttachment('images/phpmailer_mini.png');

var_dump("before sendging email<br>");

//send the message, check for errors
if (!$mail->send()) {
    echo 'Mailer Error: '. $mail->ErrorInfo;
} else {
    echo 'Message sent!';
    //Section 2: IMAP
    //Uncomment these to save your message in the 'Sent Mail' folder.
    #if (save_mail($mail)) {
    #    echo "Message saved!";
    #}
}

//Section 2: IMAP
//IMAP commands requires the PHP IMAP Extension, found at: https://php.net/manual/en/imap.setup.php
//Function to call which uses the PHP imap_*() functions to save messages: https://php.net/manual/en/book.imap.php
//You can use imap_getmailboxes($imapStream, '/imap/ssl', '*' ) to get a list of available folders or labels, this can
//be useful if you are trying to get this working on a non-Gmail IMAP server.
function save_mail($mail)
{
    //You can change 'Sent Mail' to any other folder or tag
    $path = '{imap.gmail.com:993/imap/ssl}[Gmail]/Sent Mail';

    //Tell your server to open an IMAP connection using the same username and password as you used for SMTP
    $imapStream = imap_open($path, $mail->Username, $mail->Password);

    $result = imap_append($imapStream, $path, $mail->getSentMIMEMessage());
    imap_close($imapStream);

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

PHPのforeach文で最初と最後を判別したい処理したくて、resetとendを使ってみたが・・・

PHPのforeach文で最初と最後を判別したかったので、備忘録の為に記載。

$array = ['sibuya', 'ebisu', 'meguro', 'gotanda', 'osaki'];
foreach ($array as $key => $value) {
    if ($value === reset($array)) {
        print "${value}は最初だよ\n";
    }
    if ($value === end($array)) {
        print "${value}最後だよ\n";
    }
}

結果

sibuyaは最初だよ
osaki最後だよ

配列要素の最初 sibuya を2つ用意したらどうなる?

$array = ['sibuya', 'ebisu', 'sibuya', 'gotanda', 'osaki'];
foreach ($array as $key => $value) {
    if ($value === reset($array)) {
        print "${value}は最初だよ\n";
    }
    if ($value === end($array)) {
        print "${value}最後だよ\n";
    }
}

結果

sibuyaは最初だよ
sibuyaは最初だよ
osaki最後だよ

おいおい!笑
配列要素内に重複する値が入ってると予期せぬ動きが・・・
待てよ・・・最後の要素も・・・

配列要素の最後 osaki を2つ用意したらどうなる?

$array = ['sibuya', 'ebisu', 'osaki', 'gotanda', 'osaki'];
foreach ($array as $key => $value) {
    if ($value === reset($array)) {
        print "${value}は最初だよ\n";
    }
    if ($value === end($array)) {
        print "${value}最後だよ\n";
    }
}

結果

sibuyaは最初だよ
osaki最後だよ
osaki最後だよ

ジーザス・・・

ループ内で resetend を使うのはご法度らしいですね。
https://qiita.com/ay123/items/90d0e107813e00f2b11f

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

ストアフロントテーマを作成

ストアフロントテーマを作成

店頭テーマのステップを作成ステップス:

1.以下のディレクトリにテーマのディレクトリを作成します

app/design/frontend/[your_vendor_name]/[your_theme_name].
2. theme.xmlファイルにテーマを宣言する
3. registration.phpファイルにテーマを登録する
4. etc/view.xmlファイルに画像の設定を設定する
5. テーマロゴを宣言する
6. 管理画面でテーマを構成します。

テーマのディレクトリを作成

Go to MAGENTO_DIRECTORY/app/design/frontend に、

上記のディレクトリに、<vendor>ディレクトリを作成して、テーマのディレクトリを作成します。
私の場合、以下を設定します。
  - ベンダー名: Karabiner
  - テーマ名:green

app/design/frontend/
Karabiner
├── green/
│   ├── etc/
│   │   ├── view.xml
│   ├── web/
│   │   ├── images
│   │   │   ├── karabiner_logo.svg
│   ├── registration.php
│   ├── theme.xml

theme.xmlファイルにテーマを宣言する

app/design/frontend/Karabiner/greenに、 theme.xmlファイルを作成します。
theme.xmlファイルはテーマの基本情報(テーマ名、親テーマ、プレビュー画像など)を設定します。

app/design/frontend/Karabiner/green/theme.xml
<?xml version="1.0" encoding="UTF-8"?>
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Green</title> <!-- your theme's name -->
    <parent>Magento/blank</parent> <!-- the parent theme, in case your theme inherits from an existing theme -->
</theme>

registration.phpファイルにテーマを登録する

テーマの登録ファイルは以下のような感じです。

app/design/frontend/Karabiner/green/registration.php
<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::THEME,
    'frontend/Karabiner/green', // theme path
    __DIR__
);

etc/view.xmlファイルに画像の設定を設定する

これは構成ファイルです。このファイルは、Magento 2テーマでは「必須」※です。
※ - 親テーマに存在する場合はオプションです。

app/design/frontend/Karabiner/green/ に、 etc/view.xmlファイルを作成する
Blank テーマなどの親テーマでview.xmlファイルをコピーできます。
vendor/magento/theme-frontend-blank/etc/view.xml

app/design/frontend/Karabiner/green/etc/view.xml
<image id="category_page_grid" type="small_image">
    <width>250</width>
    <height>250</height>
</image>

view.xmlでは、画像のプロパティは要素のスコープで設定されます:

app/design/frontend/Karabiner/green/etc/view.xml
<images module="Magento_Catalog">
...
<images/>

画像プロパティは、 imageタグのidとtype属性によって定義される各画像タイプに対して設定されます:

app/design/frontend/Karabiner/green/etc/view.xml
<images module="Magento_Catalog">
    <image id="unique_image_id" type="image_type">
    <width>100</width> <!-- Image width in px --> 
        <height>100</height> <!-- Image height in px -->
    </image>
<images/>

テーマロゴを宣言する

Magento 2のデフォルトでは、 [theme_dir]/web/images/logo.svgを使用します。default.xmlでは、png、jpgなどの別の画像形式に変更できますが、設定する必要があります。

ロゴのサイズは100x100pxにします
[theme_dir]/Magento_Theme/layout/default.xml

app/design/frontend/Karabiner/green/Magento_Theme/layout/default.xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="logo">
            <arguments>
                <argument name="logo_file" xsi:type="string">images/karabiner_logo.svg</argument>
                <argument name="logo_img_width" xsi:type="number">100</argument> 
                <argument name="logo_img_height" xsi:type="number">100</argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

管理画面でテーマを設定します。

Magentoにテーマが登録されたことを確認するには

Admin panel -> content -> under Design -> click on Themes
content-design-themes.png

テーマページに新しいテーマが表示されます。

content-design-themes-new-theme.png

テーマを設定する

Admin panel -> content -> under Design -> Configuration

content-design-config.png

テーマを選択して、 [save Configuration]にクリックする
content-design-config-change-theme.png

キャッシュをクリアして、フロントエンドを確認します

karabinerロゴを表示される
logo-changed.png

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

管理画面テーマを作成

管理画面テーマを作成します。

管理画面テーマのステップを作成します。

1.以下のディレクトリにテーマのディレクトリを作成します

app/design/adminhtml/[your_vendor_name]/[your_theme_name].
2. theme.xmlファイルにテーマを宣言する
3. registration.phpファイルにテーマを登録する
4. etc/view.xmlファイルに画像の設定を設定する
5. テーマロゴを宣言する

テーマディレクトリを作成する

テーマのディレクトリを作成します:

MAGENTO_DIRECTORY/app/design/adminhtmlに、

上記のディレクトリに、<vendor>ディレクトリを作成して、テーマのディレクトリを作成します。
私の場合、以下を設定します。
  - ベンダー名: Karabiner
  - テーマ名:「green」。

app/design/adminhtml/
Karabiner ←ベンダー名
├── green/ ←テーマ名
│   ├── etc/
│   │   ├── view.xml
│   ├── web/
│   │   ├── images
│   │   │   ├── karabiner_logo.svg
│   ├── registration.php
│   ├── theme.xml

theme.xmlファイルにテーマを宣言する

app/design/adminhtml/Karabiner/greenに、 theme.xmlファイルを作成します。
theme.xmlファイルはテーマの基本情報(テーマ名、親テーマ、プレビュー画像など)を設定します。

app/design/adminhtml/Karabiner/green/theme.xml
<?xml version="1.0" encoding="UTF-8"?>
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Green</title> <!-- テーマ名 -->
    <parent>Magento/blank</parent> <!-- 親テーマ -->
</theme>

registration.phpファイルにテーマを登録する

テーマの登録ファイルは以下のような感じです。

app/design/adminhtml/Karabiner/green/registration.php
<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::THEME,
    'adminhtml/Karabiner/green', // theme path
    __DIR__
);

etc/view.xmlファイルに画像の設定を設定する

これは構成ファイルです。このファイルは、Magento 2テーマでは「必須」※です。
※ - 親テーマに存在する場合はオプションです。

app/design/adminhtml/Karabiner/green/に、 etc/view.xmlファイルを作成する
theme-adminhtml-backendテーマなどの親テーマでview.xmlファイルをコピーできます。

vendor/magento/theme-adminhtml-backend/etc/view.xml

app/design/adminhtml/Karabiner/green/etc/view.xml
<image id="category_page_grid" type="small_image">
    <width>250</width>
    <height>250</height>
</image>

テーマロゴを宣言する

Magento 2のデフォルトでは、 [theme_dir]/web/images/logo.svgを使用します。default.xmlでは、png、jpgなどの別の画像形式に変更できますが、設定する必要があります。
[theme_dir]/Magento_Theme/layout/default.xml
ロゴのサイズは100x100pxにします

app/design/adminhtml/Karabiner/green/Magento_Theme/layout/default.xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="logo">
            <arguments>
                <argument name="logo_file" xsi:type="string">images/karabiner_logo.svg</argument>
                <argument name="logo_img_width" xsi:type="number">100</argument> 
                <argument name="logo_img_height" xsi:type="number">100</argument>
            </arguments>
        </referenceBlock>
    </body>
</page>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gRPC client for PHP

gRPC client for PHP

概要

恥ずかしながら gRPC というものを知りませんでした。.route ファイルだけを提供されて、「あとはそちらでよろしく」的な投げっぱなしジャーマンを食らったので、やったことをメモしておきます。

前提

PHPで gRPC サーバと通信することを目的としています。サーバの構築、Protocol Buffer の定義ファイル(.proto)作成は、すでにできていると仮定します。

環境

  • Ubuntu 18.04.2 LTS
  • PHP 7.2.7

やること

  • gRPC for PHP のインストール
  • protoc のインストール
  • gRPC PHP Protoc Plugin のインストール
  • クライアントPHPコードの生成

実際に行っていることは、以下を参考にしました。
https://grpc.io/docs/quickstart/php/

gRPC for PHP のインストール

以下を参考にして PHP に拡張モジュールをインストールします。

https://cloud.google.com/php/grpc?hl=ja

ソースからビルドする方法もありますが、私は pecl でインストールしました。php.ini で extension として追加したら、composer で、プロジェクトに grpc/grpc パッケージを追加します。

composer require "grpc/grpc:^v1.1.0"

また、通信するためにはランタイムライブラリが必要です。これは、拡張モジュールとしてインストール方法とPHP実装をパッケージとして追加する方法があります。資料にある通り、パフォーマンスを求めるのであれば、拡張モジュールをインストールした方が良いです。が、私はそこまでパフォーマンスを求めていなかったので、composer でのパッケージ追加で対応しました。

composer require "google/protobuf:^v3.3.0"

protoc のインストール

protoc は、大雑把にいうと Protocol Buffer 定義ファイル(.proto)から、対応するクラス等を生成するコンパイラです。これを使って、PHPコードを生成するのでインストールします。

https://developers.google.com/protocol-buffers/docs/downloads

上記からリンクをたどるとコンパイル済みのバイナリがあるので最新版をインストールします。私がインストールした時点では、v3.11.4 でした。

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
unzip -d protoc protoc-3.11.4-linux-x86_64.zip 

bin と include をパスと通っている場所にコピーします。私の場合は以下。

sudo mv protoc/bin/* /usr/local/bin/
sudo mv protoc/include/* /usr/local/include/

権限は適宜変更してください。

gRPC PHP Protoc Plugin のインストール

PHP のクライアント・スタブを生成するには、gRPC PHP Protoc Plugin(grpc_php_plugin) が必要です。apt で protobuf-compiler-grpc を入れれば入ることは入るのですが、古いバージョンであることが多いので、公式の手順にしたがって、インストールします。

git clone -b v1.27.2 https://github.com/grpc/grpc
cd grpc
git submodule update --init
make grpc_php_plugin

make には時間がかかります。私の場合は30分程かかりました。./bins/opt 以下に grpc_php_plugin ができているはずなので、お好きな場所にどうぞ。私は、パスの通っている以下に置きました。

sudo cp -p ./bins/opt/grpc_php_plugin /usr/local/bin/

クライアントPHPコードの生成

最後に protoc を使って、PHPのコードを生成します。.proto ファイルは、提供されている前提ですが、サンプルがないとよくわからんと思うので、公式にあるサンプルを使います。

https://github.com/grpc/grpc/blob/v1.27.2/examples/protos/helloworld.proto

ここでは PHP コードを生成するだけです。gRPCサーバーがなく、実際にリクエストを試したい方は、以下の Node.js チュートリアルを参考にサーバーを作成してください。

https://grpc.io/docs/tutorials/basic/node/

.proto ファイルがあれば、あとはそれを protoc に渡すだけです。

mkdir -p work/protos
mkdir -p work/src
cd work/protos
wget https://raw.githubusercontent.com/grpc/grpc/v1.27.2/examples/protos/helloworld.proto
cd ../
protoc ./protos/helloworld.proto --proto_path=./protos --php_out=./src --grpc_out=./src --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
tree src 
src/
├  GPBMetadata
│   └ Helloworld.php
└  Helloworld
    ├  GreeterClient.php
    ├── HelloReply.php
    └── HelloRequest.php

2 directories, 4 files

これでPHPコードが生成できたと思います。生成されたコードの使用方法は公式を参考にしてください。

https://grpc.io/docs/tutorials/basic/php/

ちなみに、上記の .proto ファイルで生成すると、勝手に Helloworld の namespace が作成されますが、これは .proto ファイル内のオプション

package helloworld;

を指定することで namespace が決定されます。それ以外の PHP コード生成に関するオプションには、以下があります。

// 生成されるクラスに prefix を付けます
option php_class_prefix = "Sample";

// 生成されるクラスの namespace を変更します。デフォルトは package を元に決定されます。
option php_namespace = "App\\Services\\Sample";

// 生成されるメタデータクラスの namespace を変更します。
option php_metadata_namespace = "App\\Services\\Sample\\GPBMetadata";

その他のオプションは以下から探してみてください。

https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto

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

Unity WebAPIを使ってデータを扱う

環境

PHP(Laravel5)
Unity 2019.1.4f1 (64-bit)

概要

WebAPIを使った通信とデータ扱い。
・UnityWebRequest
APIのリクエストするため

・JsonUtility
APIで取得したjsonを使うため

この二点を使う。

実装

Model.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking; 

public class Model 
{
    public int id;
    public int name;
    public int hp;

    string access_url = "https://hoge.com/api/v1";

    [System.Serializable]
    public class Character
    {
        public int id;
        public string name;
        public int hp;
    }

    [System.Serializable]
    public class Characters
    {
        public Character[] characters;
    }

    public IEnumerator ApiRequest()
    {
        UnityWebRequest request = UnityWebRequest.Get(access_url);
        request.SetRequestHeader("Content-Type", "application/json");
        yield return request.SendWebRequest();

        Characters characters = JsonUtility.FromJson<Characters>("{\"characters\":" + request.downloadHandler.text + "}");

        id = characterClass.characters[0].id;
        name = characterClass.characters[0].name;
        hp = characterClass.characters[0].hp;   
    }
}

使い方

読み込まれた時点で呼びだしてあげればいいかと思います。
後は好みでどうぞ

    public void Start()
    {
        StartCoroutine(getModel());
    }

    IEnumerator getModel()
    {
        characterModel = new CharacterModel(); 
        yield return StartCoroutine(characterModel.ApiRequest());
                // 以下表示させるなど
    }

PHP - Laravel

特別なことはしてない
LaravelのModelを読んでreturnで返すだけです。

namespace App\Http\Controllers;
use App\Models\Character;

class ApiController extends Controller
{
    public function index()
    {
        return Character::all();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像アップロードの留意点

ファイルサイズの上限

上限の設定をPHP側でするのかブラウザ側でするのかで作業内容が変わる。
PHP側で設定してもブラウザ側で何も設定していない場合、上限のチェックが入るのはサーバにファイルをアップロード処理をしてからとなってしまう。
よって、巨大なファイルを転送しようとして長時間待ち続け、 結局ファイルが大きすぎてアップロードできなかったという事態に陥る。

  • PHP側で設定する場合

php.iniのupload_max_filesizeに設定する。
upload_max_filesizeは1ファイルあたりの上限サイズの設定
post_max_sizeはPOST全体のサイズ上限
post_max_sizeはupload_max_filesizeよりも常に大きくなければならない。

  • ブラウザ側で設定する場合

"file"inputフィールドの前に"MAX_FILE_SIZE"hiddenフィールドを置く必要がある。
この値はバイト数で指定する。

例).1MBの場合は1024*1024= 1048576バイト

 <input type="hidden" name="MAX_FILE_SIZE" value="1048576">
    <input type="file" name="image">

ファイルをアップしようとして、MAX_FILE_SIZE の制限を超えている場合、
$_FILESの['type']['tmp_name']は空である。

参考記事
http://hensa40.cutegirl.jp/archives/930

ファイルの検証

  • そもそもファイルがちゃんとPOSTされているか
if (!isset($_FILES['image']) || !isset($_FILES['image']['error'])) {
  throw new \Exception('Upload Error!');
}
  • エラーの内容によってエラーメッセージを出し分ける
switch($_FILES['image']['error']) {
  case UPLOAD_ERR_OK:
   return true;
  case UPLOAD_ERR_INI_SIZE: //php.iniのupload_max_filesize超過
  case UPLOAD_ERR_FORM_SIZE: //HTMLフォームで指定されたMAX_FILE_SIZE超過
   throw new \Exception('File too large!');
  default:
   throw new \Exception('Err: ' . $_FILES['image']['error']);
 }

参考記事
https://www.softel.co.jp/blogs/tech/archives/1824

画像ファイルの判別

exif_imagetype関数を使う

switch(exif_imagetype($_FILES['image']['tmp_name'])) {
      case IMAGETYPE_GIF:
        return 'gif';
      case IMAGETYPE_JPEG:
        return 'jpg';
      case IMAGETYPE_PNG:
        return 'png';
      default:
        throw new \Exception('PNG/JPEG/GIF only!');
    }

実際にアップロードする

  • ファイル名を決める

ファイル名は被らないように考慮する必要があるためuniqid関数を使うとよい。
これはマイクロ秒単位の現在時刻に基づいた13文字のIDが生成される。第二引数をtrueにするとさらに文字が長くなる。

  • 格納ディレクトリ

予め定数で格納ディレクトリを決めておくとよい。

define('IMAGES_DIR', __DIR__ . '/images');
  • 保存処理

move_uploaded_file関数を使う。引数にはファイル名を含むパスを指定する。
処理が失敗した場合はFALSEが返ってくるため、その場合は例外を投げるようにする。

    $savePath = IMAGES_DIR . '/' . $this->_imageFileName;
    $res = move_uploaded_file($_FILES['image']['tmp_name'], $savePath);
    if ($res === false) {
      throw new \Exception('Could not upload!');
    }

サムネイルを作る

横幅が大きい場合、サムネイルを作るためまずは画像サイズのチェックをする

   $imageSize = getimagesize($savePath);
    $width = $imageSize[0];
    $height = $imageSize[1];
    if ($width > THUMBNAIL_WIDTH) {
      $this->_createThumbnailMain($savePath, $width, $height);
      }
    }

サムネイルを作るには元画像の画像リソースを作り、それを元にサムネイルを作ってそれで保存をする。
(※gif,jpg,pngで、元画像を読み込むときと、サムネイルに加工後に出力する時の2つのポイントで関数が異なる。)

全体的な流れ

  • 元画像のリソースを読み込む
    • (imagecreatefromgif,imagecreatefromjpeg,imagecreatefrompng)
  • サムネイルの型を作る
    • (imagecreatetruecolor関数)
  • 実際にサムネイルを作る
    • (imagecopyresampled関数)
  • サムネイル画像を出力する
    • (imagegif,imagejpeg,imagepng)

関連記事
https://webkaru.net/php/image-thumbnail/
https://the-zombis.sakura.ne.jp/wp/blog/2012/01/03/post-1154/
https://00m.in/UBZ2n

画像一覧を表示する

基本的な流れとしては画像を取得してループ。
PHPで指定したディレクトリのファイル一覧にはテンプレがある。

    $imageDir = opendir(IMAGES_DIR);
    while (false !== ($file = readdir($imageDir))) {
      if ($file === '.' || $file === '..') {
        continue;
      }
      $files[] = $file;

アップロード成功時・失敗時のメッセージを出力する

メッセージを出力する関数には成功時・失敗時両方のメッセージを返すようにする

  public function getResults() {
    $success = null;
    $error = null;
    ...処理
    return [$success, $error];
  }

一覧画面では両方を一度に受け取る(list関数)

  list($success, $error) = $uploader->getResults();

アップロードボタンのスタイリング

画像ファイルを選択したらそのままアップロードされるようにする。
"submit"フィールドを消して、fileフィールドを透明にしつつボタン横いっぱいまで広げ、jQueryで要素が変わったらsubmitボタンが有効になるようにする

イベントの詳細
https://qiita.com/bibouroku/items/6b998ab90194b473286e#%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E9%96%A2%E9%80%A3%E3%81%AE%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88

HTML
<div class="btn">
  アップロードする
  <form action="" method="post" enctype="multipart/form-data" id="my_form">
    <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo h(MAX_FILE_SIZE); ?>">
    <input type="file" name="image" id="my_file">
  </form>
</div>
jQuery
<script>
$(function() {
  $('.msg').fadeOut(3000);
  $('#my_file').on('change', function() {
    $('#my_form').submit();
  });
});
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでSORACOM APIを呼び出す

SORACOMのAPIをPHP(Guzzle)を使って呼び出してみます。

SAMを用意する

ユーザーコンソールからAPIを実行するためのSAM(SORACOM Access Management)ユーザーを用意します。

ユーザーを作成して権限設定、認証情報を作成しましょう。
AWSのIAMと同じような感じです。

発行した認証キーIDと認証キーシークレットはAPI呼び出しに必要になります。

PHPからAPIを呼び出す

Guzzleをインストール

HTTPクライアントのGuzzleをインストールします。

composer require guzzlehttp/guzzle

APIを呼び出す

最初にauthAPIに認証キーIDと認証キーシークレットを渡して、APIキーとAPIトークンを取得する必要があります。
他のAPIはすべて、取得したAPIキーとAPIトークンをリクエストヘッダに含めてリクエストしなければいけません。

下記のサンプルでは、最初にauthAPIを呼び出して次にLatestBillingAPIを呼び出しています。

exec.php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

const BASE_URL = 'https://api.soracom.io/v1/';
$apiKey;
$operatorId;
$userName;
$token;

$credential = [
    'authKeyId' => 'keyId-************************',
    'authKey' => 'secret-*****************************************',
];

$httpClient = new Client();

// APIキーとAPIトークンを取得
$url = BASE_URL . 'auth';
try {
    $response = $httpClient->request('POST', $url, [
        'headers' => [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => $credential,
    ]);
} catch (ClientException $e) {
    throw $e;
}
$body = $response->getBody();
$info = json_decode($body, true);
$apiKey = $info['apiKey'];
$operatorId = $info['operatorId'];
$userName = $info['userName'];
$token = $info['token'];

// LatestBillingAPIの呼び出し
$url = BASE_URL . 'bills/latest';
try {
    $response = $httpClient->request('GET', $url, [
        'headers' => [
            'Accept' => 'application/json',
            'X-Soracom-API-Key' => $apiKey,
            'X-Soracom-Token' => $token,
        ],
    ]);
} catch (ClientException $e) {
    throw $e;
}
$body = $response->getBody();
echo $body;

実行結果は次のようになります。

>php exec.php
{"lastEvaluatedTime":"20200317003205","amount":0}

参考

API リファレンス

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

stripe-phpメモ

stripe-phpメモ

備忘録レベルの殴り書き

stripe-phpとは?

3分で調べた内容

  • 決済代行サービスstripeをPHPから叩くためのライブラリ
    • CCbillとかとは毛色が違いそう?
  • 決済マージン3.6%
  • クレジット決済可能
  • 便利なダッシュボードがあるらしい
  • サブスクサービスが作れるらしい

stripe-phpの使い方

おまけ:file_get_contentsで叩くラッパーのサンプル

public static function Get($url, $id) {
    $header = [
        'Authorization: Basic ' . base64_encode("${id}:")
    ];
    $context = [
        'http' => [
            'header' => implode("\r\n", $header)
        ]
    ];
    return file_get_contents($url, false, stream_context_create($context));
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Wordpressでプラグインを使わず人気記事ランキングを表示する方法

人気記事を作るには記事が表示された際にカスタムフィールドに加算していくようにするだけ。

人気記事のfunctions.php設定

functions.phpに以下のコードを追記する。対応するkeyのカスタムフィールドがない場合は新しく作成する。

2つめの関数でwp_headが実行されるときにカウントを実行するように設定。

function my_popular_post_counter($post_id) {
    $count_key = 'view_count';
    $count = get_post_meta($post_id, $count_key, true);
    if ($count == '') {
        $count = 0;
        delete_post_meta($post_id, $count_key);
        add_post_meta($post_id, $count_key, '0');
    } else {
        $count++;
        update_post_meta($post_id, $count_key, $count);
    }
}
function my_popular_post_trigger($post_id) {
    if (!is_single()) return;
    if (empty($post_id)) {
        global $post;
        $post_id = $post->ID;
    }
    my_popular_post_counter($post_id);
}
add_action('wp_head', 'my_popular_post_trigger');

テンプレートに表示

テンプレートにランキングを表示するには、WP_Query()でクエリを実行するだけ。

画面が表示されるたびにカウントするので、リロードを繰り返すとランクが変わるのがわかる。

<h3>人気記事</h3>
<ul>
    <?php 
    $args = array(
        'orderby'=>'meta_value_num', 
        'order' => 'DESC', 
        'showposts' => 5, 
        'meta_key'=>'view_count',
    );
    $wp_query = new WP_Query( $args );
    if ($wp_query->have_posts()) : while ($wp_query->have_posts()) : 
  $wp_query->the_post(); ?>
        <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
    <?php endwhile; endif; wp_reset_postdata();?>
</ul>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む