20190411のPHPに関する記事は22件です。

初心者teamlabのオンラインスキルアップ課題step1をやってみた(コード初心者向け記事)

dockerって何とか、DBとの連携でコード上で具体的にどうするのとか、初心者的にはイメージがつかないほど良くわからないので、teamlabさんのオンラインスキルアップ課題をやってみた。

こちらから課題にアクセスします。

STEP1の内容は下記のようになっています。

  1. インターネットのしくみ
  2. WEBサーバについて
  3. HTMLとCSSについて 4-a. 【Windows】Dockerと開発環境の作り方 4-b. 【Mac】Dockerと開発環境の作り方
  4. PHPを書いてみる
  5. PHPでGET/POSTをやってみる
  6. データベースについて
  7. SQLを書いてみる
  8. PHPでデータベースを操作してみる
  9. 10. GitとGitHubについて

4-b:【Mac】Dockerと開発環境の作り方

1,2,3はわかった気になってとりあえず進めて、まずは4のDockerのセットアップから入ります。

docker-compose -v

dockerがちゃんとインストールされているか上記のコマンドで確かめます。

docker-compose up -d

teamlabさんのサイトからダウンロードしたフォルダに遷移して、上のコマンドを叩くとコンテナを作成して、起動します。

※Dockerのコマンドに関してはこちら

スクリーンショット 2019-04-09 15.36.00.png
なんかいろいろエラーは起きたのですが、無事?doneになっています。
「skillup-php-step1-master」をダウンロードして解凍したフォルダに移動したいけど移動できない!っていう場合にですが、フォルダさえ探せれば、フォルダごとターミナルに落としてあげることでpathが表示されるので、あとは先頭にcdつけるだけでフォルダに移動できるはずです。

1-6:PHPでGET/POSTをやってみる

1-5は普通にコピペでコード貼って、Dockerで立ち上げたlocalhostにアクセスするだけなので、はしおります。

まず、エディターがない人は適当なエディターをインストールしてください。
(ぼくはsublimeをつかっています。)

とりあえず、課題のpostだけやってみました

index.html1

<!DOCTYPE html>
<html>
  <head>
<meta charset="UTF-8" />
<title>POSTのサンプル</title>
</head>
<body>
<p>コメントしてください。</p>
    <form method="POST" action="index.php">
      <input name="comment" />
      <input type="submit" value="送信" />
     </form>
 </body>
</html> 

php1

