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

Win10でapacheサーバーに、PHPをインストール

PHPのダウンロード

以下サイトからダウンロード、今回試したのは、PHP8

https://www.php.net/

自分がサーバーにしてるマシンが32bitなので、
Download -> Windows downloads
VS16 x86 Thread Safe (2021-Jan-06 00:27:59) の zip をダウンロード

PHPをインストール

ダウンロードした zip を展開して、任意のパスに配置
C:php とかに配置

環境パスの設定

PHPを配置したパスを、環境パスの path に設定する

PHPの設定ファイルを調整する

iniファイルを作成

../php/php.ini-developmentが展開したフォルダに含まれているので、
複製して php.iniにリネームしてから編集する

タイムゾーンを設定

; date.timezone = 
 ↓
date.timezone = "Asia/Tokyo"

エクステンションフォルダパスを設定

; extension_dir = "./"
 ↓
extension_dir = "C:/php/ext"

その他に必要なエクステンションを設定

;extension=mbstring
;extension=gd
 ↓
extension=mbstring
extension=gd

apacheの設定にPHPを追加する

../Apache24/conf/httpd.conf をテキストエディタで編集する
最下部に書きを追記

LoadModule php_module "C:/php/php8apache2_4.dll"
PHPIniDir "C:/php/"
AddHandler application/x-httpd-php .php

動作チェック

test.phpの作成

../Apache24/htdocs 以下に test.php の名称でファイルを作成する
テキストファイルの中身は、以下の phpinfo関数の呼び出しを記述

<? php
phpinfo();
?>

test.phpをブラウザから呼び出す (ポート番号80 の場合)

chorome ブラウザなどで、 localhost:80/test.php
で、phpinfoの内容が、ブラウザ上に表示されれば、完了

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

ビデオ管理アプリケーションをつくる

はじめに

phpの現場に入る前に予習しようとしてこの教材を買ったけど結局難しくて挫折してプロジェクトで半年死ぬような目にあったけどいまなら雪辱をはらす

参考

https://www.udemy.com/course/symfony-4-web-development-from-beginner-to-advanced/learn/lecture/13003870#overview

composer create-project でプロジェクトを作成すると、
最新の Symfony が組み込まれるため、記述通りには動かないケースが出てきます。「Unrecognized option "cookie_samesite" under "framework.session"」というエラーが表示された場合は、config/packages/framework.yaml ファイルを開き、そこにある「cookie_samesite: lax」という文をコメントアウトして下さい。これで server:run で実行できるようになります。

プロジェクト作成
# run this if you are building a traditional web application
> symfony new my-project --full
> cd my-project

my-project> symfony check:requirements
  Symfony Requirements Checker
  > PHP is using the following php.ini file:
  C:\php\php.ini
  > Checking Symfony requirements:
  ...................................

   [OK]                                         
   Your system is ready to run Symfony projects 

確認

my-project> symfony server:start
  Tailing Web Server log file 
  (C:\Users\yoshi\.symfony\log\7e6fb0ddf9da9c70d237a54d719757b699a859e0.log)
Tailing PHP-CGI log file (C:\Users\yoshi\.symfony\log\7e6fb0ddf9da9c70d237a54d719757b699a859e0\79ca75f9e90b4126a5955a33ea6a41ec5e854698.log)

   [OK] Web server listening                                                                                              
        The Web server is using PHP CGI 7.4.9                                                                             
        https://127.0.0.1:8000

image.png

baseテンプレートの削除

my-project/templates/base.html.twig
(全部削除)

コントローラ作成

FrontControllerと名付ける
my-project> php bin/console make:controller
 Choose a name for your controller class (e.g. GrumpyChefController):
 > FrontController

  created: src/Controller/FrontController.php
  created: templates/front/index.html.twig

    Success! 

  Next: Open your new controller class and add some pages!

