20200820のPHPに関する記事は14件です。

生年月日から年齢を計算して表示させる JavaScript PHP

少し前の案件で「会社概要ページの代表者プロフィールに年齢を表記する」ってことがあって、
年齢は毎年変わるから自動で更新させないとなー、と思いつつ面倒くさくてそのまま表記したって事がありました。

その事を思い出して、jsとphpで年齢を計算して表示させるってのを作ってみました。
jsかphpのどちらかで実装できるので、好きな方を使ってください。

PHPで年齢を計算して表示

"19910402"の部分が生年月日です、年齢を表示させたい人の生年月日に変更してください。
ちなみに1991年4月2日は僕の誕生日です。

php
年齢は<?php echo floor((date('Ymd') - 19910402) / 10000); ?>歳です。
実行結果
年齢は29歳です。

JavaScriptで年齢を計算して表示

同じく"19910402"の部分が生年月日です、年齢を表示させたい人の生年月日に変更してください。

html
年齢は<span id="age"></span>歳です。
javascript
function calcAge(birthdate) {
    const today = new Date();
    targetdate = today.getFullYear() * 10000 + (today.getMonth() + 1) * 100 + today.getDate();
    return (Math.floor((targetdate - birthdate) / 10000));
}
document.getElementById("age").innerHTML = calcAge(19910402);
実行結果
年齢は29歳です。

まとめ

現在の日付から生年月日を引いて、1万で割ると年齢が出るらしいです。
数学って不思議。

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

ラズパイとカメラを使用してカラーセンサー作成

要望 加工品のつまり、終了を見抜きたい

機械工作工場にて自動レーンでの製品のつまり、最終加工品の完了した時を知りたい。
製品のつまり.jpg
バルブの赤いハンドルの位置(開閉表示)を知りたい。
バルブの状態.png

製品は黄色、背景(レーン)は白、青、又は黒の原色。
既製品のセンサーが高すぎるので安く作って欲しいとの要望。

完成品

ラズパイをローカルWIFIにつなげて。
http://"ラズパイのIPアドレス"/home.php

加工品.jpg

抜き出し場所.JPG

カメラ画像から当該箇所を指定して抜き出す。
色範囲を指定して、色範囲に入った場合GPIOを出力する。
GPIOの出力にて警報ランプを起動させる。
警報解除は警報リセットにて行う。

ラズパイ環境設定

python OpenCV ダウンロード
参考サイト 計算機科学を学んだソフトウェア技術者の経験やメモなどの記録様

sudo apt-get install libopencv-dev
sudo apt-get install python-opencv

アパッチダウンロード
参考サイト @mono_taro様
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install apache2
apachectl -v

phpダウンロード
参考サイト The modern stone age.様
sudo apt install php7.2 libapache2-mod-php7.2

php及びhtml作成

/var/www/html/home.php
<html>
<head>
<meta http-equiv="content-type" charset="utf-8">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="refresh" content="5" >
</head>
</html>
<form action="set.php" method="get">
<body>
    <h1>カメラ画像</h1>
    <?php
        $lines_reset = file('/home/pi/Documents/reset.csv');
        foreach($lines_reset as $line_reset){
            $data_reset = explode(',',$line_reset);
        if ($data_reset[0] == 1){
        echo "<FONT COLOR=\"RED\"> 現在警報動作中 解除する場合は下ボタンにて </FONT>";}
        }
    ?>
        <img src="1.jpg?1">
        <h2>抜出画像</h2>
        <img src="2.jpg?1">
</body>
<?php
$lines = file('/home/pi/Documents/color.csv');
foreach($lines as $line){
    $data = explode(',',$line); 
    echo '<p>';
    echo ' 赤色R',$data[0];
    echo ' 緑色G',$data[1];
    echo ' 青色B',$data[2];    
    echo '</p>';
}
?>
<a href="./set.html">設定画面</a>
<a href="./reset.html">警報リセット</a>
</form>
/var/www/html/set.html
<form action="set.php" method="get">
<p>
<a href="./home.php">戻る</a>
</p>
<img src="1.jpg">
<p>色の範囲指定</p>
<p>赤下限: <input type="number" name="r_2_1" value="1" min="0" max="255"></p>
<p>赤上限: <input type="number" name="r_2_2" value="1" min="0" max="255"></p>
<p>緑下限: <input type="number" name="g_2_1" value="1" min="0" max="255"></p>
<p>緑上限: <input type="number" name="g_2_2" value="1" min="0" max="255"></p>
<p>青下限: <input type="number" name="b_2_1" value="1" min="0" max="255"></p>
<p>青上限: <input type="number" name="b_2_2" value="1" min="0" max="255"></p>
<p>画像抜出ファイル 上下は上からの距離、左右は左からの距離</p>
<p>top: <input type="number" name="top" value="1" min="0" max="2000"></p>
<p>下bot: <input type="number" name="bot" value="100" min="0" max="2000"></p>
<p>left: <input type="number" name="left" value="1" min="0" max="2000"></p>
<p>右right: <input type="number" name="right" value="100" min="0" max="2000"></p>
<p>シャッター間隔、規定回数</p>
<p>シャッター間隔 秒(s): <input type="number" name="interval" value="60" min="10" max="6000"></p>
<p>回数: <input type="number" name="nutime" value="1" min="1" max="100"></p>
<input type="submit" />
<p> </p>
<p>補足色説明</p>
<p>赤色 R255 G0 B0</p>
<p>黒 R0 G0 B0</p>
<p>白 R255 G255 B255</p>
<p>黄色 R255 G255 B0</p>
<p>灰色 R128 G128 B128</p>
<p>その他の色はRGB色で検索願います。</p>
<p>規定回数とは連続で指定範囲内に入った回数の時、リレーが作動する回数</p>
</form>
/var/www/html/set.php
<meta http-equiv="content-type" charset="utf-8">
<?php 
     $r_2_1=htmlspecialchars($_GET["r_2_1"],ENT_QUOTES);
     $r_2_2=htmlspecialchars($_GET["r_2_2"],ENT_QUOTES);
     $g_2_1=htmlspecialchars($_GET["g_2_1"],ENT_QUOTES);
     $g_2_2=htmlspecialchars($_GET["g_2_2"],ENT_QUOTES);
     $b_2_1=htmlspecialchars($_GET["b_2_1"],ENT_QUOTES);
     $b_2_2=htmlspecialchars($_GET["b_2_2"],ENT_QUOTES);
     $top=htmlspecialchars($_GET["top"],ENT_QUOTES);
     $bot=htmlspecialchars($_GET["bot"],ENT_QUOTES);
     $left=htmlspecialchars($_GET["left"],ENT_QUOTES);
     $right=htmlspecialchars($_GET["right"],ENT_QUOTES);
     $interval=htmlspecialchars($_GET["interval"],ENT_QUOTES);
     $nutime=htmlspecialchars($_GET["nutime"],ENT_QUOTES);