<?php
  //commentがPOSTされているなら
  if(isset($_POST["comment"])){
    //エスケープしてから表示
    $comment = htmlspecialchars($_POST["comment"]);
    print("あなたのコメントは「 ${comment} 」です。");
  } else {
?>

最後にこの課題があります。

[課題]送信する内容を変更してみよう
掲示板に必要な情報としては少なくとも名前と本文が必要です。名前も送信し、受け取るように変更してみましょう。余力のある人はタイトル・文字色・メールアドレスなども追加してみましょう。

なんかとりえあえず複数のデータを送ってみてほしいとのことだったので、僕は次のようにしました。

index.html2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>

<style> 
.cell{ 
  padding: 1px 15px; 
  margin: 1px 30px; 
  line-height: 0.1; 
} </style> 

    <h1>「同窓会の参加有無」</h1>
    <div class=cell> 
      <form method="POST" action="index.php">
        <ol>
         <li><p>氏名:<input name="name"  /></p></li>
          <li><p>メール:<input name="mail" /></p></li>
          <li><p>参加可否:<input name="comment" /></p></li>
      <input type="submit" value="送信" style=         "width:150px;,height:100px;"/>
    </div> 
    </form>
  </body>
</html>

php 2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>
    <?php
      //commentがPOSTされているなら
          if(isset($_POST["mail"],$_POST["name"],$_POST["comment"])){
        //エスケープしてから表示
        $mails = htmlspecialchars($_POST["mail"]);
        $names = htmlspecialchars($_POST["name"]);
        $comments = htmlspecialchars($_POST["comment"]);
        print("あなたの入力した情報は以下になります。<br/>");
        print("名前:${mails}<br/>");
        print("メール:${names}<br/>");
        print("参加有無:${comments}<br/>");
      }else{
        print("情報の入力が正しくありません。");
        }
    ?>
  </body>
</html>

震えるくらいクソコードであることはなんとなくですが、自覚してます....

STEP1-9.PHPでデータベースを操作してみる

これやってみたのですが、すごい勉強になりました。
phpでデータベースをこうやっていじるんだ!みたいなのがなんとなくですが理解できます。

こちらも特に説明を読んでいけば問題なく進めました。

唯一初心者目線でひっかかりそうなのが、データベースにアクセスするときに自分でつけたデータベースの名前じゃないと、エラーになるので注意が必要。
僕の場合はdbname=「TEST」ではなくて「text」でしたので最初はエラってました。

実行結果1

スクリーンショット 2019-04-09 19.22.48.png

先程はDBのname,textを全て表示したが今度はJohnにしぼったものだけを表示させます。

実行コード

<?php
$dsn = 'pgsql:dbname=test;host=pgsql;port=5432';
$user = 'postgres';
$pass = 'example';

try {
  // DBに接続する
  $dbh = new PDO($dsn, $user, $pass);
//$query_result = $dbh->query('SELECT * FROM test_comments');
  $sth_select = $dbh->prepare('SELECT * FROM test_comments WHERE name = ?');
// prepareメソッド(INSERT)最下部でinsertするname/textを定義
  $sth = $dbh->prepare('INSERT INTO test_comments (name, text) VALUES (?, ?)');
 // DBを切断する
  $dbh = null;
} catch (PDOException $e) {
    // 接続にエラーが発生した場合ここに入る
    print "DB ERROR: " . $e->getMessage() . "<br/>";
    die();
}
?>
<?php
//これでdbのnameとtextに新たに文字列が加わる
  $name = "John";
  $text = "Power to the People";
  $sth->execute(array($name, $text));
?>
<?php
  $name = "John";
  $sth_select->execute(array($name));
  //実行したクエリから実行結果を取得している
  $prepare_result = $sth_select->fetchAll();
  foreach($prepare_result as $row) {
    print $row["name"] . ": " . $row["text"] . "<br/>";
  }
  $sth_select->execute(array($name));
?>

実行結果2

スクリーンショット 2019-04-09 20.34.46.png

とりあえず、無事表示されました。

次は気が向いたらSTEP2をやります。

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

初心者がteamlabのオンラインスキルアップ課題step1をやってみた(コード初心者向け記事)

dockerって何とか、DBとの連携でコード上で具体的にどうするのとか、初心者的にはイメージがつかないほど良くわからないので、teamlabさんのオンラインスキルアップ課題をやってみた。

こちらから課題にアクセスします。

STEP1の内容は下記のようになっています。

  1. インターネットのしくみ
  2. WEBサーバについて
  3. HTMLとCSSについて 4-a. 【Windows】Dockerと開発環境の作り方 4-b. 【Mac】Dockerと開発環境の作り方
  4. PHPを書いてみる
  5. PHPでGET/POSTをやってみる
  6. データベースについて
  7. SQLを書いてみる
  8. PHPでデータベースを操作してみる
  9. 10. GitとGitHubについて

4-b:【Mac】Dockerと開発環境の作り方

1,2,3はわかった気になってとりあえず進めて、まずは4のDockerのセットアップから入ります。

docker-compose -v

dockerがちゃんとインストールされているか上記のコマンドで確かめます。

docker-compose up -d

teamlabさんのサイトからダウンロードしたフォルダに遷移して、上のコマンドを叩くとコンテナを作成して、起動します。

※Dockerのコマンドに関してはこちら

スクリーンショット 2019-04-09 15.36.00.png
なんかいろいろエラーは起きたのですが、無事?doneになっています。
「skillup-php-step1-master」をダウンロードして解凍したフォルダに移動したいけど移動できない!っていう場合にですが、フォルダさえ探せれば、フォルダごとターミナルに落としてあげることでpathが表示されるので、あとは先頭にcdつけるだけでフォルダに移動できるはずです。

1-6:PHPでGET/POSTをやってみる

1-5は普通にコピペでコード貼って、Dockerで立ち上げたlocalhostにアクセスするだけなので、はしおります。

まず、エディターがない人は適当なエディターをインストールしてください。
(ぼくはsublimeをつかっています。)

とりあえず、課題のpostだけやってみました

index.html1

<!DOCTYPE html>
<html>
  <head>
<meta charset="UTF-8" />
<title>POSTのサンプル</title>
</head>
<body>
<p>コメントしてください。</p>
    <form method="POST" action="index.php">
      <input name="comment" />
      <input type="submit" value="送信" />
     </form>
 </body>
</html> 

php1

<?php
  //commentがPOSTされているなら
  if(isset($_POST["comment"])){
    //エスケープしてから表示
    $comment = htmlspecialchars($_POST["comment"]);
    print("あなたのコメントは「 ${comment} 」です。");
  } else {
?>

最後にこの課題があります。

[課題]送信する内容を変更してみよう
掲示板に必要な情報としては少なくとも名前と本文が必要です。名前も送信し、受け取るように変更してみましょう。余力のある人はタイトル・文字色・メールアドレスなども追加してみましょう。

なんかとりえあえず複数のデータを送ってみてほしいとのことだったので、僕は次のようにしました。

index.html2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>

<style> 
.cell{ 
  padding: 1px 15px; 
  margin: 1px 30px; 
  line-height: 0.1; 
} </style> 

    <h1>「同窓会の参加有無」</h1>
    <div class=cell> 
      <form method="POST" action="index.php">
        <ol>
         <li><p>氏名:<input name="name"  /></p></li>
          <li><p>メール:<input name="mail" /></p></li>
          <li><p>参加可否:<input name="comment" /></p></li>
      <input type="submit" value="送信" style=         "width:150px;,height:100px;"/>
    </div> 
    </form>
  </body>
</html>

php 2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>
    <?php
      //commentがPOSTされているなら
          if(isset($_POST["mail"],$_POST["name"],$_POST["comment"])){
        //エスケープしてから表示
        $mails = htmlspecialchars($_POST["mail"]);
        $names = htmlspecialchars($_POST["name"]);
        $comments = htmlspecialchars($_POST["comment"]);
        print("あなたの入力した情報は以下になります。<br/>");
        print("名前:${mails}<br/>");
        print("メール:${names}<br/>");
        print("参加有無:${comments}<br/>");
      }else{
        print("情報の入力が正しくありません。");
        }
    ?>
  </body>
</html>

震えるくらいクソコードであることはなんとなくですが、自覚してます....

STEP1-9.PHPでデータベースを操作してみる

これやってみたのですが、すごい勉強になりました。
phpでデータベースをこうやっていじるんだ!みたいなのがなんとなくですが理解できます。

こちらも特に説明を読んでいけば問題なく進めました。

唯一初心者目線でひっかかりそうなのが、データベースにアクセスするときに自分でつけたデータベースの名前じゃないと、エラーになるので注意が必要。
僕の場合はdbname=「TEST」ではなくて「text」でしたので最初はエラってました。

実行結果1

スクリーンショット 2019-04-09 19.22.48.png

先程はDBのname,textを全て表示したが今度はJohnにしぼったものだけを表示させます。

実行コード

<?php
$dsn = 'pgsql:dbname=test;host=pgsql;port=5432';
$user = 'postgres';
$pass = 'example';

try {
  // DBに接続する
  $dbh = new PDO($dsn, $user, $pass);
//$query_result = $dbh->query('SELECT * FROM test_comments');
  $sth_select = $dbh->prepare('SELECT * FROM test_comments WHERE name = ?');
// prepareメソッド(INSERT)最下部でinsertするname/textを定義
  $sth = $dbh->prepare('INSERT INTO test_comments (name, text) VALUES (?, ?)');
 // DBを切断する
  $dbh = null;
} catch (PDOException $e) {
    // 接続にエラーが発生した場合ここに入る
    print "DB ERROR: " . $e->getMessage() . "<br/>";
    die();
}
?>
<?php
//これでdbのnameとtextに新たに文字列が加わる
  $name = "John";
  $text = "Power to the People";
  $sth->execute(array($name, $text));
?>
<?php
  $name = "John";
  $sth_select->execute(array($name));
  //実行したクエリから実行結果を取得している
  $prepare_result = $sth_select->fetchAll();
  foreach($prepare_result as $row) {
    print $row["name"] . ": " . $row["text"] . "<br/>";
  }
  $sth_select->execute(array($name));
?>

実行結果2

スクリーンショット 2019-04-09 20.34.46.png

とりあえず、無事表示されました。

次は気が向いたらSTEP2をやります。

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

PHPを少しだけ学んだ話

プロゲートでPHPの導入となる部分を学んだ。

1:出力するためにはechoの入力が必要
スクリーンショット 2019-04-11 22.17.21.png

2:PHPには、計算をさせることもできる
スクリーンショット 2019-04-11 22.23.49.png

3:PHPは、頭に$をつけるだけで、変数化することができる
スクリーンショット 2019-04-11 22.25.39.png

4:変数に名前をつける時の注意点
基本的に、ローマ字、日本語はNG
英語で書くようにする
スクリーンショット 2019-04-11 22.27.55.png

5:変数に数を足す場合
省略形
スクリーンショット 2019-04-11 22.30.32.png
スクリーンショット 2019-04-11 22.30.29.png

6:変数展開のダブルクオーテーションとシングルクオーテーションの違い
スクリーンショット 2019-04-11 22.32.24.png

7:if文<論理的な思考力が身につく>
スクリーンショット 2019-04-11 22.32.30.png
 スクリーンショット 2019-04-11 22.32.39.png

8:条件のまとめ
スクリーンショット 2019-04-11 22.37.15.png
スクリーンショット 2019-04-11 22.37.05.png
スクリーンショット 2019-04-11 22.37.19.png
スクリーンショット 2019-04-11 22.37.26.png

続きは、次の投稿で。

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

【PHP8】short_open_tagにさよなら?

Deprecate PHP Short open tagsというRFCが投票フェーズに入りました。
投票期間は2019/04/10から2019/04/24、採択には投票数の2/3+1の賛成が必要です。

Deprecate PHP Short open tags

Short open tagsとは

PHPの開始を示すタグは<?php、もしくは<?=です。
後者は<?php echoとほぼ同じであり、今回のRFCでは一切影響を受けません。

php.iniの設定でshort_open_tagを有効にすると、PHPの開始タグを<?と省略して書けるようになります。

// 通常
<?php
    echo 'hoge';

// short_open_tag=1ならこう書ける
<?
    echo 'hoge';

Short open tagsのデフォルト値

PHP7.3時点では1、つまり初期設定で有効になっている。
ただし、多くの実装系(XAMPPとか)では0と無効にされているようだ。

Short open tagsの問題点

うっかりXMLをコピペすると死ぬ。

<?xml version="1.0"?>
<?php
    echo 'XMLの中身';

このコードは、short_open_tagが有効な場合syntax errorのE_PARSEになります。
short_open_tagが無効であれば普通にXMLが出力されます。

このように、設定によって動作が根本的に変わってしまうので、あまりよろしくありません。

Remove alternative PHP tags

PHP5時代は、これ以外にも<%<script language="php">といった記述でPHPに入ることが可能でした。
それらはPHP7移行の際に、Remove alternative PHP tagsのRFCで削除されました。

が、なぜか<?だけは残ったままでした。

RFC

<?をPHP7.4でDeprecatedにし、PHP8で削除しよう、という提案です。
これによってPHPの開始タグは<?php<?=の2種類だけになり、とてもすっきりします。

プルリク

PHP7.4ではshort_open_tagのデフォルト値を1から0に変更します。
また有効にするとDirective 'short_open_tag' is deprecatedのE_DEPRECATEDが発生するようになります。

PHP8では設定項目そのものが削除され、有効にすることができなくなります。

internal

「え?short_open_tag削除して常に有効になるんじゃないの?」
「わざわざ削除するメリットが見当たらない」
「今PHP7で書いてる人はほぼ影響受けないだろうけど、未だにPHP5の人はアプデの障壁がさらに高くなるよ。」
「Facebookでアンケ取ってみたら96%が削除賛成だったよ」
<?=が削除されないんだったらかまわない」

投票

2019/04/12時点では、PHP7.4でのDeprecateは賛成17反対9で、このままだと却下されます。
PHP8でのRemoveは賛成19反対9で、このままだと受理されます。

感想

PHP7.4でのDeprecateが却下され、かつPHP8でのRemoveが受理されると、それまで使えてたのにPHP8でいきなり削除ってなるんだけどどうなんだろう。
その場合はPHP8でDeprecateになるのかな?

個人的には<?は全く使ってないので消してもらって全くかまわないんだけど、その結果vendor配下からDeprecatedが大量発生ってなったりすると困りますね。
かつてPEARでもAssigning the return value of new by reference is deprecated湧き潰しできず死ぬという事件がありましたが、それの再来にならないことを祈りましょう。

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

Remove your all hunger and complete all your needs by call girls

Conserve your prolonged length romance by reigniting the spark and creating 'special' time for every other. Just because you're not ready to see every single other type, it doesn't mean that you can't have a date together. It comes again to artistic thinking. There are so several video clip chat solutions accessible now that eating a meal or watching a motion picture collectively actually is attainable, even when you happen to be a bunch of miles apart.

These are just some basic techniques that you can set into motion swiftly to help save your great length connection. But of the program, it might not be that uncomplicated. You may perhaps have trust difficulties or be finding it definitely challenging to open up to your lover and say just how you experience. If that's the scenario, you may want to consider trying to get assistance from an outside source that will assist you the two to re-link and convey back the spark that you've misplaced.

We have ultimate call girl mobile number and photo

If you are hunger for romance and want to fulfill your desire then you are just one step away. With Call girl you can easily remove your all hunger and complete all your needs and demands with. Feel the ultimate satisfaction and turn you are all dream fantasies into reality. We have ultimate call girl mobile number and photo and these call girls are educated and look like high profile models. These girls are passionate and you can go anywhere with them because they are classy. You can also feel the dating and night dining pleasure with. Get high profile call girl names and mobile number at the cheap rate and get unlimited pleasure with them and make your life interesting. They are naughty and always ready for fun because they are open minded and give you proper and complete fun for the full night and you will never forget these moments.    

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

Ajaxでテキストをgzipして他のパラメータと一緒にPOSTするテスト

クライアント側でgzipをした後、バイナリで送信してみた。
gzipはこちらを使用してみる
https://www.npmjs.com/package/zlibjs

バイナリはファイルとして送信するといいらしい。

クライアント(javascript)
let a = "クライアント側でgzipをした後、バイナリで送信してみた。";
console.log("元データ:"+encodeURIComponent(a).replace(/%../g,"x").length+"Bytes");

var gzip = new Zlib.Gzip(unicode2utf8_uint8array(a));
var compressed = gzip.compress();
console.log("圧縮:"+compressed.length+"Bytes");

let fd = new FormData();
fd.append('hoge', "aaa");
fd.append('hoge2', "bbb");
fd.append('hoge3', new Blob([compressed], {type: "application/octet-binary"}));

 $.ajax({url: "https://xxxx.com/hoge.php",
  type: "POST",
  contentType:false,
  processData: false,
  cache: false,
  data: fd,
  }).then(function(data, textStatus, jqXHR) {
     alert(data);
  },function(jqXHR, textStatus, errorThrown) {});
}

unicode2utf8_uint8arrayは、https://qiita.com/ukyo/items/1626defd020b2157e6bfから。
ありがとうございます!

送信後は、サーバーにファイルとして保存されている。

サーバー側(PHP)
header('Content-Type: text/html; charset=UTF-8');
header("Access-Control-Allow-Origin: *");

echo "hoge:".$_POST["hoge"];
echo "hoge2:".$_POST["hoge2"];

var_dump($_FILES["hoge3"]);

echo "[". gzdecode(file_get_contents($_FILES["hoge3"]["tmp_name"])) ."]";

テスト結果
元データ:82Bytes
圧縮:98Bytes
hoge:aaa
hoge2:bbb
array(5) {
  ["name"]=>
  string(4) "blob"
  ["type"]=>
  string(24) "application/octet-binary"
  ["tmp_name"]=>
  string(14) "/tmp/phpHlyZxg"
  ["error"]=>
  int(0)
  ["size"]=>
  int(98)
}
[
クライアント側でgzipをした後、バイナリで送信してみた。

]

普通のパラメータとバイナリが一緒に送られていますね。
gzip後のサイズと送信後に受け取ったサイトが同じなので、そのまま送られているはず。たぶん。

よく見ると圧縮後のサイズが増えとるよ…

送信前にバイト数を確認して、そのまま送るか圧縮するか判断するべきですね。

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

MockeryのshouldReceiveはonceをつけないと意味がない

はじめに

  • Mockeryでハマったので他の人がハマらないようにメモ書きを残す

通常サンプル

MyClass.php
class MyClass {
    public function get_message($name) {
        return $this->get_hello() . $name;
    }

    public function get_hello() {
        return 'Hello ';
    }
}
MyClassTest.php
class MyClassTest {
    public function test_get_message(){
        $object = new MyClass();
        $result = $object->get_message('TEST');
        $expect = 'Hello TEST';
        $this->assertSame($expect, $result);
    }
}

普通に使う場合はこんな感じで書けるかと思います

該当のソース

MyClassTest.php
class MyClassTest {
    public function test_get_message(){
        $testDouble = \Mockery::mock(MyClass::class)->makePartial();
        $testDouble->shouldReceive('dummy_method');

        $result = $testDouble->get_message('TEST');
        $expect = 'Hello TEST';
        $this->assertSame($expect, $result);
    }
}

dummy_methodという存在しないメソッドがshouldReceive(呼び出されていること)をテストしています。
dummy_methodは存在しないし呼び出してもいないので当然テストは失敗する、と思いたかったんですが実はこのテスト成功してしまいます。

対策

以下のように書けばうまくいきます

MyClassTest.php
class MyClassTest {
    public function test_get_message(){
        $testDouble = \Mockery::mock(MyClass::class)->makePartial();
        $testDouble->shouldReceive('dummy_method')->once();

        $result = $testDouble->get_message('TEST');
        $expect = 'Hello TEST';
        $this->assertSame($expect, $result);
    }
}
E                                                                   1 / 1 (100%)

Time: 12.39 seconds, Memory: 26.00MB

There was 1 error:

1) XXX::test_get_message
Mockery\Exception\InvalidCountException: Method dummy_method(<Any Arguments>) from Mockery_0_XXX should be called
 exactly 1 times but called 0 times.