my-project/templates/front/index.html.twig は削除する
my-project/templates/front の中身を入れ替える(gitを見て

baseテンプレートに転記

転記元: my-project/templates/front/includes/_header.php
転記元: my-project/templates/front/includes/_footer.php
転記先: my-project/templates/base.html.twig
マージするってこっちゃな。マージし終わったら転記元ファイルは削除。

my-project/templates/base.html.twig
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="assets/css/styles.css">

    <style>
        .form-control {
            background-color: #f8f5f5;
        }
    </style>

    <link rel="stylesheet" href="assets/css/fontawesome.min.css">

    <title>Symfony 4 course app - video sharing service</title>

</head>

<body class="text-center">
<div class="container-fluid">

</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="assets/js/jquery.js"></script>
<script src="assets/js/popper.js"></script>
<script src="assets/js/bootstrap.js"></script>

<script>
    $(function () {
        $('[data-toggle="tooltip"]').tooltip()
    })
</script>
</body>
</html>

ファイルの拡張子を変えていく

さっきgitからコピーしてきたやつ
「.php」>「.html.twig」
image.png

参照を差し替える

my-project/templates/front/index.html.twig
- <?php include('includes/_header.php'); ?>
- <?php include('includes/_menu.php'); ?>
+ {% extends 'base.html.twig' %}
+ {% block main %}
  <h1 class="text-info mb-5 mt-4">Where the best videos happens</h1>
  <div class="embed-responsive embed-responsive-16by9">
     <iframe class="embed-responsive-item" src="https://player.vimeo.com/video/137857207" allowfullscreen></iframe>
  </div>
+ {% endblock %}
- <?php include('includes/_footer_links.php'); ?>
- <?php include('includes/_footer.php'); ?>
my-project/templates/base.html.twig
 :
  <div class="container-fluid">
+   {% block main %}
+   {% endblock %}
  </div>
 :
my-project/src/Controller/FrontController.php
    /**
-    * @Route("/front", name="front")
+    * @Route("/", name="main_page")
     */
    public function index(): Response
    {
-       return $this->render('front/index.html.twig', [
-           'controller_name' => 'FrontController',
-       ]);
+       return $this->render('front/index.html.twig');
    }

確認

CSSはまだ効いていません
image.png

publicフォルダにassetsをコピー

gitから持ってきて
image.png

templateフォルダにadminをコピー

gitから持ってきて
image.png

cssやfontawesome、jsの参照を変える

my-project/templates/base.html.twig
    <!-- Bootstrap CSS -->
-   <link rel="stylesheet" href="assets/css/styles.css">
+   <link rel="stylesheet" href="{{ asset('assets/css/styles.css') }}">
   :

-   <link rel="stylesheet" href="assets/css/fontawesome.min.css">
+   <link rel="stylesheet" href="{{ asset('assets/css/fontawesome.min.css') }}">
     :

<!-- jQuery first, then Popper.js, then Bootstrap JS -->
-   <script src="assets/js/jquery.js"></script>
-   <script src="assets/js/popper.js"></script>
-   <script src="assets/js/bootstrap.js"></script>
+   <script src="{{ asset('assets/js/jquery.js') }}"></script>
+   <script src="{{ asset('assets/js/popper.js') }}"></script>
+   <script src="{{ asset('assets/js/bootstrap.js') }}"></script>

確認

image.png
image.png

カスタムブロックをつくる

まぁこのへんは自由に、ってやつよな

      :
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="{{ asset('assets/css/styles.css') }}">
+   {% block customstylesheets %}{% endblock %}
      :
-   <title>Symfony 4 course app - video sharing service</title>
+   <title>{% block title %}Symfony 4 course app - video sharing service{% endblock %}</title>
      :
  <div class="container-fluid">
+   {% block menu %}
+       <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
+           <h5 class="my-0 mr-md-auto font-weight-normal"><a href="index.php">Awesome Videos</a></h5>
+           <form method="POST" class="form-inline my-0 mr-md-auto" action="search_results.php">
+               <input class="form-control mr-sm-2" type="search" placeholder="video title" aria-label="Search video">
+               <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search video</button>
+           </form>
+           <nav class="my-2 my-md-0 mr-md-3">
+               <a class="p-2 text-dark" href="videolist.php">Funny</a>
+               <a class="p-2 text-dark" href="videolist.php">Scary</a>
+               <a class="p-2 text-dark" href="videolist.php">Unbelievable</a>
+               <a class="p-2 text-dark" href="videolist.php">Inspirational</a>
+               <a class="p-2 text-dark" href="videolist.php">Motivating</a>
+               <a class="p-2 text-dark" href="admin/my_profile.php">My account</a>
+           </nav>
+           <a class="btn btn-outline-primary mr-2" href="pricing.php">Sign up</a>
+           <a class="btn btn-outline-primary" href="login.php">Sign in</a>
+       </div>
+   {% endblock %}
    {% block main %}
    {% endblock %}
+   {% block footer_links%}
+       {% block footer_links%}
+       <footer class="pt-4 my-md-5 pt-md-5 border-top">
+           <div class="row">
+               <div class="col-12 col-md">
+                   <small class="d-block mb-3 text-muted">&copy; 2017-2018</small>
+               </div>
+               <div class="col-6 col-md">
+                   <h5>Features</h5>
+                   <ul class="list-unstyled text-small">
+                       <li><a class="text-muted" href="#">Live streaming</a></li>
+                       <li><a class="text-muted" href="#">Video player</a></li>
+                       <li><a class="text-muted" href="#">Monetization</a></li>
+                       <li><a class="text-muted" href="#">Hosting</a></li>
+                       <li><a class="text-muted" href="#">Privacy</a></li>
+                       <li><a class="text-muted" href="#">Analytics</a></li>
+                       <li><a class="text-muted" href="#">Speed test</a></li>
+                   </ul>
+               </div>
+               <div class="col-6 col-md">
+                   <h5>Resources</h5>
+                   <ul class="list-unstyled text-small">
+                       <li><a class="text-muted" href="#">Help center</a></li>
+                       <li><a class="text-muted" href="#">Blog</a></li>
+                       <li><a class="text-muted" href="#">Guidelines</a></li>
+                       <li><a class="text-muted" href="#">Developers</a></li>
+                   </ul>
+               </div>
+               <div class="col-6 col-md">
+                   <h5>About</h5>
+                   <ul class="list-unstyled text-small">
+                       <li><a class="text-muted" href="#">Team</a></li>
+                       <li><a class="text-muted" href="#">Contact</a></li>
+                       <li><a class="text-muted" href="#">Jobs</a></li>
+                       <li><a class="text-muted" href="#">Partners</a></li>
+                       <li><a class="text-muted" href="#">Terms</a></li>
+                   </ul>
+               </div>
+           </div>
+       </footer>
+   {% endblock %}
 </div>
  :
<script>
    $(function () {
        $('[data-toggle="tooltip"]').tooltip()
    })
</script>
+ {% block customjavascript %}{% endblock %}

確認

nice menu and footer!
image.png
image.png

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

「備忘録」パスワード保護Excel作成と読込自動化検討(OSSだけで)

検討中の要件で、Workbook全体のパスワード設定&暗号化のExcelファイルの作成と読込の自動化の検討が必要になったため、現状のシステムで埋め込めるのかを調べるために整理しておいた内容です。

調査・検証内容上、間違いが、もっといい案があるかもなので、参考までにみてください。(筆者も、後ほど時間あれば、もっと検証したいと思ってます)

もし、過ちがあれば、コメントいただけると嬉しいです。

結論

パスワード保護Excelの作成には、nodeベースの「Secure Spreadsheet」を使う

https://github.com/ankane/secure-spreadsheet

npm install -g secure-spreadsheet

# CSV to Password Protected Encrypted XLSX
cat input.csv | secure-spreadsheet --password secret > output.xlsx

# XLSX to Password Protected Encrypted XLSX
cat input.xlsx | secure-spreadsheet --password secret --input-format xlsx > output.xlsx

パスワード保護Excelの読込・解除には、pythonベースの「msoffcrypto-tool」を使う

https://pypi.org/project/msoffcrypto-tool/

pip install msoffcrypto-tool

msoffcrypto-tool encrypted.xlsx decrypted.xlsx -p Passw0rd

背景

施策の要件で、パスワード保護Excelを使って、先方とやりとりをするのが必須

  • Excelファイルにパスワードをかけて、先方に送信
  • 先方の処理結果を、パスワード保護Excelで受信

を、人のてでするか、自動化するかの選択で「OSSだけで自動化できるか?」の疑いから、検証にいたる

ベースシステム

php 5.3のBackofficeシステム。

php 7のapiサーバーがあるものの、機能追加するシステムとは分かれているので、 php5.3ベースでやりたいことが前提となっていた。

調べた内容と出た課題

ライブラリー別比較

library platform パスワード保護Excel作成 パスワード保護Excel読込 補足
★ msoffcrypto-tool Python 支援X O 復号専用
★ Secure Spreadsheet Node O 支援X 暗号化専用
PHPExcel PHP >= 5.x ※1動作異常 支援X 数年前にサポート終了
PHPSpreadsheet PHP >= 7.x 検証していない 支援X PHP7以上必須
openpyxl Python ※1動作異常 支援X msoffcrypto-toolと並行すると、読込可能

※1動作異常
プログラム的にエラーなくちゃんと回って、ファイルも生成されるが、実際にMacのNumbersで開いてみると、保護されてなかったため、保護が正常に聞いていないと判断した。

ログ

PHP5.3では、最新のExcelライブラリーが使えない。

PHPには「PHPExcel」と「PHPspreadsheet」の二つのライブラリーが存在する

PHPExcelは5.3で使えるが、何年前にサポート中断されている。
公式的にPhpspreadsheetを使うように推奨されている

しかしPhpspreadsheetは、php7以上じゃないと使えない。

「PHPExcel」と「PHPspreadsheet」のどっちも、ちゃんとしたパスワード保護Excelのサポートをしていない

個別のsheet, cellの保護はサポートしているように見えるが、Workbook全体のパスワード設定&暗号化はサポートしていない。

作成の場合はエラーとかも出ずちゃんとファイルも作られるが、保護がされてなく、MacのNumbersプログラムで開くと、保護が効かず、パスワードなして閲覧・編集ができてしまう。

なので、パスワード保護がうまく効かないと判断。

  • 「個別のsheet, cell」の保護機能
    • サポートしているように見える
  • ★「Workbook全体暗号化&パスワード保護」作成
    • 機能は提供しているみたいだが、ちゃんと動作しないというissueがあった。
    • https://github.com/PHPOffice/PHPExcel/issues/442
    • 実際検証してみたところ、ちゃんと保護されない。
    • 「PHPExcel_Reader_Excel2007」か、「PHPExcel_Writer_Excel2007」を使うことを推奨しているが、それでもダメだった。
  • ★「Workbook全体暗号化&パスワード保護」読込
    • サポートしていない

※ PHPSpreadsheetでの、パスワード保護Excel作成は試してないが、読込をサポートしてないので、除外した。

PHPでは、できないと思って、Pythonの、「openpyxl」と、「msoffcrypto-tool」を組み合わせて、作成と読込ができると思ったが、作成の方はできなかった。

openpyxlの場合は、Workbook全体のパスワード設定&暗号化の関数は用意されているように見えるが、うまく動作しなかった。

関連stack overflow
https://stackoverflow.com/questions/36122496/password-protecting-excel-file-using-python

issueもあった気がするが、忘れた。

JavaやNETの、有料ライブラリーもあったが、有料は選定条件から外した。

https://www.easyxls.com/

これだと、いろいろちゃんとサポートしているんじゃないかなと思うが、有料なので、選定から外した。
https://www.easyxls.com/manual/tutorials/java/encrypt-excel-file.html

ただ、読み込みができるかは不明

結局Shell上で実行する方針で考えたところ、NODEとPYTHONで、簡単に暗号化&復号ができることがわかった。

  • nodeベースの「Secure Spreadsheet」は、暗号化専用
  • pythonベースの「msoffcrypto-tool」は、復号専用

二つを組み合わせて使うと、動作確認&検証ができた。

GoogleSheet APIでも、excelの暗号化&復号はサポートしていない

GoogleSheetで、Native Excelがサポートされたので、ひょっとしてだと思ったが、
ざっくり探してみたが、やはりサポートしていない&予定も未定だった

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

「備忘録」パスワード保護Excel作成と読込自動化検討のメモ

検討中の要件で、Workbook全体のパスワード設定&暗号化のExcelファイルの作成と読込の自動化の検討が必要になったため、現状のシステムで埋め込めるのかを調べるために整理しておいた内容です。

調査・検証内容上、間違いが、もっといい案があるかもなので、参考までにみてください。(筆者も、後ほど時間あれば、もっと検証したいと思ってます)

もし、過ちがあれば、コメントいただけると嬉しいです。

結論

パスワード保護Excelの作成には、nodeベースの「Secure Spreadsheet」を使う

https://github.com/ankane/secure-spreadsheet

npm install -g secure-spreadsheet

# CSV to Password Protected Encrypted XLSX
cat input.csv | secure-spreadsheet --password secret > output.xlsx

# XLSX to Password Protected Encrypted XLSX
cat input.xlsx | secure-spreadsheet --password secret --input-format xlsx > output.xlsx

パスワード保護Excelの読込・解除には、pythonベースの「msoffcrypto-tool」を使う

https://pypi.org/project/msoffcrypto-tool/

pip install msoffcrypto-tool

msoffcrypto-tool encrypted.xlsx decrypted.xlsx -p Passw0rd

背景

施策の要件で、パスワード保護Excelを使って、先方とやりとりをするのが必須

  • Excelファイルにパスワードをかけて、先方に送信
  • 先方の処理結果を、パスワード保護Excelで受信

を、人のてでするか、自動化するかの選択で「OSSだけで自動化できるか?」の疑いから、検証にいたる

ベースシステム

php 5.3のBackofficeシステム。

php 7のapiサーバーがあるものの、機能追加するシステムとは分かれているので、 php5.3ベースでやりたいことが前提となっていた。

調べた内容と出た課題

ライブラリー別比較

library platform パスワード保護Excel作成 パスワード保護Excel読込 補足
★ msoffcrypto-tool Python 支援X O 復号専用
★ Secure Spreadsheet Node O 支援X 暗号化専用
PHPExcel PHP >= 5.x ※1動作異常 支援X 数年前にサポート終了
PHPSpreadsheet PHP >= 7.x 検証していない 支援X PHP7以上必須
openpyxl Python ※1動作異常 支援X msoffcrypto-toolと並行すると、読込可能
apache poi Java 検証していない 検証していない ドキュメント上では、サポート可能に見える(IF提供している模様)

※1動作異常
プログラム的にエラーなくちゃんと回って、ファイルも生成されるが、実際にMacのNumbersで開いてみると、保護されてなかったため、保護が正常に聞いていないと判断した。

ログ

PHP5.3では、最新のExcelライブラリーが使えない。

PHPには「PHPExcel」と「PHPspreadsheet」の二つのライブラリーが存在する

PHPExcelは5.3で使えるが、何年前にサポート中断されている。
公式的にPhpspreadsheetを使うように推奨されている

しかしPhpspreadsheetは、php7以上じゃないと使えない。

「PHPExcel」と「PHPspreadsheet」のどっちも、ちゃんとしたパスワード保護Excelのサポートをしていない

個別のsheet, cellの保護はサポートしているように見えるが、Workbook全体のパスワード設定&暗号化はサポートしていない。

作成の場合はエラーとかも出ずちゃんとファイルも作られるが、保護がされてなく、MacのNumbersプログラムで開くと、保護が効かず、パスワードなして閲覧・編集ができてしまう。

なので、パスワード保護がうまく効かないと判断。

  • 「個別のsheet, cell」の保護機能
    • サポートしているように見える
  • ★「Workbook全体暗号化&パスワード保護」作成
    • 機能は提供しているみたいだが、ちゃんと動作しないというissueがあった。
    • https://github.com/PHPOffice/PHPExcel/issues/442
    • 実際検証してみたところ、ちゃんと保護されない。
    • 「PHPExcel_Reader_Excel2007」か、「PHPExcel_Writer_Excel2007」を使うことを推奨しているが、それでもダメだった。
  • ★「Workbook全体暗号化&パスワード保護」読込
    • サポートしていない

※ PHPSpreadsheetでの、パスワード保護Excel作成は試してないが、読込をサポートしてないので、除外した。

PHPでは、できないと思って、Pythonの、「openpyxl」と、「msoffcrypto-tool」を組み合わせて、作成と読込ができると思ったが、作成の方はできなかった。

openpyxlの場合は、Workbook全体のパスワード設定&暗号化の関数は用意されているように見えるが、うまく動作しなかった。

関連stack overflow
https://stackoverflow.com/questions/36122496/password-protecting-excel-file-using-python

issueもあった気がするが、忘れた。

JavaやNETの、有料ライブラリーもあったが、有料は選定条件から外した。

https://www.easyxls.com/

これだと、いろいろちゃんとサポートしているんじゃないかなと思うが、有料なので、選定から外した。
https://www.easyxls.com/manual/tutorials/java/encrypt-excel-file.html

ただ、読み込みができるかは不明

結局Shell上で実行する方針で考えたところ、NODEとPYTHONで、簡単に暗号化&復号ができることがわかった。

  • nodeベースの「Secure Spreadsheet」は、暗号化専用
  • pythonベースの「msoffcrypto-tool」は、復号専用

二つを組み合わせて使うと、動作確認&検証ができた。

GoogleSheet APIでも、excelの暗号化&復号はサポートしていない

GoogleSheetで、Native Excelがサポートされたので、ひょっとしてだと思ったが、
ざっくり探してみたが、やはりサポートしていない&予定も未定だった

Javaの場合は、Apache POIを検討

また検証はしていないが、Javaなら、これで行けるかも。後で時間あれば検証する

https://poi.apache.org/
https://stackoverflow.com/questions/8817290/create-a-password-protected-excel-file-using-apache-poi
https://github.com/Icemanbeta/PHP-POI

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

paizaのレベルアップ問題集「じゃんけんの手の出し方 (paizaランク A 相当)」 をPHPで解きたい

paizaのレベルアップ問題集「じゃんけんの手の出し方 (paizaランク A 相当)」 をPHPで解いてみました。
自力では解けなかったので、ググりまくって考え方を導き出しましたが、PHPで書かれている記事は見当たらなかったため、記事にしました。
スキルチェック見本問題セット(PHP編) 提出結果 - レベルアップ問題集 - プログラミング学習サービス【paizaラーニング】 - paiza.jp.png

実現したいこと

paizaのレベルアップ問題集「じゃんけんの手の出し方 (paizaランク A 相当)」 をPHPで解きたい
https://paiza.jp/works/mondai/skillcheck_sample/janken?language_uid=php

開発環境

OS

% sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69

エディター

ATOM version1.53.0

その他

・MAMP version 6.3(PHPversion 7.4.12)
・ブラウザ:Chrome

考え方

①問題文にある通り、最終的に出力したいのは「最高で何回じゃんけんに勝つことができる」か
②ただし、出す指の数には制約がある
③指の数の制約がなければ、全勝できる
以上三点から、以下の考え方に至りました
A、"千里眼の持ち主"当人の出し手の組み合わせを網羅的に特定
B、出し手の指数が、標準入力から得られる指数と合致するかを判定し、合致していれば後続処理に回す
C、出し手の組み合わせごとに、最大の勝利数を算出(最大の勝利数さえわかれば良いので、出し手の組み合わせがどの順番で出されるかは関係ない)
D、考えうる全ての出しを考慮した上で、最大の勝利数を出力

処理フロー図

凡例
凡例.png
メインフロー図
メインフロー図.png
・最大の勝利数算出方法
最大勝利数の算出方法.png

解説

さて、本題に入りましょう。
一番下に、私が提出したプログラムは書きますが、考え方も読んでいただければ幸いです。
何かご指摘があれば、やさし〜く教えてください。

下準備

標準入力から得られる情報を変数に詰めておきましょう。
1、対戦数
2、当人の出し手の指数
3、相手の出す手を配列化

$file = fopen("/Applications/MAMP/htdocs/test_data/test_data_p_janken3.txt", "r");

// ファイルの内容を一行ずつ配列に代入します
if($file) {
  while ($line = fgets($file)) {
    $tmp[] = trim($line);
  }
}
$temp_explode = explode(" " ,$tmp[0]);
$battle_number = $temp_explode[0];//対戦数
$finger_number = $temp_explode[1];//指の数の合計
$temp_explode_rival = str_split($tmp[1]);//相手の出す手を配列化

※私はこの問題をlocal環境で解いたため、以下のような書きっぷりをしています。

test_data_p_janken3.txt の中身は以下

3 2
GPC

標準入力のPHPでの取得方法について、詳しくは以下を参照
https://qiita.com/one-kelvin/items/09068b8971c4da509cd3
local環境は以下手順で構築しました
https://qiita.com/pilloty/items/4ad567d3f289470e938c

"千里眼の持ち主"当人の出し手の組み合わせを網羅的に特定

例えば、3回の対戦をするとします。
その場合、出し手の組み合わせは
(パー,チョキ,グー)=(0,0,3)、(0,1,2)、(0,2,1)、(0,3,0)、(1,0,2)、(1,1,1)、(1,2,0)、(2,0,1)、(2,1,0)、(3,0,0)
パーが0の場合だけ見ると、(0,0,3)、(0,1,2)、(0,2,1)、(0,3,0)の4通り
パーが1の場合だけ見ると、(1,0,2)、(1,1,1)、(1,2,0)の3通り
パーが2の場合だけ見ると、(2,0,1)、(2,1,0)の2通り
パーが3の場合だけ見ると、(3,0,0)の1通り
になりますね。
つまり、パーの出し手の組み合わせを網羅的に洗い出すには、”パーを出す回数が0回のパターン"、”パーを出す回数が1回のパターン"、・・・、”パーを出す回数がN回(Nは「"対戦数"」)のパターン"としなければなりません
そのために、「"対戦数"(N)+1」回繰り返し処理を行います。

for($i=0; $i<$battle_number+1; $i++){
  $paper_num = $i; //今回の周回のパーを出す回数
 //後続処理
}

今回の周回でパーを出す回数は”$i”を詰めてあげれば、0、1、2、、、N となっていくつくりです。
便宜上、これを「1次元目の繰り返し処理」とよびます。

これで今回の周回で出す手の組み合わせについて、パーを出す回数が特定できたため、次はそのほかの手を出す回数を特定する処理を入れます。
今回の周回の組み合わせでパーを出す回数が、、、

0回の場合、出し手の組み合わせは「"対戦数"(N)+1」通り
1回の場合、出し手の組み合わせはN通り
・・・
N回の場合、出し手の組み合わせは1通り

となるので、「"対戦数"(N)+1-"今回の周回でパーを出す回数"」回繰り返し処理を行います

for($i=0; $i<$battle_number+1; $i++){
  $paper_num = $i; //今回の周回のパーを出す回数
  for($ii=0; $ii<$battle_number-$i+1; $ii++){
    $scissors_num = $ii; //今回の周回のチョキを出す回数
    $rock_num = $battle_number - $i - $ii; //今回の周回のグーを出す回数
    //後続処理
  }
}

今回の周回でチョキを出す回数は、”$ii”詰めてあげれば、0、1、2、、、M(Mの最大値は「$battle_number」) となっていくつくりです。
今回の周回でグーを出す回数は、”対戦数”-"パーを出す回数"-"チョキを出す回数"としてあげれば、問題ないです。
便宜上、これを2次元目の繰り返し処理とよびます。

これで「1次元目の繰り返し処理」が終われば、自然と全ての出し手の組み合わせについて、網羅的に後続処理を通せることになります。

出し手の指数が、標準入力から得られる指数と合致するかを判定し、合致していれば後続処理に回す

前述しましたが、本問は当人の出し手の指数に制約($finger_number)があります。
そのため、今回の周回の出し手の組み合わせの合計指数が、$finger_number と一致するかを確認する必要があります。
もし一致しなければ、今回の周回は後続の処理を通す必要がないのです。

for($i=0; $i<$battle_number+1; $i++){
  $paper_num = $i; //今回の周回のパーを出す回数
  for($ii=0; $ii<$battle_number-$i+1; $ii++){
    $scissors_num = $ii; //今回の周回のチョキを出す回数
    $rock_num = $battle_number - $i - $ii; //今回の周回のグーを出す回数
    //特定した今回の周回の指数と標準入力から取れる指数が一致しているかを確認
    $finger_number_of_a_w = ($paper_num*5) + ($scissors_num*2);
    if($finger_number == $finger_number_of_a_w){ //指数が入力値と一致する場合のみ後続処理へ
        //後続処理
    }
  }
}

出し手の組み合わせごとに、最大の勝利数を算出

この処理を通すのは、考えうる出し手のうち、標準入力から得られる指数と一致する出し手の場合です。
さて、最終的に出力したいのは、考えうる出し手のうち(標準入力と指数が一致して、かつ)最大の勝利数なので、、、

今回の周回の出し手の組み合わせで可能な最大の勝利数 = 
グーを出して勝てる最大の勝利数(ただし、$rock_numより多くは出せない)+
チョキを出して勝てる最大の勝利数(ただし、$scissors_numより多くは出せない)+
パーを出して勝てる最大の勝利数(ただし、$paper_numより多くは出せない)

というように考えてみます。
さらに、「グーを出して勝てる最大の勝利数(ただし、$rock_numより多くは出せない)」は、相手がチョキを出す回数に依存します。
当人がグーを何回出そうと相手がチョキを1回も出さなけば、グーでは1回も勝てないのです。
つまり、相手がチョキを出す回数より、当人がグーを出す回数が多ければ、グーで勝てるのは「相手がチョキを出した回数」になります。
一方、それ以外の場合(相手がチョキを出す回数が、当人がグーを出す回数以上の場合)は、グーで勝てるのは「当人がグーを出した回数」となるわけです。
これを、チョキ、パーを出す場合にも適用してあげれば、前述の「今回の周回の出し手の組み合わせで可能な最大の勝利数」が算出できるというわけです。
※相手がグー、チョキ、パーを出す回数は「1次元目の繰り返し処理」が始まる前に定義しておきましょう。

 //相手がチョキを出す回数
$rock_win_num_b = count(array_keys($temp_explode_rival, "C"));
//相手がパーを出す回数
$scissors_win_num_b = count(array_keys($temp_explode_rival, "P"));
//相手がグーを出す回数
$paper_win_num_b = count(array_keys($temp_explode_rival, "G"));

for($i=0; $i<$battle_number+1; $i++){
  $paper_num = $i; //今回の周回のパーを出す回数
  for($ii=0; $ii<$battle_number-$i+1; $ii++){
    $scissors_num = $ii; //今回の周回のチョキを出す回数
    $rock_num = $battle_number - $i - $ii; //今回の周回のグーを出す回数
    //特定した今回の周回の指数と標準入力から取れる指数が一致しているかを確認
    $finger_number_of_a_w = ($paper_num*5) + ($scissors_num*2);
    if($finger_number == $finger_number_of_a_w){ //指数が入力値と一致する場合のみ後続処理へ
      if($rock_win_num_b<$rock_num){
        $rock_num_win = $rock_win_num_b; //相手がチョキを出す回数より今回の周回のグーを出す回数が多い場合は、グーで勝てるのは、相手がチョキを出した回数
      }else{
        $rock_num_win = $rock_num; //相手がチョキを出す回数が今回の周回のグーを出す回数以上の場合は、グーで勝てるのは、グーを出した回数
      }
      if($scissors_win_num_b<$scissors_num){
        $scissors_num_win = $scissors_win_num_b; //相手がパーを出す回数より今回の周回のチョキを出す回数が多い場合は、チョキで勝てるのは、相手がパーを出した回数
      }else{
        $scissors_num_win = $scissors_num; //相手がパーを出す回数が今回の周回のチョキを出す回数以上の場合は、チョキで勝てるのは、チョキを出した回数
      }
      if($paper_win_num_b<$paper_num){
        $paper_num_win = $paper_win_num_b; //相手がグーを出す回数より今回の周回のパーを出す回数が多い場合は、パーで勝てるのは、相手がグーを出した回数
      }else{
        $paper_num_win = $paper_num; //相手がグーを出す回数が今回の周回のパーを出す回数以上の場合は、パーで勝てるのは、パーを出した回数
      }
      $win_num_b = $rock_num_win + $scissors_num_win + $paper_num_win; //今回の周回の出し手の組み合わせで勝てる最大勝利数
      // 後続処理
    }
  }
}

考えうる全ての出しを考慮した上で、最大の勝利数を出力

最後に今回の周回の出し手の組み合わせで可能な最大の勝利数($win_num_b)が、出力するべき数字なのかを判定してあげましょう
例えば、5回対戦したとして、当人が(チョキ、チョキ、チョキ、チョキ、チョキ)と出す場合と(パー、パー、グー、グー、グー)と出す場合を考えると、両方指数合計($finger_num)が10ですが、どちらが考えうる最大の勝利数を得られるかは、相手の出し手によります。
前述のロジックで、上記2パターンはどちらも「1次元目の繰り返し処理」が終わるときにはここまでの処理を通っているはずなので、前回の周回までの最大の勝利数($win_num)と、今回の周回の最大の勝利数($win_num_b)を比べて、今回の周回の最大の勝利数が多い場合のみ、$win_num を更新する処理を入れます。
※「1次元目の繰り返し処理」が始まる前に、初期値=0を定義するのを忘れずに、、
「1次元目の繰り返し処理」が終わるときに、$win_num に保持している値が、出力するべき値となるわけです。

<?php
//URL https://paiza.jp/works/mondai/skillcheck_sample/janken?language_uid=php

//複数行の標準入力を配列に詰める
$file = fopen("/Applications/MAMP/htdocs/test_data/test_data_p_janken3.txt", "r");

// ファイルの内容を一行ずつ配列に代入します
if($file) {
  while ($line = fgets($file)) {
    $tmp[] = trim($line);
  }
}
$temp_explode = explode(" " ,$tmp[0]);
$battle_number = $temp_explode[0];//対戦数
$finger_number = $temp_explode[1];//指の数の合計
$temp_explode_rival = str_split($tmp[1]);//相手の出す手を配列化

// 出しての組み合わせを網羅的に特定し、そのグー,チョキ,パーの組み合わせで勝てる最大の勝利数を考える

// $win_num;int
// その回の周回の最大の勝利数が、それまでの周回の勝利数を超えていれば、その値を保持
// その回の周回の最大の勝利数が、それまでの周回の勝利数以下の場合は、値の更新を行わない
$win_num = 0;

 //相手がチョキを出す回数
$rock_win_num_b = count(array_keys($temp_explode_rival, "C"));
//相手がパーを出す回数
$scissors_win_num_b = count(array_keys($temp_explode_rival, "P"));
//相手がグーを出す回数
$paper_win_num_b = count(array_keys($temp_explode_rival, "G"));

for($i=0; $i<$battle_number+1; $i++){
  $paper_num = $i; //今回の周回のパーを出す回数
  for($ii=0; $ii<$battle_number-$i+1; $ii++){
    $scissors_num = $ii; //今回の周回のチョキを出す回数
    $rock_num = $battle_number - $i - $ii; //今回の周回のグーを出す回数
    //特定した今回の周回の指数と標準入力から取れる指数が一致しているかを確認
    $finger_number_of_a_w = ($paper_num*5) + ($scissors_num*2);
    if($finger_number == $finger_number_of_a_w){ //指数が入力値と一致する場合のみ後続処理へ
      if($rock_win_num_b<$rock_num){
        $rock_num_win = $rock_win_num_b; //相手がチョキを出す回数より今回の周回のグーを出す回数が多い場合は、グーで勝てるのは、相手がチョキを出した回数
      }else{
        $rock_num_win = $rock_num; //相手がチョキを出す回数が今回の周回のグーを出す回数以上の場合は、グーで勝てるのは、グーを出した回数
      }
      if($scissors_win_num_b<$scissors_num){
        $scissors_num_win = $scissors_win_num_b; //相手がパーを出す回数より今回の周回のチョキを出す回数が多い場合は、チョキで勝てるのは、相手がパーを出した回数
      }else{
        $scissors_num_win = $scissors_num; //相手がパーを出す回数が今回の周回のチョキを出す回数以上の場合は、チョキで勝てるのは、チョキを出した回数
      }
      if($paper_win_num_b<$paper_num){
        $paper_num_win = $paper_win_num_b; //相手がグーを出す回数より今回の周回のパーを出す回数が多い場合は、パーで勝てるのは、相手がグーを出した回数
      }else{
        $paper_num_win = $paper_num; //相手がグーを出す回数が今回の周回のパーを出す回数以上の場合は、パーで勝てるのは、パーを出した回数
      }
      $win_num_b = $rock_num_win + $scissors_num_win + $paper_num_win; //今回の周回の出し手の組み合わせで勝てる最大勝利数
      if($win_num_b > $win_num){
        $win_num = $win_num_b; //これまでの周回の最大勝利数より、今回の周回の最大勝利数が多い場合は、最大勝利数(=アウトプット)を更新
      }
    }
  }
}
$answer = $win_num;
echo $answer;
?>

結び

以上で、今回の記事については終了です。
頭のいい人の書くコードは、当人の指数が「2の倍数なのか」、「5の倍数なのか」、「2の倍数でもあるし、5の倍数でもあるのか」、「2の倍数でも、5の倍数でもないのか」によって、繰り返し処理の回数を絞っているようですが、私にはそこまで考慮する元気がありませんでした。
システムなぞ、多少重くても動けば良いのです
いろいろな言語で、本問題を解いたネット記事は見つけたのですが、私のかけるPHPで書いていた記事を見つけられなかったので、投稿してみました。
また機会があれば、やってみようとおもいます。

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

Doctrineで主キーの無いテーブルに対してSELECTをかける

データを取りたいが重複したデータ(先頭データ)しか取れない

コントローラ内で、

KaraagekunController.php
$this->KaraagekunRepository->findBy(['id' => $id]);

としてRepository経由でfindByしてデータを取ろうとしたら、
同じレコードが3件返ってきていた。
件数は3件で間違っていないものの、同じレコードが返ってくるのはおかしいと思って調べました。

doctrineでは主キーの無いテーブルを推奨していない

みたいです。
なので、主キーの無いテーブルに対してレコード取ろうとしてて、
doctrine側でレコードが認識できずに変な値が返ってくるみたいですね。

解決方法

Repository内でQueryBuilderを使って取るしか方法なさそうでした。

KaraagekunRepository.php
public function getData($id) {
    return $this->createQueryBuilder('t')
      ->select('t.id, t.type, t.price')
      ->where('t.id = :id')
      ->setParameter('id', $id)
      ->getQuery()
      ->getResult();
}
KaraagekunController.php
$result = $this->KaraagekunRepository->getData($id);

そもそも主キーの無いテーブルがいいのか悪いのかって議論があると思いますが、
一応こういうやり方で取れましたっていう報告です。
ご参考までに!

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

PHPの変数にHTML要素を格納する

目的

  • HTMLのtable要素の一連の文字列をPHPの変数に格納する時に若干詰まったので方法をメモ的にまとめておく。

方法

  • ""(ダブルクオート)ではなく''(シングルクオート)でくくるようにする。(""でくくるとHTML要素のクラス名の指定部分などと競合してエラーになる。どうしても""でくくりたいなら文字列として扱うHTML要素の中の""\でエスケープする。)

  • シングルクオートでHTMLの要素をくくる方法を下記に記載する。

    $content = '<table border="1"><tr><th>info_1</th><th>info_2</th></tr><tr><td>Tokyo</td><td>Kanagawa</td></tr><tr><td>Osaka</td><td>Hyougo</td></tr></table>';
    
  • どうしてもダブルクオートでHTMLの要素をくくる時は下記のように要素内の""をエスケープして文字列として変数に格納する。

    $content = "<table border=\"1\"><tr><th>info_1</th><th>info_2</th></tr><tr><td>Tokyo</td><td>Kanagawa</td></tr><tr><td>Osaka</td><td>Hyougo</td></tr></table>";
    
  • 下記方法はNG!!!要素中の""がPHPの文字列認識の""と競合してしまう。

    $content = "<table border="1"><tr><th>info_1</th><th>info_2</th></tr><tr><td>Tokyo</td><td>Kanagawa</td></tr><tr><td>Osaka</td><td>Hyougo</td></tr></table>";
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP cheat sheet

This PHP cheat sheet provides a reference for quickly looking up the correct syntax for the code you use most frequently.

Php cheat sheet quick reference guide

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

メール送信周り 勉強メモ

envelope-from

  • envelope(エンベロープ)
  • もともと封筒の意味
  • mail関数の5番目の引数。「-f」と送信者のアドレスを設定する
  • 電子メールの送受信にサーバーが使用する宛先や送信者名などの付加情報や、Webサービスに使われるXMLの付加情報   * 例として、SOAPにおいて用いられるSOAPエンベロープなどがある。
  • メールのヘッダーに書かれるenvelope-fromは、メッセージヘッダのFromとは別のもの。厳密な送信先を示し、エラーメール(bounce:バウンス)の送信先になる

ヘッダFrom

メールクライアント(Microsoft OutlookやMozilla Thunderbirdなど)でメールを受信したときに、「差出人」欄に表示されている情報です。
これは、封筒の中の便箋に書かれている差出人に例えることができ、実際の差出人とは異なる情報を表示することもできます。

ヘッダTo

メールクライアント(Microsoft OutlookやMozilla Thunderbirdなど)でメールを受信したときに、「宛先」欄や「Cc」欄に表示されている情報です。
これは、封筒の中の便箋に書かれている宛先に例えることができ、実際の宛先とは異なる情報を表示することもできます。

エンベロープFrom

メールを手紙に例えると、封筒に記載されている差出人です。メールの世界では、この情報が正しいものでないと正しくメール送信ができないので、「エンベロープFrom」が本当のメールの差出人となります。
これはメールクライアントの「差出人」欄に表示されている情報とは異なる場合があります。
また、送ったメールがエラーで届かなかった場合、エラーメールはこのエンベロープFromのアドレス宛に届きます。

エンベロープTo

メールを手紙に例えると、封筒に記載されている宛先です。この情報が正しいものでないと正しくメール送信ができないので、エンベロープToが本当のメールの宛先となります。
これはメールクライアントの「宛先」欄や「Cc」欄に表示されている情報とは異なる場合があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メール関連 勉強メモ

メール関連の勉強メモ

MTA(メール転送エージェント:Message Transfer Agent)

  • ネットワーク上でメールを転送・配送するソフトウェア。
  • メールを受け取って次の適切な配信先を決定するのが主な役割
  • 配信先への送信・転送はMDAが行う
  • 利用者とのメール送受信やMTA間のメール送受信にはSMTP通信プロトコルが標準的に用いられる。
  • SMTPサーバーのプログラムの1つ 電子メールクライアント(MUA)・・・メールソフトのこと

種類

  • Sendmail
  • Courier-MTA
  • Postfix
  • qmail
  • Exim
  • Apache James
  • Microsoft Exchange Server

qmail(きゅーめーる)

  • メールサーバーソフトウェア・MTA
  • Sendmailの問題点を解消するためにつくられた。

特徴

  • 設定がシンプル
  • Sendmail,Postfixと互換性なし
  • セキュリティが強固
  • 高速なメール配信が可能
  • MailDir形式で安全なメッセージ保管ができる
  • インストールに手間がかかる

メールアドレスの仕様

https://www.nic.ad.jp/ja/dom/system.html

envelope-from

  • envelope(エンベロープ)
  • もともと封筒の意味
  • mail関数の5番目の引数。「-f」と送信者のアドレスを設定する
  • 電子メールの送受信にサーバーが使用する宛先や送信者名などの付加情報や、Webサービスに使われるXMLの付加情報   * 例として、SOAPにおいて用いられるSOAPエンベロープなどがある。
  • メールのヘッダーに書かれるenvelope-fromは、メッセージヘッダのFromとは別のもの。厳密な送信先を示し、エラーメール(bounce:バウンス)の送信先になる

ヘッダFrom

メールクライアント(Microsoft OutlookやMozilla Thunderbirdなど)でメールを受信したときに、「差出人」欄に表示されている情報です。
これは、封筒の中の便箋に書かれている差出人に例えることができ、実際の差出人とは異なる情報を表示することもできます。

ヘッダTo

メールクライアント(Microsoft OutlookやMozilla Thunderbirdなど)でメールを受信したときに、「宛先」欄や「Cc」欄に表示されている情報です。
これは、封筒の中の便箋に書かれている宛先に例えることができ、実際の宛先とは異なる情報を表示することもできます。

エンベロープFrom

メールを手紙に例えると、封筒に記載されている差出人です。メールの世界では、この情報が正しいものでないと正しくメール送信ができないので、「エンベロープFrom」が本当のメールの差出人となります。
これはメールクライアントの「差出人」欄に表示されている情報とは異なる場合があります。
また、送ったメールがエラーで届かなかった場合、エラーメールはこのエンベロープFromのアドレス宛に届きます。

エンベロープTo

メールを手紙に例えると、封筒に記載されている宛先です。この情報が正しいものでないと正しくメール送信ができないので、エンベロープToが本当のメールの宛先となります。
これはメールクライアントの「宛先」欄や「Cc」欄に表示されている情報とは異なる場合があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのユニットテストでDBにないエラーを明確にする

すみませんが、日本語訳はあとにします。

Problem

When seeInDatabase asserting was failed, I can’t not known where the path of properies is not match.

Example: With bellow test, I will receive the bellow message.

Test code:
    $this->seeInDatabase('les_knowledge', [
        'lesson_id' => $lesson->id,
        'number' => 1,
        'content_en' => '~ years old DIFFFFFFFFFF',
        'explanation_en' => 'In Japan, you are born as a 0 year-old and turn 1 on your first birthday.',
    ]);

Test result:

Unable to find row in database table [les_knowledge] that matched attributes
javascript
[{
"lesson_id":98,"number":1,
"content_en":"~ years old DIFFFFFFFFFF",
"explanation_en":"In Japan, you are born as a 0 year-old and turn 1 on your first birthday."
}]

It is hard to find where is the path don’t match.

Solution

Create a function likes a bellow.

    function seeInDatabaseAndHasProperties($table, array $filter, array $properties, $connection = null){
        $this->seeInDatabase($table, $filter, $connection);
        $model = (array)DB::table($table)->where($filter)->first();
        $this->assertEquals($properties, Arr::only($model, array_keys($properties)));
    }
Test code will be:
    $this->seeInDatabaseAndHasProperties('les_knowledge',
    [
        'lesson_id' => $lesson->id,
        'number' => 1,
    ], [
        'content_en' => '~ years old DIFFFFFFFFFF',
        'explanation_en' => 'In Japan, you are born as a 0 year-old and turn 1 on your first birthday.',
    ]);
Test result will be:
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
    -    'content_en' => '~ years old DIFFFFFFFFFF'
    +    'content_en' => '~ years old'
    'explanation_en' => 'In Japan, you are born as a 0...thday.'
)