?>
<body>
<?php echo $_GET["r_2_1"]; ?>
<?php 
$data = [
    [$_GET["r_2_1"], $_GET["r_2_2"], $_GET["g_2_1"], $_GET["g_2_2"], $_GET["b_2_1"], $_GET["b_2_2"],
    $_GET["top"], $_GET["bot"], $_GET["left"], $_GET["right"],
    $_GET["interval"], $_GET["nutime"]],
];
$fp = fopen('/home/pi/Documents/position.csv', 'w');
foreach ($data as $line) {
    fputcsv($fp, $line);
}
fclose($fp);
?>
<p>
<a href="./home.php">戻る</a>
</p>
/var/www/html/reset.html
<meta http-equiv="content-type" charset="utf-8">
<meta http-equiv="Cache-Control" content="no-cache">
<form action="reset.php" method="get">
<input type="submit" />
</form>
/var/www/html/reset.php
<meta http-equiv="content-type" charset="utf-8">
<?php 
$data = [["0"]];
$fp = fopen('/home/pi/Documents/reset.csv', 'w');
foreach ($data as $line) {
    fputcsv($fp, $line);}
fclose($fp);
?>
<p>
<a href="./home.php">戻る</a>
</p>

python 及びCSV作成

/home/pi/Documents/gazou.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import cv2, os
import numpy as np
import csv
import time
import RPi.GPIO as GPIO
time.sleep(60)#ラズパイ起動時の待機時間
#GPIO設定
PIN = 14
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN, GPIO.OUT)
#イメージファイルの保存場所
image_file = '/var/www/html/1.jpg' #写真撮影
image_file_2 = '/var/www/html/2.jpg' #検証1
#読み込み用CSVの場所
poss = '/home/pi/Documents/position.csv'
colo_0 = '/home/pi/Documents/color0.csv'
colo_1 = '/home/pi/Documents/color.csv'
poss_reset = '/home/pi/Documents/reset.csv'
while True:
    try :
        with open(poss) as data0:#切り出し場所を読み取る
            reader = csv.reader(data0)
            data1 = [row for row in reader]
        #範囲読み取り
        r_2_1 = int(data1[0][0]) #赤下限 img2
        r_2_2 = int(data1[0][1]) #赤上限 img2
        g_2_1 = int(data1[0][2]) #緑下限 img2
        g_2_2 = int(data1[0][3]) #緑上限 img2
        b_2_1 = int(data1[0][4]) #青下限 img2
        b_2_2 = int(data1[0][5]) #青上限 img2
        #img2.jpgの切り出し部分
        top_2   = int(data1[0][6])
        bot_2   = int(data1[0][7])
        left_2  = int(data1[0][8])
        right_2 = int(data1[0][9])
        #繰り返し秒数及び動作確認数
        jobs_s = int(data1[0][10])
        time.sleep(jobs_s)
        with open(poss_reset) as data_reset:#リセットCSV読み取り
            reader_reset = csv.reader(data_reset)
            data_reset1 = [row for row in reader_reset]
        data_resset2 = int(data_reset1[0][0])
        with open(colo_0) as data2:#書き込み用のデフォルトデータを読み込む
            reader = csv.reader(data2)
            data3 = [row for row in reader]
        cam = cv2.VideoCapture(0)
        # カメラから映像を読み込む
        _, img = cam.read()
        # 画像ファイルとして書き出す
        img2 = img[top_2 : bot_2, left_2: right_2]
        #イメージ保存
        cv2.imwrite(image_file, img)
        cv2.imwrite(image_file_2, img2)
        # RGB平均値を出力
        # flattenで一次元化しmeanで平均を取得 
        r2 = img2.T[2].flatten().mean()
        g2 = img2.T[1].flatten().mean()
        b2 = img2.T[0].flatten().mean()     
        if data_resset2 == 0:
            GPIO.output(PIN, GPIO.LOW) #とりあえず切る
            if r_2_1 < r2 < r_2_2 and g_2_1 < g2 < g_2_2 and b_2_1 < b2 < b_2_2:
                GPIO.output(PIN, GPIO.HIGH)
                data_resset2 = 1
        # RGB平均値を取得
        data3[0][0] = '%.0f' % r2
        data3[0][1] = '%.0f' % g2
        data3[0][2] = '%.0f' % b2
        #CSVを保存
        with open(colo_1, 'w') as f:
            writer = csv.writer(f)
            for data4 in data3:
                writer.writerow(data4)
        data_reset1[0][0] = data_resset2
        with open(poss_reset, 'w') as f:
            writer = csv.writer(f)
            for data_reset2 in data_reset1:
                writer.writerow(data_reset2)
        # 事後処理
        cam.release()
        cv2.destroyAllWindows()
    except:
        time.sleep(30)
        pass