1回呼び出されることを想定していて0回呼び出しだったので正しく失敗してくれています

まとめ

この動きが正しいのかよく分かりません
もしかしたら使い方が間違っているだけなのかもしれないので有識者の方コメントをお願いします
ひとまずは「呼び出されたこと」をテストしたい場合は必ずonceなどの回数指定のものを設定しましょう

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

Laravelのクエリビルダを使い回す際の注意

はじめに

  • Laravel初心者向け
  • 自分がハマったので他の人がハマらないように残す

該当ソース

        $company_where = Company::where(['is_valid' => '1']);
        $company1 = $company_where->where(['id' => '1'])->toSql();
        var_dump($company1);
        // "select * from `companies` where (`is_valid` = ?) and (`id` = ?)"
        $company2 = $company_where->where(['id' => '2'])->toSql();
        var_dump($company2);
        // "select * from `companies` where (`is_valid` = ?) and (`id` = ?) and (`id` = ?)"

やりたいことはis_validが1の企業(有効な企業)の中でid=1のもの、2のものを取得したい
is_validの条件は共通なので変数に持ち、それを使いまわそうと思って書いたコードなのですが見事にハマりました
id=1は無事に取れるが、id=2が何故かとれない…レコードはあるしis_valid=1なのに何故…

解説