Now you can easily see which properties don’t match.
Original post is: https://khoinv.com/post/639359603705020416/clear-errors-not-found-in-the-database-laravel

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

Laravelで明確配列タイプをする

すみませんが、日本語訳はあとにします。

The origin code

public function get_user_badge(array $user_ids, array $course_ids, Carbon $from, Carbon $end, $direction = 'desc'){}

Refactoring 1: clarify array type

Util Classes
use Illuminate\Support\Collection;

class IntegerCollection extends Collection
{
    public function __construct(int ...$values)
    {
        parent::__construct($values);
    }
}

class UserIds extends IntegerCollection {}
class CourseIds extends IntegerCollection {}
The code will rewrite as bellow:
public function get_user_badge(UserIds $userIds, CourseIds $courseIds, Carbon $from, Carbon $end, $direction = 'desc'){}

// Usage
$userIds = new UserIds($user_ids);
$courseIds  = new CourseIds($course_ids);
get_user_badge($userIds, $courseIds, ...)

Refactoring 2: Combine $from/$end to InclusiveRange class

Util Classes
    abstract class BaseRange
{
    public $begin;
    public $end;

    public function __construct(Carbon $begin, Carbon $end)
    {
        $this->begin = $begin;
        $this->end = $end;
    }

    //...some ultil functions in here
}