/home/pi/Documents/position.csv
1,1,1,1,1,1,1,100,1,100,60,1
/home/pi/Documents/color0.csv
100,100,100,100,100,100
/home/pi/Documents/color.csv
100,100,100,100,100,100
/home/pi/Documents/reset.csv
0

起動時にpythonを起動する設定をする。

sudo にて gazou.pyを自動起動する。
sudo でないと/var/www/html/にjpgの画像を書き込まない
参考サイト@yuru-camper様

使用したカメラ

C270N HD WEBCAM
logicool様サイト

将来やりたい事

設定画面が下記のように設定しにくいので抜き出し箇所はマウスで選択できるようにしたい。
色設定もマウスで決定できるようにしたい。
現在の状態だと設定がトライアンドエラーをたくさん繰り返さなくてはいけない。
設定画面.JPG

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

DockerでGD Libraryのエラー対応

LaravelをローカルのDockerで動かしていて、
画像の圧縮しようとしたときにつまずいたのでメモ。

エラー

GD Library extension not available with this PHP installation.

PHPのGDライブラリが使用できないらしい。
早速Dockerfileを編集

結論だけ言うと、このようなDockerfileになりました

FROM php:7.4-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
  && apt-get install -y wget git unzip libzip-dev libpq-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
  && docker-php-ext-install zip pdo_mysql \
  && docker-php-ext-configure gd --with-freetype --with-jpeg \
  && docker-php-ext-install -j$(nproc) gd

#Composer install
COPY --from=composer /usr/bin/composer /usr/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin


WORKDIR /var/www

RUN composer global require "laravel/installer"

Dockerfileの変更を反映

イメージ:再構築の必要あり:docker-compose build
コンテナ:再構築の必要あり:docker-compose up -d

無事にエラーが出なくなりました。

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

[PHP]特定のYouTube動画の情報を取得する

特定チャンネルの動画情報だったりプレイリスト一覧を取得したいとき、YouTubeAPIv3を使うと思います。
PHPだと、ComposerとかでGoogle公式ライブラリを使うのもいいですが特定の動画の場合はもっとかんたんに動画情報を取得できます。

具体的にはAPIから動画情報が格納されているJSONを呼び出します。
以上!

JSONのURL構造

https://www.googleapis.com/youtube/v3/videos?id=[動画ID]&key=[APIKey]&part=snippet,contentDetails,statistics,status

[APIKey]にはGoogleAPIから取得したAPIキーを入力してください。
キーの取得方法はネットに溢れかえってるんで自分で調べてください。

サンプルコード

YouTubeAPIv3から特定動画のタイトルを取得してみます。

yt.php
$api_key = APIキーを入力
$video_id = 動画IDを入力
$url = "https://www.googleapis.com/youtube/v3/videos?id=$video_id&key=$api_key&part=snippet,contentDetails,statistics,status";
$json = file_get_contents($url);
$getData = json_decode( $json , true);
foreach((array)$getData['items'] as $key => $gDat){
    $title = $gDat['snippet']['title'];
}
// タイトルをechoする
echo $title;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでOpenCVを使うまで(OpenCV-for-PHP)

PHPでOpenCVを利用する方法はいくつかあるようですが
OpenCV-for-PHPを利用する方法を紹介します。

[mgdm/OpenCV-forPHP]
https://github.com/mgdm/OpenCV-for-PHP

注意:
OpenCV-for-PHPはOpenCVの全ての関数がラップされていないので
利用したいOpenCVの関数が準備されていない場合、ご自身でOpenCV-for-PHP
ソースコードの改修・ビルドが必要になります。

インストール前準備

インストールに必要なミドルウェアとバージョン
OpenCV 2.4.13
PHP5.2

動作確認できたOS
CentOS5
CentOS7

OpenCV2.4 インストール

OpenCVのビルドにgccとcmakeが必要なためそれぞれインストールします。
cmakeは、ソースコードcmake-2.8.12.1.tar.gzをダウンロードしてきて
ビルドしました。rpmパッケージがあればそちらを利用しても大丈夫と思います。

gccインストール
yum install gcc44 gcc44-c++
cmakeインストール
cd /usr/local/src
tar xzvf cmake-2.8.12.1.tar.gz
cd cmake-2.8.12.1
.configure
make
make install
opencvインストール

OpenCV2.1以降が要求されますが、2.4以降に実装されている機能を利用したいため
OpenCV2.4.13をダウンロードしました。
こちらもrpmパッケージを利用しても大丈夫かと思います。

https://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.13/opencv-2.4.13.zip/download

ビルドの際の注意点としてビルド用のディレクトリ(名前は何でもOK、手順ではbuildディレクトリ)を作成する必要があります。

cd /usr/local/src
unzip opencv-2.4.13.zip
cd opencv-2.4.13
mkdir build
cd build
cmake -D BUILD_EXAMPLES=YES -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local/opencv -D BUILD_NEW_PYTHON_SUPPORT=NO -D WITH_1394=OFF -D CMAKE_C_COMPILER=gcc44 -D CMAKE_CXX_COMPILER=g++44 ../
make
make install

ライブラリ及びパッケージ登録

cd /etc/ld.so.conf.d
vi opencv.conf
opencv.conf
/usr/local/opencv/lib
ldconfig
cd /usr/lib/pkgconfig
ln -s /usr/local/opencv/lib/pkgconfig/opencv.pc .

PHP5.4インストール

OpenCV-for-PHPのビルド時に
PHP5.2以降を要求されますが、こちらの環境の都合上5.4をインストール
しました。