もうソースにコメントで書いてありますがtoSqlで発行されるクエリに注目です

-- 1つ目
select * from `companies` where (`is_valid` = ?) and (`id` = ?)
-- 2つめ
select * from `companies` where (`is_valid` = ?) and (`id` = ?) and (`id` = ?)

2番目のクエリにidの検索条件がANDで2つついているのが分かるかと思います
1つ目のクエリのwhereでid=1の検索条件がつき、2つ目のクエリで上書きされるかと思いきや同じカラムでもAND検索で追加される仕様のようでした
(まぁメソッドチェーンでAND検索になるんだから当然っちゃ当然なんですが。。。)

解決策

大人しくインスタンスを分けましょう

        $company_where1 = Company::where(['is_valid' => '1']);
        $company1 = $company_where1->where(['id' => '1'])->toSql();
        var_dump($company1);
        // "select * from `companies` where (`is_valid` = ?) and (`id` = ?)"
        $company_where2 = Company::where(['is_valid' => '1']);
        $company2 = $company_where2->where(['id' => '2'])->toSql();
        var_dump($company2);
        // "select * from `companies` where (`is_valid` = ?) and (`id` = ?)"

もしくはクエリを分けなくても良い方法を考えましょう

        $companies = Company::where(['is_valid' => '1'])
            ->whereIn('id', ['1', '2'])
            ->toSql();
        var_dump($companies);
        // "select * from `companies` where (`is_valid` = ?) and `id` in (?, ?)"

おわりに

自分と同じ状況でハマっている人が解決したなら良かったです

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

この名前空間ってどこにあるんだ?

Laravel5.7に入れたbarryvdh/laravel-debugbarのコードを読んでいた時に、use DebugBar\OpenHandler;と書いてあって「namespace DebugBar/なんかどこにも書いてないぞ?」と思っていました。

ですがcomposerが生成したコードを読んでいたら、/vendor/composer/autoload_classmap.phpに書いてありました。

autoload_classmap.php
'DebugBar\\OpenHandler' => $vendorDir . '/maximebf/debugbar/src/DebugBar/OpenHandler.php',

DebugBarから始まるのはmaximebf/debugbarのようですね。
これでコードリーディングが捗るぞい!

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

DjangoのtemplateとLaravelのbladeの構文をちょっと比較してみた

まとめ

  • 両者非常に似通っていて、同じ感覚で使用できる

  • Laravelでは親の@yieldに子の@sectionが展開されるが、Djangoでは親子ともにblockで表現する

  • Laravelでは、@extendslayouts/app.blade.phpを使用する際に、layouts.appと指定する(layouts/appではない)のが少し戸惑った

Djangoの場合

親のテンプレート

templates/base.html
<!DOCTYPE html>
<html>
<head>
  ...
</head>
<body>
  <header>
    ...
  </header>
  <main>
    {% block content %}
    {% endblock %}
  </main>
  <footer>
    ...
  </footer>
</body>

子のテンプレート

templates/hoge/fuga.html
{% extends 'base.html' %}

{% block content %}
<div>
  {% if items %}
    <ul>
       {% for item in items %}
         <li>{{ item }}</li>
       {% endfor %}
    </ul>
  {% endif %}
</div>
{% endblock %}

Laravelの場合

親のテンプレート

resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
<head>
  ...
</head>
<body>
  <header>
    ...
  </header>
  <main>
    @yield('content')
  </main>
  <footer>
    ...
  </footer>
</body>

子のテンプレート

resources/views/hoge.blade.php
@extends('layouts.app')

@section('content')
<div>
  @if (count($items)) > 0)
    <ul>
       @foreach( $items->all() as $item)
         <li>{{ $item }}</li>
       @endforeach
    </ul>
  @endif
</div>
@endsection
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CakePHP2系でLOAD DATA LOCAL INFILE のエラー

CSVファイルをDBにぶち込むのに LOAD DATA LOCAL INFILEを使いたかった

MySQLクライアントでやってみたら難なくできたのにPHPでやろうとするとこんなエラー

PDOStatement::execute(): LOAD DATA LOCAL INFILE forbidden

使えないの...><

PDOの設定を変えたいので、
/Config/database.phpを書き換えます。

databse.php
public $default = array(
        'datasource' => 'hoge',
        'persistent' => false,
        'host' => 'hoge',
        'port' => '',
        'login' => 'root',
        'password' => 'password',
        'database' => 'dbs',
        'schema' => '',
        'prefix' => '',
        'encoding' => 'utf8',
        //以下を追加
        "flags" => array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => PDO::MYSQL_ATTR_LOCAL_INFILE
        )
    );

でよろし。

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

独学でプログラミングを学んでいる僕が使っているUdemy教材

テスターやネットワークのエンジニアとしての傍ら、12月よりプログラミングの独学を始めた僕が使っている教材について書いていこうと思う。僕が使っている教材について書いてきます。
その前に、独学よりプログラミングスクール行けばいいじゃんって言う人に向けて僕が何故独学しているのかを記しておこう。

(1)お金がない

色々音楽機材を買いすぎてお金がなかった(笑)今でも毎月5,6万円ほど払っています(笑)エンジニアとしてお金稼ぎます‼

(2)仮に通ってもどうせ独学しなければいけない気がした。

「ruby使ってtwitterに似たアプリ作ります!」位のレベル感で50万円近く払うのって結構自分的には嫌だったんだよね。
色々サービスがあるのに、勉強する環境を構築するのにここまでお金を支払う必要性があるのかと感じたっていう所がでかい。


+

やってきた・やっている教材

(1)【世界で30万人が受講】フルスタック・Webエンジニア講座(2017最新版)
https://www.udemy.com/share/1000IwBEYSd1dXRng=/

 HTML5,CSS3,Javascript,jQuery,Bootstrap4,Wordpress,PHP,MySQL,API,モバイルアプリ,Pythonなど結構詰め込んでいる。
 PCに環境を構築するためとか、プログラミングについての基本的な考え方について学ぶためには良いと思います。
 文系出身の僕には関数とか何で使うかちょっと理解できない所あったけど、JS,PHP,Pythonで何回か説明されるうちに分かるようになってきたところあるし。一方で、様々な分野を取り上げている弊害と言えますが、深く学ぶ分にはおススメ出来ないです。それぞれについてのUdemy教材を買うか、書籍を買っていきましょう!