class InclusiveRange extends BaseRange{}
The code will rewrite as bellow:
public function get_user_badge(UserIds $userIds, CourseIds $courseIds, InclusiveRange $range, $direction = 'desc'){}

// Usage
$inclusiveRange = new InclusiveRange($from, $end)
get_user_badge($userIds, $courseIds, $inclusiveRange...)

Original post is: https://khoinv.com/post/639390454460432384/clarify-type-in-laravel-code

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

Re-wirte test-case with magic numbers 1, 2, 4

すみませんが、日本語訳はあとにします。

The original code
    private client_fixture(){
        return Client::firstOrFail();
    }
    private message_fixture($client, $count = 1, $adjust_min_version = 0, $adjust_max_version = 0){
        return factory(Notification::class, $count)->create([
            'app_version_min' => $client->version + $adjust_min_version,
            'app_version_max' => $client->version + $adjust_max_version,
        ]);
    }

    public function it_returns_only_messages_for_correct_app_version(){
        $client = $this->client_fixture();
        $this->message_fixture($client)

        $this->assertEqual(1, Notification::forAppVersion($client->version)->count());
    }

    public function it_returns_only_messages_for_valid_range_app_version(){
        $client = $this->client_fixture();
        $this->message_fixture($client, 2, -rand(0, 1), rand(0, 1)

        $this->assertEqual(2, Notification::forAppVersion($client->version)->count());
    }

    public function it_does_not_return_messages_for_invalid_range_app_version(){
        $client = $this->client_fixture();
        $this->message_fixture($client, 4, -rand(2, 3), -rand(1, 2)

        $this->assertEqual(0, Notification::forAppVersion($client->version)->count());
    }
Refacoring code
    public function it_returns_only_messages_for_correct_app_version(){
        $client = $this->client_fixture();
        $this->message_fixture($client, $correct_version_message_count = 1)
        $this->message_fixture($client, $valid_range_message_count = 2, -rand(0, 1), rand(0, 1)
        $this->message_fixture($client, $_invalid_range_message_count = 4, -rand(2, 3), -rand(1, 2)

        $this->assertEqual($correct_version_message_count + $valid_range_message_count, Notification::forAppVersion($client->version)->count());
    }

Note 1, 2, and 4 are the magic numbers of the above test because the expected result of the test is 3, but there is only one way to get 3 by combining (1 + 2).

  • Pros: Only one test can cover three independent test cases, and a combination of three conditions can be tested in the same test case.
  • Cons: If the code is wrong, it will take longer to debug.

Original post is: https://khoinv.com/post/639393766259736576/re-wirte-test-case-with-magic-numbers-1-2-4

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

Laravelコードを見直す

すみませんが、日本語訳はあとにします。
After reviewing the code for the new team members, I found long/wrong codes. I write it down here.

Content

1. Use default params when getting value from request

The original code
    $search_type = SearchTypeConstant::TITLE;
    if($request->has('search_type')){
        $search_type = $request->get('search_type');
    }

It is verbose

Refactoring
    $search_type = $request->get('search_type', SearchTypeConstant::TITLE);

2. Use Eloquent when function

The original code
    $query = User::active();
    if($first_name = $request->get('first_name')){
        $query = $query->where('first_name', $first_name);
    }
    $users = $query->get();

We can remove temp $query variable

Refactoring
    $users = User::active()->when($first_name = $request->get('first_name'), function($first_name){
        return $q->where('first_name', $first_name);
    })->get();

3. Use Eloquent updateOrCreate

The original code
    if($user->profile){
        $user->profile->update($request->get('profile')->only('phone', 'address'));
    } else {
        $user->profile()->create($request->get('profile')->only('phone', 'address'));
    }

It is verbose

Refactoring
    $user->profile()->updateOrCreate([], $request->get('profile')->only('phone', 'address'));

4. Use map instead of bundle if/else

The original code
    function get_status_i18n($status){
        if($status == STATUS::COMING){
            return 'coming';
        }

        if($status == STATUS::PUBLISH){
            return 'publish';
        }

        return 'draft';
    }

It is long

Refactoring
    private const MAP_STATUS_I18N = [
        STATUS::COMING => 'coming',
        STATUS::PUBLISH => 'publish'
    ];

    function get_status_i18n($status){
        return self::MAP_STATUS_I18N[$status] ?? 'draft';
    }

5. Use collection when you can

The original code
function get_car_equipment_names($car){
    $names = [];
    foreach($car->equipments as $equipment){
        if($equipment->name){
            $names[] =  $equipment->name;
        }
    }

    return $names;
}

It is long

Refactoring
function get_car_equipment_names($car){
    return $car->equipments()->pluck('name')->filter();
}

6. Stop calling query in loop

Ex1:

The original code
    $books = Book::all(); // *1 query*

    foreach ($books as $book) {
        echo $book->author->name; // Make one query like *select \* from authors where id = ${book.author_id}* every time it is called
    }

N + 1 queries problem

Refactoring
    $books = Book::with('author')->get();
    // Only two queries will be called.
    // select * from books
    // select * from authors where id in (1, 2, 3, 4, 5, ...)

    foreach ($books as $book) {
        echo $book->author->name;
    }
Simple implementation of $books = Book::with('author')->get() code as the bellow.
    $books = Book::all(); //*1 query*
    $books_authors = Author::whereIn('id', $books->pluck('author_id'))->get()->keyBy('author_id'); // *1 query*
    foreach($books as $book){
        $books->author = $books_authors[$book->author_id] ?? null;
    }

Ex2;

The original code
    $books = Book::all(); // *1 query*
    foreach($books as $book){
        echo $book->comments()->count(); // Make one query like *select count(1) from comments where book_id = ${book.id}* every time it is called
    }

N + 1 queries problem

Refactoring

=>
php
$books = Book::withCount('comments')->get();
// Only two queries will be called.
foreach($books as $book){
echo $book->comments_count;
}

Simple implementation of $books = Book::withCount('comments')->get() code as the bellow.
    $books = Book::all(); // *1 query*
    $books_counts= Comment::whereIn('book_id', $books->pluck('id'))->groupBy('book_id')->select(['count(1) as cnt', 'book_id']
    )->pluck('book_id', cnt); // *1 query*

    foreach($books as $book){
        $book->comments_count = $likes[$book->id] ?? 0;
    }

Total: 2 queries

Note: Read more about eager-loading
In some frameworks, like phoenix, lazy-load is disabled by default to stop incorrect usage.

7. Stop printing raw user inputted values in blade.

The original code
    <div>{!! nl2br($post->comment) !!}</div>

There is a XSS security issue

Refactoring
    <div>{{ $post->comment }}</div>

Note: if you want to keep new line in div, you can use **white-space: pre-wrap**
Rules: Don't use printing raw({!! !}}) with user input values.

8. Be careful when running javascript with user input.

The original code
    function linkify(string){
        return string.replace(/((http|https)?:\/\/[^\s]+)/g, "<a href="%241">$1</a>")
    }

    const $post_content = $("#content_post");
    $post_content.html(linkify($post_content.text()));

There is a XSS security issue.
Easy hack with input is http:deptrai.com<a href="javascript:alert('hacked!');">stackoverflow.com</a> or http:deptrai.com<img src="1" alt="image">

Refactoring
    function linkify(string){
        return string.replace(/((http|https)?:\/\/[^\s]+)/g, "<a href="%241">$1</a>")
    }

    const post = $("#content_post").get(0);
    post.innerHTML = linkify(post.innerHTML)

Bonus: simple sanitize function
Note: Don't use unescaped user input to **innerHTML. Almost javascript frameworks will sanitize input before print it into Dom by default.
So, be careful when using some functions like **react.dangerouslySetInnerHTML* or jquery.append() for user inputted values.*

In my test results, WAF(Using our provider's default rules) can prevent server-side XSS quite well, but not good with Dom-Base XSS.

Rules: Be careful when exec scripts that contains user input values.

9. Stop abusing Morph

When using morph, we cannot use foreign key relationship and when the table is grow big we will face performance issues.

The original code
    posts
        id - integer
        name - string

    videos
        id - integer
        name - string

    tags
        id - integer
        name - string

    taggables
        tag_id - integer
        taggable_id - integer
        taggable_type - string
Refactoring
    posts
        id - integer
        name - string

    videos
        id - integer
        name - string

    tags
        id - integer
        name - string

    posts_tags
        tag_id - integer
        post_id - integer

    videos_tags
        tag_id - integer
        video_id - integer

Original post is: https://khoinv.com/post/639439824292593665/write-better-laravel-code

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

LaravelでFormRequestを使ってバリデーションする方法

Controllerが肥大化(MVCの宿命)してしまうのを少しでも解決するために、バリデーションはFormRequestを使って処理するのが良さそうだったので、自分用のメモとして使い方をまとめてみた。

FormRequestとは

バリデーションルールを外部クラス(FormRequestクラス)にまとめることができ、任意の処理で呼び出すことができる
FormRequestを下記のようにDIしてあげると、バリデーションが通った時だけコントローラー内の処理が走るようになります
このようにバリデーションを外部クラスで処理することでControllerでは自分の処理に専念することができる

public function store(StoreRequest $request)
{
  // バリデーションが通ったときにここの処理が走る
}

使ってみる

artisanコマンドで作成できる

php artisan make:request StoreRequest
StoreRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // デフォルトはfalse(アクセス権限を付けない場合はtrueにする)
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            // ここにバリデーションルールを書いていく
        ];
    }
}

バリデーションルール

    public function rules()
    {
        return [
           'title' => 'required|unique:posts|max:255',
           'body' => 'required',
        ];
    }

こんな感じで書く

バリデーションエラー時のレスポンスをjsonに変更

デフォルトではレスポンスがHTMLのためAPI開発などでも扱いやすいようjsonに変更する
バリデーションエラー時に実行されるfailedValidationメソッドをオーバーライドする

    protected function failedValidation(Validator $validator)
    {
        $res = response()->json([
            'status' => 400,
            'errors' => $validator->errors(),
        ],400);
        throw new HttpResponseException($res);
    }

他にも色々な機能がある

https://qiita.com/OKCH3COOH/items/53db8780027e5e11be82

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

PHP学習備忘録

去年の10月から新人プログラマとして開発に挑戦しています。
以前は運用系の業務だったため、自分で何かを作って動かすのはほぼ未経験です。
今までは、研修でJavaを覚えてきましたが、新たにPHPを覚えることになりそうなので、PHPについて色々調べてみました。
なかなか物覚えはよくない方なので、自身の備忘録としてここに残しておこうと思います。

PHPについて

PHPとは正式名称は「Hypertext Preprocessor」と呼ばれているとのことです。
ハイパーテキストって書かれている通り、WEB開発で非常に使われている言語になっているようです。
(私はPHP初心者なのでまだ細かいことはわかりませんが…)
簡単にWEBサイトを作成できることでおなじみの「WordPress」もPHPがベースになっています。

PHPの記述

PHPはHTMLの中に記述できます。拡張子は.phpとなります。
HTML内で記述する際はbodyタグの中に書きます。

HTML文書内に記述する場合、文頭は、<?phpとなり、
?>が締めとなります。
ただ、PHPだけで記述されたphpファイルは?>で締めないことが多いようです。
また、PHPは命令の最後にセミコロンを記述しないと、セミコロンが記述されるまでずっと一つの文として扱ってしまいます。

PHPの構文

出力

実際にPHPの構文を使って何か文字を出力します。
どんなプログラミング言語もまず初歩は、「HELLO WORLD」を画面に出力することから始まることが多いですね。
PHPでは出力に「echo」を使います。JavaでいうところのSystem.out,print()、C++でいうところの、coutみたいなものでしょうか。
おなじみのHELLO WORLDを出力したい場合、

test.php
<body>
    <?php
    echo "HELLO WORLD";
    ?>
</body>

と記述します。文字の出力は、自分の知っている言語の中で一番シンプルに見えます。

test.php
print "HELLO WORLD";

これでも記述できます。「print」はなじみ深い単語ですね。ただ文字列を出力するだけでも別解が色々あるんですね。

文字列の出力ができたら次は、条件文に行くことが多いイメージですが、
PHPの変数の書き方が独特だったので、まず変数について考えていきます。

変数の扱い

PHPでは$を変数にしたい文字の頭につけます。PHPには型宣言がなんと無いそうな。
変数の命名規則は、文字列は先頭がアンダースコアかアルファベット、その後にアンダースコア、数字、アルファベットとなります
型宣言intだの、Stringだの考えなくていいのは少し楽に思えます。
ただし型宣言が不要なだけで、データ型自体はばっこしあります。(intとかbooleanとかいうアレ)
文字列を出力したい場合は、ダブルクォーテーション(シングルクォーテーションでも可)
で挟まないと文字列として認識されないことは注意が必要です。他の言語でも同様ですね。

test.php
 $変数 = "test";

ちなみにデータ型を知りたいときは「var_dump」なるものがあります。

test.php
 $変数 = "test";
 var_dump($変数);

こう書くと、$変数のデータ型を教えてくれます。この例で行くとString型になります。

また、値を変えたくない、つまり定数として扱い場合はdefineを使います。

test.php
define("定数",);

となります。不変の定数(円周率π、自然対数の底e、プランク定数?...etc)やいじりたくない数字、文字を扱うのにいいですね。
Javaでいうfinalみたいなイメージでしょうか。
ここまで、変数と定数について調べたので、次は繰り返し処理について調べてみました。

繰り返し処理

PHPもほかのプログラミング言語と同様に、for文、while文などの繰り返し処理分が一通りあります。
やはり前述の二つはとりわけし開発初心者の私にとってなじみ深いので
ここからPHPの条件式に慣れていきたいです。

for文

おなじみの繰り返し文ですね。
構文は次のようになります。

test.php
for(初期化文;条件式;更新式){
//ループ処理
}

Javaとほぼ同じですね、違うといえば変数を扱う際に、変数の頭に$を付ける必要があることと、PHPなので
<?php
?>
で挟まなきゃいけないくらいでしょうか。
また、このままだと改行がされないので、nl2br("\n")なるものをつけると改行されます。
brは個人的に乱用したくないので、nl2br関数を使っていこうと思います。

php.test.php
for($i=0; $i<=5; $i++){
    echo $i.nl2br("\n");
}

私はこれで改行ができていました。ちなみに繰り返し処理同様によく使われるwhile文
条件式、Javaなどの他のプログラミング言語とほぼ同じでしたので割愛します。

配列

PHPの非常に簡単なルールがわかったところで、配列について調べてみます。
PHPでもそこまで配列の仕様はほかの言語は変わらないようです。

test.php
$変数 = [値1, 値2, 値3, ...]
echo $変数[n]

これもそこまで難しくなさそうですね。やはりシンプルです。PHPが初心者向けってよく言われるのが何となくわかったような気がしないでもないです。
配列の中身の取り出し方も[]の中に取り出したい要素のインデックスナンバーを記述すればよいです。
例によって0から始まるので、配列の最初の要素を取り出すときは$値[0]となることに注意しましょう。

関数

PHPでも関数は使うことができます。
複雑な処理をまとめて処理したい場合、もしくは単調だけど何度も書かなきゃいけない処理なんかは関数を使うとコードがすっきりして見やすくなりますね。

test.php
function 関数名(){
//関数の処理内容
}

となります。これもほかの言語でよく見る形になりますね。
関数を呼び出すときは、関数名()を記述するだけでその処理を呼び出せます。
もちろん、
<?php
?>
内で記述しなきゃダメです。他のプログラミング言語にも言えることですが、何度も記述する必要のある処理は、関数にしてまとめてあげることで、単純に打ち間違いなども減らし、修正する時間を減らすことができるので、単純に生産性が向上すると思っています

あとがき

Javaの勉強さることながら、新しくPHPも覚えていきゃなきゃならん!となったとき、ものすごく不安になりましたが、ふたを開けてみたら今まで見たきた言語に似ている部分も多く、むしろPHPでは記述がシンプルなもの多いように感じました。
まだまだ、基礎中の基礎しか勉強していないので勉強中の部分もあります。
これからも少しずつ、備忘録も兼ねてPHPの勉強内容を追記し、あとで見返したときに参考にできればと思う次第です。

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