CentOS5の場合の手順になりますが、CentOS7の場合もyumレポジトリの追加以外は
ほぼ同様になります。

インストール済みのPHPを削除

#yum remove php php-*

PHP5.4のインストール
(利用するパッケージは環境に応じて選択してください。)

rpm -ivh http://rpms.remirepo.net/enterprise/remi-release-5.rpm  
yum --enablerepo=epel install libedit
yum --enablerepo=epel install t1lib
yum install --enablerepo=remi php php-pdo php-cli php-bcmath php-mbstring php-devel php-common php-mysql php-ldap php-gd
yum install --enablerepo=remi php-mcrypt php-pear

php.ini設定
date.timezone、mbstringの文字コード等設定を行います。

OpenCV-for-PHPのインストール

gitインストール

yum install --enablerepo=epel git

OpenCV-for-PHPのビルド

cd /usr/local/src
git clone git://github.com/mgdm/OpenCV-for-PHP.git
cd OpenCV-for-PHP
phpize
.configure
make install
vi /etc/php.d/opencv.ini
opencv.ini
extension=opencv.so

php -mでopencvが存在することを確認します。

php -m
[PHP Modules]
..略..
opencv
..略..

以上になります。

使い方は、OpenCV-for-PHP(https://github.com/mgdm/OpenCV-for-PHP)
内のexampleが参考になります。

全てのOpenCV関数が揃ってるわけではないため

OpenCVのリファレンスを参照して
https://docs.opencv.org/2.4/modules/refman.html

OpenCV-for-PHPのソースコード改修を行います。


参考情報
https://qiita.com/Kogarasi/items/8c5e58821374cd7f332b

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

【PHPでECサイト④】購入履歴・購入明細

はじめに

今回は、購入履歴を実装していきます。
※当ページは、【PHPでECサイト】で作られたものを前提にしています。

バージョン

PHP:7.4.5
phpMyAdmin:5.0.2
MySQL:5.7.30

今回作成するファイル

html
- history.php
- detail.php

model
- histories.php

view
- history_view.php
- detail_view.php

テーブルの作成

sample_histories
CREATE TABLE `sample_histories` (
    `order_id` int(11) NOT NULL,
    `user_id` int(11) NOT NULL,
    `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `sample_histories`
  MODIFY `order_id` int(11) NOT NULL AUTO_INCREMENT,
  ADD PRIMARY KEY (`order_id`),
  ADD KEY `user_id` (`user_id`);
sample_details
CREATE TABLE `sample_details` (
    `order_id` int(11) NOT NULL,
    `item_id` int(11) NOT NULL,
    `amount` int(11) NOT NULL,
    `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `sample_details`
  ADD KEY `item_id` (`item_id`);

定義

const.php
define('HISTORY_URL', '/order_history.php');
define('DETAIL_URL', 'order_detail.php');

カートの購入処理に追記

carts.php
// 購入処理
function purchase_carts($db, $carts){
  if(validate_cart_purchase($carts) === false){
    return false;
  }
  // 購入後、カートの中身削除&在庫変動&購入履歴・明細にデータを挿入
  $db->beginTransaction();
  try {
    insert_history($db, $carts[0]['user_id']);
    $order_id = $db->lastInsertId();

    foreach($carts as $cart){ 
      insert_detail($db, $order_id, $cart['item_id'], $cart['price'], $cart['amount']);   
      if(update_stock($db, $cart['item_id'], $cart['stock'] - $cart['amount']) === false){
          set_error($cart['name'] . 'の購入に失敗しました。');
        }
      }
      delete_user_carts($db, $carts[0]['user_id']);
      $db->commit();
  }catch(PDOException $e){
    $db->rollback();
    throw $e;
  }
}

// 購入履歴へINSERT
function insert_history($db, $user_id){
  $sql = "
    INSERT INTO
      sample_histories(
        user_id
      )
    VALUES(?)
  ";
  return execute_query($db, $sql, array($user_id));
}

// 購入明細にINSERT
function insert_detail($db, $order_id, $item_id, $price, $amount){
  $sql = "
    INSERT INTO
      sample_details(
        order_id,
        item_id,
        price,
        amount
      )
    VALUES(?,?,?,?)
  ";
  return execute_query($db, $sql, array($order_id, $item_id, $price, $amount));
}

購入履歴

Model

histories.php
<?php
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'db.php';

// ユーザ毎の購入履歴
function get_history($db, $user_id){
    $sql = "
      SELECT
        sample_histories.order_id,
        sample_histories.created,
        SUM(sample_details.price * sample_details.amount) AS total
      FROM
        sample_histories
      JOIN
        sample_details
      ON
        sample_histories.order_id = sample_details.order_id
      WHERE
        user_id = ?
      GROUP BY
        order_id
      ORDER BY
        created desc
    ";
    return fetch_all_query($db, $sql, array($user_id));
}

View

history_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>購入履歴</title>
  </head>

  <body>
    <h1>購入履歴</h1>

    <!-- メッセージ・エラーメッセージ -->
    <?php include VIEW_PATH. 'templates/messages.php'; ?>

    <!-- 購入履歴 -->
    <?php if(!empty($histories)){ ?>
    <table>
      <thead>
        <tr>
          <th>注文番号</th>
          <th>購入日時</th>
          <th>合計金額</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
      <?php foreach($histories as $history){ ?>
        <tr>
          <td><?php print($history['order_id']); ?></td>
          <td><?php print($history['created']); ?></td>
          <td><?php print($history['total']); ?></td>
          <td>
            <form method="post" action="detail.php">
              <input type="submit" value="購入明細表示">
              <input type="hidden" name="order_id" value="<?php print($history['order_id']); ?>">
            </form>
          </td>
        </tr>
      <?php } ?>
      </tbody>
    </table>
    <?php }else{ ?>
    <p>購入履歴がありません。</p>
    <?php } ?>
  </body>
</html>

Controller

history.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';
require_once MODEL_PATH. 'items.php';
require_once MODEL_PATH. 'carts.php';
require_once MODEL_PATH. 'histories.php';

session_start();

if(is_logined() === false){
  redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);
$histories = get_history($db, $user['user_id']);

$order_id = get_post('order_id');

include_once VIEW_PATH. 'history_view.php';

購入明細

Model

histories.php
// ユーザ毎の購入明細
function get_detail($db, $order_id){
    $sql = "
      SELECT
        sample_details.price,
        sample_details.amount,
        sample_details.created,
        SUM(sample_details.price * sample_details.amount) AS subtotal,
        sample_items.name
      FROM
        sample_details
      JOIN
        sample_items
      ON
        sample_details.item_id = sample_items.item_id
      WHERE
        order_id = ?
      GROUP BY
        sample_details.price, sample_details.amount, sample_details.created, sample_items.name
    ";
    return fetch_all_query($db, $sql, array($order_id));
}

View

detail_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>購入明細</title>
  </head>

  <body>
    <h1>購入明細</h1>

    <!-- メッセージ・エラーメッセージ -->
    <?php include VIEW_PATH. 'templates/messages.php'; ?>

    <!-- 購入明細 -->
    <table>
      <thead>
        <tr>
          <th>注文番号</th>
          <th>購入日時</th>
          <th>合計金額</th>
        </tr>
      </thead>
      <tbody>
      <?php foreach($histories as $history){ ?>
        <tr>
          <td><?php print($history['order_id']); ?></td>
          <td><?php print($history['created']); ?></td>
          <td><?php print($history['total']); ?></td>
          <td>
            <form method="post" action="detail.php">
              <input type="submit" value="購入明細表示">
              <input type="hidden" name="order_id" value="<?php print($history['order_id']); ?>">
            </form>
          </td>
        </tr>
      <?php } ?>
      </tbody>
    </table>

    <!-- 購入明細 -->
    <table>
      <thead>
        <tr>
          <th>商品名</th>
          <th>価格</th>
          <th>購入数</th>
          <th>小計</th>
        </tr>
      </thead>
      <tbody>
      <?php foreach($details as $detail){ ?>
        <tr>
          <td><?php print($detail['name']); ?></td>
          <td><?php print($detail['price']); ?></td>
          <td><?php print($detail['amount']); ?></td>
          <td><?php print($detail['subtotal']); ?></td>
        </tr>
      <?php } ?>
      </tbody>
    </table>
  </body>
</html>

Controller

detail.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';
require_once MODEL_PATH. 'items.php';
require_once MODEL_PATH. 'carts.php';
require_once MODEL_PATH. 'histories.php';

session_start();

if(is_logined() === false){
  redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);
$histories = get_history($db, $user['user_id']);

$order_id = get_post('order_id');
$details = get_detail($db, $order_id);

include_once VIEW_PATH. 'detail_view.php';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのpaginationで取得したcollectionデータの更新方法

Laravelのpaginationで取得したデータは特殊なcollectionクラスであるため、
新しいcollectionクラスを返すfilterやmapのような操作では要素が更新できません。

$articles->getCollection()->transform(
            function ($article, $key) {
                 //操作
               return $article;
            }
        );

のようにgetCollectionを使ってからtransformで変形することが必要です。

ここで、注意しなくてはいけないのは、articleの下にさらにcollectionがネストしている場合[1]も、
新しいcollectionクラスを返す操作ではそのcollectionクラスが更新できないという点です。

その場合はgetCollectionなしのtransformで更新できますが、
要素を削除する場合はunset等の新しいcollectionを返さないで削除できる手段を用いる必要があります。

ただこれネストしているのでもっといい方法あるかもしれないけど

//articleの下にhashtagがネストしている
$articles->getCollection()->transform(
            function ($article, $key) {
                $article["tags"]->transform(function($tag,$key){
                        //タグ更新の場合。ちなみにここで何もreturnしなかった場合、nullが代入される。
                        return $tag;
                }

                //要素削除の場合
                foreach($article["tags"] as $key=>$tag){
                    //条件を入れる。ここは例えばタグの名前がtestの場合
                    if(($tag["name"]==='test')){
                        //条件に合致したものをunset
                        unset($article["hashtags"][$key]);
                    }
                }

                return $article;
            }
        );

[1]元データがModelでwith等を使ってクエリを作っている場合等。

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

Herokuを使って開発アプリを本番環境へデプロイする

開発環境

  • PHP 7.2
  • Laravel 5.6
  • MySQL 5.7
  • Docker
  • Nginx

おさえておきたい料金体系

基本無料。
クレカ登録をすると稼働時間の増加以外にもアドオンの追加が可能になる。

Freeプランではいくつか機能制限があります。

アカウント毎に、アプリケーションは月に550時間までの稼働時間が割り当てられる
アカウントにクレジットカードを紐付けると1000時間に増える
公開しているアプリケーションに30分間アクセスがないと、スリープモードに移行する

有料プランになると上記制限がすべてなくなる($25/月〜)

詳細はこちら

デプロイ方法

イメージ:gitのherokuリポジトリを本番環境へデプロイするだけ

0. Herokuアカウントの登録

(アカウントをもっていればスルーで)
以下ページよりアカウントを作成する
Heroku公式より

1.HerokuCLIをローカル環境にインストールする

Herokuを使うにはHeroku CLIというアプリケーションをローカル環境にインストールする必要がある。
これをインストールすることでgitを使ってデプロイが可能になる。

sudo snap install --classic heroku

※上記コマンドはLinux系のUbuntuコマンドなため各自環境に沿ったコマンドを実施する必要あり詳細は公式で確認

2. Herokuへログイン

ローカル環境で以下コマンドを入力しHerokuへログインする

heroku login -i
Enter your Heroku credentials.
Email: Herokuに登録したメールアドレスを入力
Password: Herokuに登録したパスワードを入力

Logged in as 登録したメールアドレス   //ログイン完了

3.Herokuアプリケーションの作成

デプロイしたいアプリケーションのプロジェクト直下にて下記コマンドを実行。

heroku create アプリケーション名

アプリケーション名がhttps://Herokuアプリケーション名.herokuapp.com/とドメイン名になります

Creating ⬢ example.app.. !
 ▸    Name example.app is already taken

heroku appsコマンドで作成したアプリが確認できれば作成完了

4.リモートリポジトリherokuの確認

念の為git 環境にherokuリモートリポジトリがあるか確認

git remote -v

5.Heroku設定ファイルの新規作成

プロジェクトルート直下に以下コマンドでProcfileを作成する

echo "web: vendor/bin/heroku-php-nginx -C nginx.conf public/" > Procfile

Procfileが以下のようになっているか確認

web: vendor/bin/heroku-php-nginx -C nginx.conf public/

※今回はNginxサーバーですがapacheの場合は

echo "web: vendor/bin/heroku-php-apache2 public/" > Procfile

で作成

Procfileを作業ブランチにコミット&プッシュし、ローカルmasterブランチへマージ

6.本番環境へデプロイする

いよいよデプロイといってもデプロイしたいmasterブランチへ移動して下記のコマンドを実行するだけ

git push heroku master

1分ほど待ち、下記の表記がでればデプロイ完了

remote: Verifying deploy... done.

このままだとURLにアクセスしてもエラーが出るので後ほど環境変数を設定する

URLを叩いて以下のエラーが出たら・・・

  • There's nothing here, yet. ブランチへ入力したURLが間違っている可能性があります。スペルミスを確認しましょう
  • Forbidden Procfileの設定が原因の可能性があります。こちらもスペースやスペルミス、masterブランチにProcfileがあるかを確認しましょう。

7.MySQLの導入

Herokuには複数のadd-onが用意されており、MySQLを使うための ClearDB MySQL をインストールする必要がある。
下記コマンドを実行。

heroku addons:create cleardb:ignite

これでFreeプランのMySQLが追加される。

以下のエラーが出た場合はアカウントにクレジットカードの登録が必要

▸    Please verify your account to install this add-on plan (please enter a credit card) For more
▸    information, see https://devcenter.heroku.com/categories/billing Verify now at
▸    https://heroku.com/verify

あとは環境変数を設定するだけ

環境変数の設定

.envはgitignoreされているためherokuリポジトリには反映されません。
heroku環境に環境変数を直接設定する必要があります。

  • 設定コマンド heroku config:set 環境変数名=キー
  • 確認コマンド heroku config

APP_KEYの設定

DockerPHPコンテナ内プロジェクトルート直下で

$ php artisan key:generate --show

内容をコピーしてローカル環境プロジェクトルート直下で下記コマンドを入力

heroku config:set APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

設定した APP_KEY が設定されていればOK。

MySQLの設定

heroku configで環境変数を確認。
cleardbのadd-onを追加しているためCLEARDB_DATABASE_URL:という環境変数が自動で設定されている。

CLEARDB_DATABASE_URL:  mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true

これをもとに各環境変数を設定

$ heroku config:set DB_CONNECTION=mysql
$ heroku config:set DB_DATABASE='<データベース名>'
$ heroku config:set DB_USERNAME='<ユーザー名>'
$ heroku config:set DB_PASSWORD='<パスワード>'
$ heroku config:set DB_HOSTNAME='<ホスト名>'
$ heroku config:set DB_PORT='3306'

その他の環境変数

APP_URL

$ heroku config set:https://アプリケーション名.herokuapp.com

slack認証系

SLACK_KEY=ローカル環境の`.env`の記述と同じ
SLACK_REDIRECT_URI=ローカル環境の`.env`の記述と同じ
SLACK_SECRET=ローカル環境の`.env`の記述と同じ

S3系

AWS_ACCESS_KEY_ID=ローカル環境の`.env`の記述と同じ
AWS_BUCKET=ローカル環境の`.env`の記述と同じ
AWS_DEFAULT_REGION=ローカル環境の`.env`の記述と同じ
AWS_SECRET_ACCESS_KEY=ローカル環境の`.env`の記述と同じ

その他の設定方法

Herokuのビルドに関する設定

buildpackを追加する

  • Herokuダッシュボード内の上部メニューのSettingsを選択

  • Add buildpackの選択

  • node.jsの選択

  • Save changesで完了

  • PHPの選択

  • Save changesで完了

* heroku/nodejs
* heroku/php

が表示されれば、buildpackの追加は完了

データベースのマイグレーション

Herokuからデータベース(MySql)に接続できるよう設定済みですが、まだテーブルを作成していません。
そこで、Laravelのマイグレーションコマンドを使ってテーブルを作成します。
以下コマンドを実行してください。

$ heroku run php artisan migrate —seed

heroku run (実行したいコマンド)で、Heroku上でコマンドを実行できます。
コマンドを実行すると、以下が表示されるので、yesと入力してエンターキーを押してください。

Do you really wish to run this command? (yes/no) [no]:

以下のように各テーブルのマイグレーションが完了した旨のメッセージが表示されれば問題ありません。

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.1 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.04 seconds)

Mysql接続コマンド

mysql -h <DB_HOSTNAME> -u <DB_USERNAME> -p -D <DB_DATABASE>

作業内容を本番環境へ反映したいとき

ローカルのmasterブランチに変更内容を反映させgit push heroku masterをするだけ

Heroku環境内に入るコマンド

heroku run bash

共同開発者の追加

  • Heroku にログインし、任意のサービスを選択
  • 上部メニューのaccessを選択
  • Add collaboratorを押下し、共同開発者のアカウントのメールアドレスを入力後、save changeを押下
  • 招待者のメールアドレスが追加されていることを確認
  • 招待者はメールの案内に沿ってHerokuにアクセス

これでデプロイやHeroku環境系の操作が可能になります。

参考

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

【PHP】配列keyの挙動について

配列のkeyはint型かstring型の2種類

整数を配列のkeyに指定した場合、int型として扱われるため注意が必要です。

$array1 = [
    '1983' => 'ファミコン',
    '1989' => 'ゲームボーイ',
    '1994' => 'プレイステーション',
];

var_dump(array_keys($array1));

// 実行結果
array(3) {
  [0]=>
  int(1983)
  [1]=>
  int(1989)
  [2]=>
  int(1994)
}

stringで指定していた場合でもkeyにするとint型になる

// keyをstring型で指定しておく
$key1 = (string)'1983';
$key2 = (string)'1989';
$key3 = (string)'1994';

var_dump($key1);
var_dump($key2);
var_dump($key3);

//var_dumpの結果
string(4) "1983"
string(4) "1989"
string(4) "1994"


//stringで指定した変数をkeyにして配列を作成
$array1 = [
    $key1 =>'ファミコン',
    $key2 => 'ゲームボーイ',
    $key3 => 'プレイステーション',
];

// stringで指定していた年数がint型になっている。
var_dump(array_keys($array1));

array(3) {
  [0]=>
  int(1983)
  [1]=>
  int(1989)
  [2]=>
  int(1994)
}

float型、8進数の場合

$array = [
    1.2 => 'foo',
    2.6 => 'bar',
    0332 => 'hoge',
];

var_dump($array);


// 小数点切り捨ての10進数へ変換される
array(3) {
  [1]=>
  string(4) "foo"
  [2]=>
  string(4) "bar"
  [218]=>
  string(4) "hoge"
}

整数にしたくない場合

  • 文字列を連結しておく

    例:「S_01」、「02A」など

  • ハッシュ化しておく

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

PHP基礎④

 echoの省略

PHP
<?php echo ?>
↓ ↓ ↓
<?= ?>
# 省略できる!

 ランダムなメッセージの表示

PHP
mt_rand(1,3);
# 1以上、3以下のランダムな整数値を作成

 悪意にあるコードを防げる

PHP
<?= htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); ?>

「htmlspecialchars」に(変数,ENT_QUOTES,文字コード)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでDBに接続、CRUDを利用して掲示板を作る② Readについて

前回(https://qiita.com/progterry/items/2f82b82a5118c16010a0)はPHPでDBに接続、CRUDのCreateの機能を実装するところまでお話をしました。
今回はRead機能について書いていこうと思います。

そもそもReadとは?

Readとは、掲示板の機能で必須とされるCRUDの中の1つです。
CRUDはそれぞれ、
Create
Read
Update
Delete
の頭文字を取っています。それぞれの単語の具体的な機能に関しては前回の記事を参照。

実際のコード

<?php 
$dsn = 'mysql:dbname=test_bbs2; host=localhost';
$username= 'root';
$password= 'root';
try{
    $dbh = new PDO($dsn, $username, $password);
    echo "接続成功";
    echo "<br>";
} catch(PDOException $e){
    echo "失敗:" . $e->getMessage() . "\n";
    exit();
}

//SELECT文を変数に格納
$sql = "SELECT * FROM contacts";

//SQLステートメントを実行し、結果を変数に格納
$stmt = $dbh->query($sql);

//foreach文で配列の中身を一行ずつ出力
foreach ($stmt as $row){
    echo "名前:" . $row['submit_name'] . ':' . "発言:" . $row['submit_content'];
    echo '<br>';
}
?>
<a href="index.php">トップへ戻る</a>

こまかく見ていきましょう。

DB接続までは前回記事と同様のため省略します。
毎回DB接続を行うんだ、ということさえ覚えておいたらいいと思います。

次に、

//SELECT文を変数に格納
$sql = "SELECT * FROM contacts";

この部分は変数sqlに、データベースのcontactsテーブルから取り出した内容をいれています。
SELECT * FROM contacts
この部分はPHPではなく、SQLの書き方になっているので注意です。
アスタリスク* マークは、「すべてを」という意味になっています。
このコードは、contactsテーブルから、制限なくすべてのデータを取り出すという意味です。

次に、

//SQLステートメントを実行し、結果を変数に格納
$stmt = $dbh->query($sql);

この部分をみます。

ここでは右辺の処理を、stmtに代入しています。
CRUD処理を探しているとやたらとこの$stmtが出てきます。stmtはおそらくstatementの略で、よく使われる変数名みたいです。

$dbh->query($sql);

では、queryで、$sqlの中のSQLを実行し、オブジェクトとして返します。
参考(https://www.php.net/manual/ja/pdo.query.php)

言い換えると、このqueryを行うことでsqlが目的の形になるということです。

最後に、

//foreach文で配列の中身を一行ずつ出力
foreach ($stmt as $row){

この部分で、出力します。$stmtを$rowにしていますが、$rowの部分の変数名は何でもいいです。その後の部分とつじつまが合えば。

以上、このような形でRead機能が実装できました。

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

PHPの定数について

定数とは

宣言時に決めた値を再定義できない

PHPで定数を定義する方法2つ

define()で定数を定義する

define('定数名', '値');

 PHP7〜 配列定数を定義できるようになった。

// 定数をクラス外に定義
define('CONSTANT', ['key1' => 'hoge', 'key2' => 'fuga']);
class Sample
{
    function printConst()
    {
        // クラス内から定数にアクセスする場合
        print_r(CONSTANT);
    }
}
$sample = new Sample();
$sample->printConst();                 // CONSTANTが出力される
// クラス外から定数にアクセスする場合(同じ)
print_r(CONSTANT);                     // CONSTANTが出力される

constで定数を定義する

const '定数名' = '値';

 
PHP5.3 以上で使える。
PHP7〜 配列定数を定義できるようになった。
const はクラス定数を定義できる。

class Sample
{
    // 定数をクラス内に定義
    const CONSTANT = ['key1' => 'hoge', 'key2' => 'fuga'];
    function printConst()
    {
        // クラス内から定数にアクセスする場合
        print_r(self::CONSTANT);
    }
}
$sample = new Sample();
$sample->printConst();          // CONSTANTが出力される
// クラス外から定数にアクセスする場合
print_r(Sample::CONSTANT);      // CONSTANTが出力される

define()const の違い

  • define() は関数なので、const の方が高速に処理される。
  • define() は変数や関数の戻り値を使えて、const では使えない。
$func = function(){
    return '定数';
};
define('CONST_DEFINE', $func());
echo CONST_DEFINE;  // 定数が出力される
// 定数式に無効なものが含まれているという以下のエラーで終了する
// Fatal error: Constant expression contains invalid 
const CONST_CONST = $func();
echo CONST_CONST;   
  • define()if 文や function の中でも使える。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

amazonlinux2のhttpd + php7.4で「Uncaught Error: Call to undefined function simplexml_load_string」が出た場合の対処法

エラー

amazonlinux2で動くwordpressにあるテーマをインストールしたら以下のエラーに遭遇。

Uncaught Error: Call to undefined function simplexml_load_string

ググった結果、どうやらSimpleXMLモジュールが入っていないとのこと。

動作環境

$ php -v
PHP 7.4.7 (cli) (built: Jul  2 2020 23:17:59) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

$ amazon-linux-extras | grep enabled
 24  epel=latest              enabled      [ =7.11  =stable ]
 42  php7.4=latest            enabled      [ =stable ]

$ yum list installed | grep php
php-cli.x86_64                        7.4.7-1.amzn2                  @amzn2extra-php7.4
php-common.x86_64                     7.4.7-1.amzn2                  @amzn2extra-php7.4
php-fpm.x86_64                        7.4.7-1.amzn2                  @amzn2extra-php7.4
php-json.x86_64                       7.4.7-1.amzn2                  @amzn2extra-php7.4
php-mbstring.x86_64                   7.4.7-1.amzn2                  @amzn2extra-php7.4
php-mysqlnd.x86_64                    7.4.7-1.amzn2                  @amzn2extra-php7.4
php-pdo.x86_64                        7.4.7-1.amzn2                  @amzn2extra-php7.4

対処方法

amazon-linux-extrasでepelを有効化して、php-xmlをインストールする。

sudo yum install php-xml -y

SimpleXMLが読み込まれていることを確認する。

$ php -i | grep -i simplexml
/etc/php.d/20-simplexml.ini
SimpleXML
SimpleXML support => enabled

それから、あるWebページでモジュールの読み込み順番が大切との記述を発見。

この原因がなかなかわからず、権限変えたり色々やりましたが、結論としてモジュールを読み込む順番が大切でした。
simplexml.so を実行するためには、別の拡張モジュールが必要になるため、そのモジュールよりも後に拡張モジュールを読み込まないとエラーになる、ということのようです。

from SimpleXMLがインストール出来ない時に試す方法 | 東京都|システム開発会社|株式会社ベスピィ

そのため、simplexmlモジュールの読み込み順番の調整する。

mv /etc/php.d/{20,40}-simplexml.ini

最後に、php-fpmとhttpdを再起動する

sudo systemctl restart php-fpm
sudo systemctl restart httpd

これでエラーが解消された。

初め、php-fpmが動いていることに気づかず、httpdだけ再起動しても反映されなくて焦った。

参考

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

【Xdebug】VSCODE+PHP Laravel+Windows+Xamppでデバッグ実行

環境

Windows10 x64
VisualStudio Code 1.47.3
Xampp v3.2.4 (PHP 7.4.7)
VSCODEプラグイン PHP Debug v1.13.0(本記事でインストール~設定します)

Xdebugインストールと設定

①VSCODEでPHP Debugをインストール

image.png

②php.iniに下記を追記

※ C:\xampp\php\ext\php_xdebug.dllが存在しているか確認してください

C:\xampp\php.ini
zend_extension = "C:\xampp\php\ext\php_xdebug.dll"
[XDebug]
xdebug.remote_autostart = 1
xdebug.profiler_append = 0
xdebug.profiler_enable = 0
xdebug.profiler_enable_trigger = 0
xdebug.profiler_output_dir = "c:\xampp\tmp"
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_host = "127.0.0.1"
xdebug.remote_log="c:\xampp\tmp\xdebug.txt"
xdebug.remote_port = 9000
xdebug.trace_output_dir = "c:\xampp\tmp"
xdebug.remote_cookie_expire_time = 36000

③「上部メニュー→実行→構成を追加」からlaunch.jsonを下記のようにする。

既に実行情報が記述されている場合は良い感じにマージする

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000
        }
    ]
}

デバッグ実行してみる

①左メニュー→デバッグ(再生アイコンに虫が乗っているアイコン)を選択して、構成が「Listen for Xdebug」が選択されているのを確認

image.png

②あとはブレークポイントを仕掛けて「F5」でデバッグ実行

image.png

Laravelでも動作確認済み

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