(2)【JavaScript】作って覚える!未経験者が一流WEBエンジニアになる為のノウハウを完全網羅!
https://www.udemy.com/share/100ddoBEYSd1dXRng=/

 Javascriptについて。簡単なアプリプリ作成まで書いてあるので、初心者の方にとっては良いと思う。パーと一通り流し見して、後はオリジナルアプリ制作とか模倣みたいな感じで良いのでは。

(3)Learn to Program in Javascript: Beginner to Pro
https://www.udemy.com/share/1004oqBEYSd1dXRng=/

言語Javascript。英語教材、無料で購入した覚えがある。プログラミング自体英語だし、英語の方がテンポ感あって、無駄がなくて良いよね、と感じることはまれにあります。

(4)手を動かしながら2週間で学ぶ AWS 基本から応用まで
https://www.udemy.com/share/100taiBEYSd1dXRng=/
今勉強している。

(5)【最短30分でできる!】Laravel5.7入門: 初心者でも簡単! ブラウザだけでLaravelを使ったWeb開発!
https://www.udemy.com/share/100wDkBEYSd1dXRng=/
Larabelに興味あって、無料だったので、視聴。Paiza Cloud上でやっているから、別途環境構築は必要だけど、MVCを理解するのには、講座自体の時間も短いし良い気がする。

(6)実践Webサイトコーディング講座 | HTML5とCSS3を使って、カフェのサイトやWebメディアサイトを作ってみよう
https://www.udemy.com/share/1005eiBEYSd1dXRng=/
HTMLとCSSについて。photoshopを適宜使ってデザインを構築しているので、Webエンジニア向けというより、Webデザイナー入門といった感じの講座。初学者の段階では良いかも。講座に関しては、繰り返し説明している所が多々あるので、冗長に感じる人もいるかもしれない。

(7)ゼロからはじめる Dockerによるアプリケーション実行環境構築
https://www.udemy.com/share/100gMSBEYSd1dXRng=/

(8)フロントエンドエンジニアのためのReact・Reduxアプリケーション開発入門

(9)非エンジニアでも学べるPHP入門講座
https://www.udemy.com/share/10094kBEYSd1dXRng=/

(10)8 Beautiful Ruby on Rails Apps in 30 Days & TDD - Immersive
https://www.udemy.com/share/10017ABEYSd1dXRng=/

(11)初めてでもできるWordPressで作る人気の出るホームページ作成
https://www.udemy.com/share/100k3ABEYSd1dXRng=/

(12)よくわかるRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう
https://www.udemy.com/share/100AcWBEYSd1dXRng=/

(13)フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座
https://www.udemy.com/share/1003s4BEYSd1dXRng=/

まだ書きかけなので、その適宜リライトしながら投稿していこうと思う。

下記のようになれるように頑張っていこうと思います。では。

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

Laravelでテーブルのカラムを削除する方法

Laravelでテーブルのカラムを削除する

  1. migrationファイルを作る
  2. 中身を記述する
  3. 実行!

migrationファイルを作る

`hoge' には該当のテーブル名を入れてください

migrationファイル作成
php artisan make:migration drop_column_hoges_column --table=hoges

テーブルカラムの削除処理を記述

upの方に削除処理を記述し、downの方にはカラム追加処理を記述しましょう。

以下の書き方で piyo カラムを削除することができます

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DropColumnHogesTable extends Migration
{
  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::table('hoges', function (Blueprint $table) {
      $table->dropColumn('piyo');
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    Schema::table('hoges', function (Blueprint $table) {
      $table->boolean('piyo')->default(false);
    });
  }
}

テーブルのカラム削除をLaravelで実行する

実行時はこちら

php artisan migrate

そしてrollbackも実行しておきましょう。

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

PHP dotenvの読み込み方が変わってた

dotenv使ってみようと思って日本語の入門記事をいくつかあさりながら、書いてあるとおりに

$dotenv = new Dotenv(__DIR__);
$dotenv->load();

みたいにやってみたけど、どうにも動かない。
サーバーのエラーログを見ると

PHP Catchable fatal error:  Argument 1 passed to Dotenv\\Dotenv::__construct() must be an instance of Dotenv\\Loader, string given, called in /hoge/fuga/file.php on line 17 and defined in /hoge/fuga/vendor/vlucas/phpdotenv/src/Dotenv.php on line 31

みたいなエラーが残っていた。
ん〜〜〜〜〜?と思ってGithub見に行くと、Readmeにこんなことが書いてある

UPGRADING FROM V2
(中略)
Consequently, you will need to replace any occurrences of new Dotenv(...) with Dotenv::create(...), since our new native constructor takes a Loader instance now, so that it can be truly customized if required. (後略)

雑訳: アップデートで仕様が変わったからnew Dotenv(...)じゃなくてDotenv::create(...)を使ってね

ということで、その下のUsageに載っているように

$dotenv = Dotenv\Dotenv::create(__DIR__);
$dotenv->load();

としてあげると動いた。dotenvみたいに昔からあるやつでも急に読み込み方が変わったりするんですねー。やっぱり原典に当たるのは大事。勉強になりました。

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

php-master-changes 2019-04-10

今日は新シリアライズ機構でのメモリリーク修正とエラーメッセージの修正、SPL のシリアライズ化可能オブジェクトの新シリアライズ機構への対応、JIT のレジスタ割当判定コードの簡素化と不要なジャンプ命令生成の削除、SPL の不要コードの削除、imap の実装修正、pgsql の use after free の修正とテストの修正、typo の修正、zlib / posix のテストの修正、Windows でファイルキャッシュ有効時の phar:// の include の扱い修正、Windows でのパスの正規化処理修正、Windows で opcache.protect_memory 用に VirtualProtect() を使うようにする修正があった!

2019-04-10

nikic: Fix leak on error in new serialization mechanism

nikic: Fixed bug #77873

dstogov: Don't split basic block after RECV, if function checks type hints

dstogov: Removed too strict register allocation constraint.

nikic: Fix bug #77866: Port Serializable SPL classes to use __unserialize()

nikic: Remove redundant $this args in SplObjectStorage implementation

nikic: Use release for regex in imap

dstogov: Eliminated unreachable jumps

nikic: Fix use after free on pg_close() of default connection

rovast: fix a spell mistake

nikic: Fix pgsql use after free trying to reuse closed connection

nikic: Fix 29nb_async_connect.phpt

nikic: Make zlib include_path tests more robust

nikic: Remove posix_getlogin() check from posix_getpwnam() test

weltling: Fix phar:// include handling with file cache

weltling: Ensure double slashes are replaced by the path normalization

nikic: Support VirtualProtect for opcache.protect_memory

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

PHPからAccessDBに接続する

今どき・・・な感じですが、ある案件でPHPからAccessDBに接続して情報を取得する必要があったのでそのメモです。
ネットで調べてもあんまり情報がなかったので残します。
単にPHP-->Accessっていうパターンが少ないだけだとは思うのですが。

環境

PHP7.2
Access 2010(2017でもOK)

接続

PDOで接続します。

db.php
define('ACCESS_DB_DRIVER',   '{Microsoft Access Driver (*.mdb, *.accdb)}');
define('ACCESS_DBQ',         'C:\\test\\test_be.accdb');
define('ACCESS_DB_USER',     '');
define('ACCESS_DB_PASSWORD', '');

$objPdo = new PDO("odbc:Driver=" . ACCESS_DB_DRIVER . ";Dbq=" . ACCESS_DBQ . "; Uid=" . ACCESS_DB_USER . "; Pwd=" . ACCESS_DB_PASSWORD . " ;");

Driver

データソースのドライバーの部分を指定します。
20190410_datasource.png

DBQ

AccessDB(***.accdb)のフルパスを指定します。
ここで指定するのはAccessで「データベースの分割」を行ったデータベースのみのファイルになります。

DB_USER、DB_PASSWORD

今回は使用していません。

クエリ実行

test.php
<?php

ini_set('INTERNAL_ENCODING', 'UTF-8');

define('ACCESS_DB_DRIVER',   '{Microsoft Access Driver (*.mdb, *.accdb)}');
define('ACCESS_DBQ',         'C:\\test\\test_be.accdb');
define('ACCESS_DB_USER',     '');
define('ACCESS_DB_PASSWORD', '');

$objPdo = new PDO("odbc:Driver=" . ACCESS_DB_DRIVER . ";Dbq=" . ACCESS_DBQ . "; Uid=" . ACCESS_DB_USER . "; Pwd=" . ACCESS_DB_PASSWORD . " ;");

$sql = "SELECT test_column FROM tbl_test WHERE column = 10;";
$sql = convertSJIS($sql); // 文字コード変換。AccessはSJIS-winなので変換する必要あり
$stmt = $objPdo->prepare($sql);
$result = $stmt->execute();
if ($result !== false) {
    $arrData = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($arrData as $arrDetail) {
        $strValue = convertUTF8($arrDetail['test_column']); // SJIS-winからUTF-8に変換する
    }
}

/**
 * 文字コードを変換する
 * UTF-8 --> SJIS-win 
 */
function convertSJIS($strTmp) {
    return mb_convert_encoding($strTmp, "SJIS-win", "UTF-8");
}

/**
 * 文字コードを変換する
 * SJIS-win --> UTF-8
 */
function convertUTF8($strTmp) {
    return mb_convert_encoding($strTmp, "UTF-8", "SJIS-win");
}

ポイントとしてはAccessの文字コードがSJIS-winなので、クエリ発行時はUTF-8 --> SJIS-winへ、取得したデータはSJIS-win-->UTF-8に変換する必要があります。
取得するカラム名が日本語の場合はそれも変換する必要があるので、fetchAllしたあとにループですべて変換する必要がありますね。

おまけ

このコードをテストしていたときに気づいたのですが、mb_convert_encoding関数の第1引数に連想配列を渡すと、中身をすべて変換してくれるんですね。
PHP5.4ではエラーになったのでPHP7あたりからOKになったのかな?
公式にも配列OKとはなっていないので、正式なコードではないんだとは思います。

test.php
$arrData = $stmt->fetchAll(PDO::FETCH_ASSOC);
$arrData = convertUTF8($arrData);// これで配列の中身全部変換できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでテンプレートPDFを出力する

PHPでPDF化の実装パターン

  1. HTMLからPDFにする
  2. テンプレートとなるPDFに文字を出力する

今回は2の話。

実装

TCPDFとFPDIを組み合わせて実装
composer require tecnickcom/tcpdf
composer require setasign/fpdi

これで使えるようになった。

以下は実際に使用した設定内容。

controller.php
public function generatePdf()
    {
        /*
         * PDFの初期設定
         */
        $pdf = new Fpdi('L', 'mm', 'A4');

        // テンプレートPDFの設定
        $template_path = public_path('assets/pdfs/template.pdf');
        $pdf->setSourceFile($template_path);

        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdf->SetAutoPageBreak(false);
        $pdf->SetAutoPageBreak(false);
        $pdf->SetMargins(0, 0, 0);
        $pdf->addPage();

        $font = new TCPDF_FONTS();

        //フォントの設定
        $font_bold = $font->addTTFfont(storage_path('fonts/ipag.ufm'));
        $pdf->setFont($font_bold, '', 30);

        $page = $pdf->importPage(1);
        $pdf->useTemplate($page);

        /*
         * PDFに表示するデータ
         */
        //ID
        $pdf->SetXY(100, 49);
        $pdf->Write(0, '1'));

        //名前
        $pdf->SetXY(100, 61);
        $pdf->Write(1, 'やまだたろう');


        $pdf->output();

        return Redirect::back();
    }

1個だけ詰まった点は、SetMarginsを設定しているのに余白の調整が上手くいかない。

そもそもA4サイズ(210 × 297)で作成されていないんじゃないのかと思って変換したら、ビンゴ!

当たり前だが、HTMLのようにレイアウトの調整は出来ないからPDF側で正確に調整してもらう必要がある。

HTMLからPDFにする際はレイアウトの制限が多くて、手間だったがテンプレートがある方は文字の出力位置に手間取るぐらいでわかってしまえばサクッと実装できるのがいいね。

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

Doctrine Transaction Unexpected Nesting Level Increasing

作業環境

PHP5.3 + Fuel 1.8.1 + Doctrine

問題

業務上、1つのトランザクションの中に、下記のPseudoコードの感じで処理を実行し、最後纏めてコミットしたい。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
…
DB登録処理呼び出し関数1($em);
DBクエリ処理呼び出し関数1($em);
繰り返し処理{
…
    DB登録処理呼び出し関数2($em);
    DB更新処理呼び出し関数1($em);
…
}
…
DB更新処理呼び出し関数2($em);
$em->commit();

が、コミットはどうしても成功せず、3日を悩まされました。

調査経過

たまたま、Doctrineトランザクション管理のNestingレベルを見ようとして、
Doctrineトランザクション公式参照⇒https://www.doctrine-project...

⇓をDB変更や登録関数の実行前後に貼って、驚きの結果がわかりました。
echo "TransactionNestingLevel Before Insert :".$em->getConnection()->getTransactionNestingLevel()."\n";

繰り返し処理の中で、Nestingレベルの数値がどんどん上がる!!!

TransactionNestingLevel Before Insert :11
TransactionNestingLevel After Insert :11
TransactionNestingLevel Before Insert :41
TransactionNestingLevel After Insert :41
TransactionNestingLevel Before Insert :215
TransactionNestingLevel After Insert :215
TransactionNestingLevel Before Insert :301
TransactionNestingLevel After Insert :301
TransactionNestingLevel Before Insert :345
TransactionNestingLevel After Insert :345
TransactionNestingLevel Before Insert :395
TransactionNestingLevel After Insert :395
TransactionNestingLevel Before Insert :461
TransactionNestingLevel After Insert :461

Are you kidding me!!なんじゃと

トランザクション階層が461階層に⇓

トランザクション階層1
    トランザクション階層2
        トランザクション階層3
                    …
                                        トランザクション階層461

変更をDBに反映するために、$em->commit();460回実行せよ!!だからコミットできないか!
なんじゃと

待って!今回操作対象データが230件。最後に461件というのは、230の2倍がプラスされたんじゃないか?都合がいい数値に手がかりがありそう!!

いろいろ実験を経て、下記の感じのコードを実行すると、不具合の箇所が特定できました。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
echo "TransactionNestingLevel beginTransaction:".$em->getConnection()->getTransactionNestingLevel()."\n";
$info = array();
$info[0] = array(
    "Sequence"=>5
    …
);
$info[1] = array(
    "Sequence"=>6
    …
);
echo "TransactionNestingLevel After Setting array :".$em->getConnection()->getTransactionNestingLevel()."\n";

結果:

TransactionNestingLevel beginTransaction :1
TransactionNestingLevel After Setting array :3

結論

配列代入処理が一回実行で、DoctrineのトランザクションNestingレベルが1 Plusされることが分かりました。だからか!前230件の2倍Plusされるのも、登録関数が呼ばれる前に、データを配列で渡すために、配列代入処理を2回実施しましたからです。

わけわからねぇ!!!!!

なんじゃと

対策

はい!退避策として、繰り返し処理の中のDB変更処理を全部外に出して、$em->getConnection()->setAutoCommit(false);で、繰り返し処理でPlusされたトランザクションレベルを1に戻した。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
…
DB登録処理呼び出し関数1($em);
DBクエリ処理呼び出し関数1($em);
繰り返し処理{
…
…
}
…
$em->getConnection()->setAutoCommit(false);
DB登録処理呼び出し関数2($em);
DB更新処理呼び出し関数1($em);
…
DB更新処理呼び出し関数2($em);
$em->commit();

こんなつまらないことで、3日間が蒸発した!!!
死ね

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

Doctrine Transaction Unexpected Nesting Level Up

作業環境

PHP5.3 + Fuel 1.8.1 + Doctrine

問題

業務上、1つのトランザクションの中に、下記のPseudoコードの感じで処理を実行し、最後纏めてコミットしたい。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
…
DB登録処理呼び出し関数1($em);
DBクエリ処理呼び出し関数1($em);
繰り返し処理{
…
    DB登録処理呼び出し関数2($em);
    DB更新処理呼び出し関数1($em);
…
}
…
DB更新処理呼び出し関数2($em);
$em->commit();

が、コミットはどうしても成功せず、3日を悩まされました。

調査経過

たまたま、Doctrineトランザクション管理のNestingレベルを見ようとして、
Doctrineトランザクション公式参照⇒https://www.doctrine-project...

⇓をDB変更や登録関数の実行前後に貼って、驚きの結果がわかりました。
echo "TransactionNestingLevel Before Insert :".$em->getConnection()->getTransactionNestingLevel()."\n";

繰り返し処理の中で、Nestingレベルの数値がどんどん上がる!!!

TransactionNestingLevel Before Insert :11
TransactionNestingLevel After Insert :11
TransactionNestingLevel Before Insert :41
TransactionNestingLevel After Insert :41
TransactionNestingLevel Before Insert :215
TransactionNestingLevel After Insert :215
TransactionNestingLevel Before Insert :301
TransactionNestingLevel After Insert :301
TransactionNestingLevel Before Insert :345
TransactionNestingLevel After Insert :345
TransactionNestingLevel Before Insert :395
TransactionNestingLevel After Insert :395
TransactionNestingLevel Before Insert :461
TransactionNestingLevel After Insert :461

Are you kidding me!!なんじゃと

トランザクション階層が461階層に⇓

トランスフォーマー階層1
    トランスフォーマー階層2
        トランスフォーマー階層3
                    …
                                        トランスフォーマー階層461

変更をDBに反映するために、$em->commit();460回実行せよ!!だからコミットできないか!
なんじゃと

待って!今回操作対象データが230件。最後に461件というのは、230の2倍がプラスされたんじゃないか?都合がいい数値に手がかりがありそう!!

いろいろ実験を経て、下記の感じのコードを実行すると、不具合の箇所が特定できました。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
echo "TransactionNestingLevel beginTransaction:".$em->getConnection()->getTransactionNestingLevel()."\n";
$info = array();
$info[0] = array(
    "Sequence"=>5
);
$info[1] = array(
    "Sequence"=>6
);
echo "TransactionNestingLevel After Setting array :".$em->getConnection()->getTransactionNestingLevel()."\n";

結果:

TransactionNestingLevel beginTransaction :1
TransactionNestingLevel After Setting array :3

結論

配列代入処理が一回実行で、DoctrineのトランザクションNestingレベルが1 Plusされることが分かりました。だからか!前230件の2倍Plusされるのも、登録関数が呼ばれる前に、データを配列で渡すために、配列代入処理を2回実施しましたからです。

わけわからねぇ!!!!!

なんじゃと

対策

はい!退避策として、繰り返し処理の中のDB変更処理を全部外に出して、$em->getConnection()->setAutoCommit(false);で、繰り返し処理でPlusされたトランザクションレベルを1に戻した。

$em = \Fuel\Doctrine::manager("default");
$em->getConnection()->beginTransaction();
…
DB登録処理呼び出し関数1($em);
DBクエリ処理呼び出し関数1($em);
繰り返し処理{
…
…
}
…
$em->getConnection()->setAutoCommit(false);
DB登録処理呼び出し関数2($em);
DB更新処理呼び出し関数1($em);
…
DB更新処理呼び出し関数2($em);
$em->commit();

こんなつまらないことで、3日間が蒸発した!!!
死ね

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

PHP キャメルケース、パスカルケース、スネークケース変更(自分用)

<?php

echo("Namecase.require");
class Namecase {

    const UPPER_SNAKE_CASE = 1;
    const SNAKE_CASE = 2;
    const CAMEL_CASE = 3;
    const PASCAL_CASE = 4;
    const UPPER_CAMEL_CASE = 4;

    public static function getNamecase($name) {
        if (preg_match("/^([A-Z]+)(_[A-Z]+)*$/", $name)) {
            return self::UPPER_SNAKE_CASE;
        } else if (preg_match("/^([a-z]+)(_[a-z]+)*$/", $name)) {
            return self::SNAKE_CASE;
        } else if (preg_match("/^([a-z]+)([A-Z][a-z]*)*$/", $name)) {
            return self::CAMEL_CASE;
        } else if (preg_match("/^([A-Z][a-z]*)+$/", $name)) {
            return self::PASCAL_CASE;
        } else {
            return false;
        }
    }

    public static function splitWords($name) {
        switch (self::getNamecase($name)) {
            case self::UPPER_SNAKE_CASE :
                return explode("_", strtolower($name));
            case self::SNAKE_CASE :
                return explode("_", $name);
            case self::CAMEL_CASE :
                return array_map(function($word) {
                    return strtolower($word);
                }, preg_split("/(?=[A-Z])/", $name));
            case self::PASCAL_CASE :
                return array_map(function($word) {
                    return strtolower($word);
                }, preg_split("/(?<!^)(?=[A-Z])/", $name));
            default :
                return false;
        }
    }

    public static function toUpperSnakeCase($name) {
        $words = self::splitWords($name);
        return strtoupper(implode("_", $words));
    }

    public static function toSnakeCase($name) {
        $words = self::splitWords($name);
        return strtolower(implode("_", $words));
    }

    public static function toCamelCase($name) {
        $words = self::splitWords($name);
        $firstWord = array_shift($words);
        $words = array_map(function($word) {
            return ucfirst($word);
        }, $words);
        return $firstWord . implode("", $words);
    }

    public static function toPascalCase($name) {
        $words = self::splitWords($name);
        $words = array_map(function($word) {
            return ucfirst($word);
        }, $words);
        return implode("", $words);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

元号に関する個人サービスを作る上での感想など

はじめに

2019年3月、ひっそりとGENGOHというサービスをリリースしました。

開発は2018年8月くらいに開始し、予定では2018年の12月中にリリースする予定でしたが、途中のモチベーション低下による放置期間を経て、なんとか半年以上かけてやっと完成にこぎつけてました。。。

ということで、この記事では元号を返すAPIサービス「GENGOH」を作るにあたっての考えや採用した技術、その他雑多なことを書きたいと思います。

なぜこのサービスを作ろうと思ったか

久々に個人でサービス開発をしようかなーと思い、今までストックしていた「いつかは作りたいサービス」の中から、一番簡単に出来そうな元号変換サービスを作ることにしました。

開発環境と採用技術を考える

ほぼ自己満で、それほどアクセス数を見込めるようなサービスでも無いと思たので、できるだけコストがかからない環境と、自分の学習も兼ねた技術を採用し構築することにしました。

開発環境

  • Windows10
  • xampp
  • Chrome
  • git
  • Visual Studio Code
  • 秀丸

使用言語やフレームワークなど

ホスティングなど

  • さくらのレンタルサーバ

サービスや提供内容を考える

2019年5月に改元することが決まりましたが、WEBのシステムでも「西暦」を使う場合と「和暦」を使う場合がそれぞれがあり、都度対応するのが面倒だなーと思いいつか作るリストにピックアップしていましたが、この度日の目を浴びる事となりました。
ちなみに今回は、純粋に個人の趣味の範囲で考えたので、マネタイズなどは一切考えていません。(サービスが失敗する原因とどこかの記事にありましたが、ホントその通りだと思います)
利用者数が増えて、管理にコストが掛かるようになったら少し考えようと思います。

兎にも角にも、元号をもっと使いやすくできたらいいなと思っています。

元号や暦などについて調べる

元号を改めて調べてみると元号は「645年」の「大化」から始まっていることがわかりました。
645年というと歴史の授業で習う「大化の改新」があった年なのですが、昔は「蘇我入鹿が暗殺された」程度の認識だったのが、今回改めて調べてみると至る経緯など含め多くを学ぶ機会になりました。
そのせいで、Wikipedia回りが止まらず、開発が全く進まないということも多くありました。
少し寄り道して、日本の暦を調べていくうちに、「天地明察」なども読んでしまいました。
また、西暦には「ユリウス暦」と「グレゴリオ暦」があり、それぞれどのように日付を管理するかなども結構悩み未だこれだという管理方法を決められていません。

ドメインを決めてサーバーを選定する

元号を扱うので、jpドメインを取りたいなと調べたところ、「gengoh.jp」が空いていたので、それに決定しました。
サーバーは特にこだわりはないので、とにかく安い「さくらのレンタルサーバ」にしました。

設計する

個人開発なので、細かい設計などは特になしで取り掛かりました。
規模や難易度も高くないので、なんとかなっています。

データベースを用意する

これもほぼ思いつきでデータベース構成を作りました。
規模も小さいので今段階ではあまり困りませんでした。
ただし、将来的にアカウント管理する段階になったら、少し考えようと思います。

実装

モチベーションの影響をもろに受けながら、仕事では考えられないくらいの長い開発期間を経て実装を勧めました。。。
特にこだわった点や注意すべき点もないのですが、普段使わない三項演算子を使ったり、インデントを2タブにしたりと、少しやんちゃなコードになっています。
テストも適当なので、潜在的なバグも結構ありそうですが、あまり気にせず進めています。

良かった点

  • vue.jsがとにかく便利、PHPよりもjs書いている方が多い気がする
  • 改めて歴史や暦について学ぶ機会となり勉強になった
  • 久々に作ったので楽しかった
  • 発展型のサービスアイディアを思いついた

困った点

  • モチベーションが続かない
  • 納期がないのでダレる
  • MySQL系のドライバが、PHPのバージョンの影響を受けて面倒だった
  • 資金不足で良い感じの環境で作れない
  • Vue Routerの使い所がまだわからない&必要性を感じない
  • 「ユリウス暦」と「グレゴリオ暦」のどちらで管理するほうがいいのかわからない年代がある
  • アクセス数が少なく、利用者がほぼ皆無

まとめ

個人サービスなので、勉強も兼ねてとにかく気楽にやっていきたいなーと思っています。
今後はご意見やご要望などを反映しながら適度にバージョンアップなどをしつつ、サービスを拡充していく予定です。

興味があればGENGOHを使ってみて下さい。

ご意見ご要望はTwitterまで

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

TwilioでPHPからSMSと電話機能を試しました

はじめに

初めまして、k.s.ロジャースの西谷です。

今回はPHPからSMSメッセージを送信する要件があったため、調査を行いました。
当初はプログラムから携帯電話にメッセージを送信する方法は検討もつかない状態でした。
Twilioを使えばいいと聞いて調べた結果、APIですべて出来るようになっており、びっくりしています。

間違い・助言等があればコメントにてお知らせいただけたらと思います。

Twilioの導入

SMSの送信や電話を代わりに掛けてくれるサービスです。
こちらから登録できます。
登録後500円の無料枠を頂けるのでお気軽に試すことが出来ます。

登録後、チュートリアル通りに電話番号を購入します。
スクリーンショット 2019-04-09 22.04.45.png

購入後に表示される、SIDとAUTHTOKENはAPI側で利用します。

PHP側のSDK導入は簡単で以下コマンドを実行するだけです。

composer require twilio/sdk

SMSを送信する

SMSの送信はリファレンス通りで簡単にできます。

送信先の電話番号はE.164形式で指定します。
日本の場合、080-xxxx-xxxx+8180xxxxxxxxとなります。

use Twilio\Rest\Client;

$account_sid = '{SID}';
$auth_token = '{AUTHTOKEN}';
$twilio_number = '{購入した電話番号}';

$client = new Client($account_sid, $auth_token);
$client->messages->create(
    '{送信先の番号}', 
    [
        'from' => $twilio_number,
        'body' => '送信テスト'
    ]
);

account_sid, auth_token, twilio_numberはダッシュボードから確認することが出来ます。

スクリーンショット 2019-04-10 2.37.37.png

SMSに対して返信する

取得した電話番号にSMSが送信された場合は返信することができます。
こちらはTwilio側からWebhookで返答内容を取得するため、ローカル開発の場合はngrockなどで開発環境を公開する必要があります。

全体の手順としては次のようになっています。
1. 受信内容に対して返信するAPIを作成する
2. 返信APIを公開する
3. Twilioで返信APIのURLを登録する
4. 電話番号に対してSMSを送信しると、返信APIの内容がSMSで返信される

返信APIの実装

$_REQUEST['Body']で送信内容を取得できます。
これがあれば、API側で作り込めば大抵なことは出来そうな気がします。

use Twilio\TwiML\MessagingResponse;

$body = $_REQUEST['Body'];        
header("content-type: text/xml");

$response = new MessagingResponse();
$response->message(
    "「{$body}」に対する返信です。"
);

echo $response;

TwilioにWebhook登録

Twilioの設定画面の電話番号からWebhookを設定出来ます。
こちらに先程実装したAPIのURLを登録します。

スクリーンショット 2019-04-10 22.57.03.png

登録後に、購入した電話番号にSMSを送信するとAPI通りの返信を受け取れます!
スクリーンショット 2019-04-11 0.13.21.png

電話関連の機能

電話を掛けられる場合は自動音声で応答することが出来ます。
また、APIから電話をかけることも出来ます。

電話に応答する

こちらはSMSの返信と同じです。
まずは応答のAPIを実装します。

header("content-type: text/xml");

$response = new VoiceResponse();
$response->say(
    "test message",
    ["voice" => "alice"]
);

echo $response;

こちらのURLをTwilioに登録するだけです。
スクリーンショット 2019-04-11 0.42.19.png

後は購入した電話番号に電話を掛けると自動音声で応答してくれると思います!

電話をかける

APIを実行すると指定番号に電話が掛かります。
こちらもE.164で電話番号を指定します。

電話内容はurlで指定することが出来ます。(サンプルでは上記の応答メッセージを指定しています)
また、methodを指定しない場合はPOSTでデータ取得を行う点にご注意ください。

use Twilio\Rest\Client;

$account_sid = '{SID}';
$auth_token = '{AUTHTOKEN}';
$twilio_number = '{購入した電話番号}';

$client = new Client($this->account_sid, $this->auth_token);
$client->account->calls->create(
    "{電話番号}",
    $this->twilio_number,
    [
        "url" => "http://f1683917.ngrok.io/api/test/voice.xml",
        "method" => "GET"
    ]
);

おわりに

今回はTwilioでSMSと電話機能について調査しました。
応用すれば、緊急時に自動で電話を掛けたりと面白いことができそうです。(コストはかかりますが。。。)

他にも録音やルール設定など多くの機能がありますので、機会があればこれらも触ってみようと思います。

Wantedlyでもブログ投稿してます

Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers

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