20200811のPHPに関する記事は19件です。

【Eloquent】よく使うメソッド集

 Eloquentは便利ですよね。どんなメソッドがあるかまとめたくなったので、ここに書いちゃおうと思います。

*使用環境
 PHP 7.4.5
 Laravel 7.22.4

基本的な使い方

 まずはモデルクラスを用意します。Itemクラスならば以下のようなコマンドで作れます。
php artisan make:model Item
後はそのモデルクラスを使って、あとで出る表にあるメソッドでどんどんデータ抽出するだけです。こんな感じで->でくっつけていきます。

<?php
use Item;  //この一文を書き忘れるとエラーになってしまう。

$result = Item::where('value', 1000)->limit(10)->get();

集計系(selectで使うような)

メソッド名 引数 説明
max 1.カラム名 max('value')とすればselect max(value)ということになります。

結合系(joinを使うような)

メソッド名 引数 説明
join 1.テーブル名 2.主となるテーブルの結合カラム 3.演算子(=など) 4.従となるテーブルの結合カラム join('type', 'item.item_type', '=', 'type.id')とすれば、inner join type on item.item_type = type.idということになります。

抽出系(whereで使うような)

メソッド名 引数 説明
where 1.カラム名 2.完全一致させるもの where('id', 'solabito331')とすればwhere id='solabito331'という抽出のしかたになります。
where 1.カラム名 2.演算子(=など) 3.比較対象 where('id', '>', 10)とすればwhere id>10 という抽出のしかたになります。
whereNotNull 1.カラム名 whereNotNull('age')とすれば、where age is not nullという抽出のしかたになります。
limit 1.件数 limit(10)とすれば、10件に絞られます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

独学未経験エンジニアがweb系自社開発企業でアルバイトを2ヶ月してみて感じたこと

簡単な自己紹介

  • 大学では機械工学を専攻 
  • プログラマに魅力を感じ2019年新卒でメーカー子会社のIT会社に入社
  • 入社してから10ヶ月で会社を退職
  • そこから4ヶ月間独学で勉強し、現在web系の自社開発企業でアルバイト中

ニートになって独学していた時代

新卒で入社したときに感じたことや退職理由はまた今度別の記事で書いてみたいと思います。

勉強したこと一覧

  • progate(HTML,CSS,Javascript,Ruby,RubyonRails)

  • railsチュートリアル解説動画
    2周はした。1周目は動画を見るだけで、2周目は手を動かしながらでした。
    かなりお世話になり、railsチュートリアルとは友達になった気がする

  • Ruby on Rails5 超入門
    amazonリンク
    読んだのは3割ぐらいだけ

  • Ruby on Rails5 速習実践ガイド
    amazonリンク
    辞書的な感じで使ったりした。暇なときに読んでみてもためになることが多い。

  • Gitが、おもしろいほどわかる基本の使い方33
    amazonリンク
    gitの操作の基礎を学んだ。すぐに読み終わる内容なので、読みやすい

  • キタミ式イラストIT塾 基本情報技術者
    amazonリンク
    コンピュターの基礎を理解するのにはちょーど良かった。時間をかけてちょっとずつ読んだ

  • webを支える技術
    amazonリンク
    何を書いているのか全然分からなかった。kindle版で技術書は読みにくい。

  • 図解即戦力 AWSのしくみと技術がしっかりわかる教科書
    amazonリンク
    AWSのサービスの概要は大体理解したけど、実装まではできず

  • プログラミングスクール受講(ポテパンキャンプ)
    ポテパンのリンク
    gitの操作、N+1問題、Rspecの書き方はここで自主的に学んだ

  • Everyday Rails - RSpecによるRailsテスト入門 購入リンク
    ちょくちょく参考にできた。これも詰まったときに検索する用。

ポートフォリオ作成

当時はAWS,Docker, CI/CD, Kubernetes等についてはほぼほぼ理解できずに、断念していました。
開発環境なんかはrailsチュートリアルで使っていたcloud9を使っていました。
まずはHerokuに自分が一生懸命作成したアプリケーションをデプロイし、公開できるようにしました。
一応独自ドメイン,SSL化程度は行っておき、Herokuは有料プランを使用し、少しでも応答速度が早くなるようにしておきました。
一応当時のポートフォリオのソースコードのリンクを貼っておきます。
https://github.com/ak2-lucky/clothes-app
ファッションが好きだった僕は洋服のレビューサイトを作りました。

この記事を見ている駆け出しエンジニアの日々勉強されている方々はこう思っているかもしれません。
AWSにデプロイすらできないのに、webエンジニアになれるの?
Dockerで開発環境も構築できないの?
自動テストぐらいはやっといたら?

現役のwebエンジニアも同じことを思うでしょう。
ポートフォリオも作りきれないような奴はエンジニアになる資格なんかないよって。
なぜならポートフォリオだけは運にも左右されない自分の努力だけで100パーセント作りきれる成果物だからです。

確かにそうです。
僕の努力不足です。何の異論もありません。
認めます。

そして就職活動をはじめます。

就職活動時代

結果からゆうとほぼ全落ち。
面接までいったのは1.2回でした。
面談してくれた会社で研究開発で人材を募集しているからそこに来ないか?と言ってくれた企業もありました。(結局いかず)
コロナの影響とかいう言い訳はしません。
全て自分の実力不足。
アルバイトも採用している企業に応募し,1社だけ何とかアルバイトとして採用していただきました。

今回で自分の市場価値を知りました。
大学は何も考えずに過ごし、新卒で入社した会社を何の成果も残さず10ヶ月で退職。
世間はそんな奴のことを評価はしてくれません。
もちろん当然の評価です。

アルバイトとしてweb系自社開発企業で働く(今)

技術スタック:AWS,Docker,Laravel+vue.js

働いてみるまでLaravelはおろかPHPも触ったことありませんでした。vue.jsもですが。
詳細は省きますが、実際に働いてみて感じるのは、プログラムを書く以外のことです。
技術のキャッチアップ云々の話ではなくて、組織作り、開発の体制やフローなどの重要さについてです。
自分はプログラミングが苦ではありません。
しかし、プログラミング業務以外の部分でストップすることが多い場合があります。(例えば、なぜこの変更を加えられたのかというコメントがないandコードを見ても分からない)
連携がうまく取れなくて、個々の意思で変更が加えられたりするので、デザインがバラバラ。
issue作成者等に確認もしないで変更を進められていたり。
CI/CDもそのうちの一つですが、issueを作成するのは誰なのか、誰の判断を最終的に仰ぐのか、UIのデザインは好きに決めていいのか、プルリクではどこを見ればいいのか、など他にもいろいろありますが。。。

会社によって開発の進め方などは違うと思うので、一概にあれが悪い、これが悪いというわけではありませんが、開発体制や組織作りはサービス開発にかなり大きく影響するように感じました。

開発言語や技術以前にこういった根本的な開発の導線を確保する重要性を学びました。

最後に

ここまで読んでいただいた方には、いろいろ思うことがあると思います。
2ヶ月しか働いていない、しかもアルバイト如きが何を偉そうなこと言ってるんだ!
とか思ってる方いるかもしれません。

ですが、これは個人の一意見であります。
ただ同じように駆け出しエンジニアの方の参考になればいいなと思って書きました。

webエンジニアへの道は高く険しいように感じます。
しかし、自分にとって登りたい山がどれだけ高かろうが、険しかろうが、関係ありません。
ただ登るだけ
毎日頑張って積み上げて、疲れたら一緒にサボりましょう。

長くなりましたが、初投稿は以上になります。

ここまで読んでいただきありがとうございました。

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

PHP_CodeSnifferのルールまとめ

概要

PHP_CodeSnifferにはどのようなルールがあるのか調べてまとめています。
バージョン^3.5を対象としています。

PHP_CodeSnifferとは

squizlabs/PHP_CodeSniffer

PHP_CodeSnifferはコーディング標準の違反検出するスクリプトと、それを自動で修正するスクリプトです。

PHP_CodeSniffer is a set of two PHP scripts; the main phpcs script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second phpcbf script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent.

デフォルトのコーディング標準

デフォルトで次のコーディング標準がインストールされています。

$ phpcs -i
The installed coding standards are MySource, PEAR, PSR1, PSR12, PSR2, Squiz and Zend

この中からPSR2、PSR12にどのようなルールが含まれるかリストアップします。

$ phpcs --standard=PSR12 -e

ルール一覧

※ MySource、Zendは対象(PSR2、PSR12)に含まれていないので省略します。

PSR2 PSR12
Generic Arrays ArrayIndent
DisallowLongArraySyntax
DisallowShortArraySyntax
Classes DuplicateClassName
OpeningBraceSameLine
CodeAnalysis AssignmentInCondition
EmptyPHPStatement
EmptyStatement
ForLoopShouldBeWhileLoop
ForLoopWithTestFunctionCall
JumbledIncrementer
UnconditionalIfStatement
UnnecessaryFinalModifier
UnusedFunctionParameter
UselessOverridingMethod
Commenting DocComment
Fixme
Todo
ControlStructures DisallowYodaConditions
InlineControlStructure
Debug CSSLint
ClosureLinter
ESLint
JSHint
Files ByteOrderMark
EndFileNewline
EndFileNoNewline
ExecutableFile
InlineHTML
LineEndings
LineLength
LowercasedFilename
OneClassPerFile
OneInterfacePerFile
OneObjectStructurePerFile
OneTraitPerFile
Formatting DisallowMultipleStatements
MultipleStatementAlignment
NoSpaceAfterCast
SpaceAfterCast
SpaceAfterNot
SpaceBeforeCast
Functions CallTimePassByReference
FunctionCallArgumentSpacing
OpeningFunctionBraceBsdAllman
OpeningFunctionBraceKernighanRitchie
Metrics CyclomaticComplexity
NestingLevel
NamingConventions CamelCapsFunctionName
ConstructorName
UpperCaseConstantName
PHP BacktickOperator
CharacterBeforePHPOpeningTag
ClosingPHPTag
DeprecatedFunctions
DisallowAlternativePHPTags
DisallowRequestSuperglobal
DisallowShortOpenTag
DiscourageGoto
ForbiddenFunctions
LowerCaseConstant
LowerCaseKeyword
LowerCaseType
NoSilencedErrors
RequireStrictTypes
SAPIUsage
Syntax
UpperCaseConstant
Strings UnnecessaryStringConcat
VersionControl GitMergeConflict
SubversionProperties
WhiteSpace ArbitraryParenthesesSpacing
DisallowSpaceIndent
DisallowTabIndent
IncrementDecrementSpacing
LanguageConstructSpacing
ScopeIndent
SpreadOperatorSpacingAfter
PEAR Classes ClassDeclaration
Commenting ClassComment
FileComment
FunctionComment
InlineComment
ControlStructures ControlSignature
MultiLineCondition
Files IncludingFile
Formatting MultiLineAssignment
Functions FunctionCallSignature
FunctionDeclaration
ValidDefaultValue
NamingConventions ValidClassName
ValidFunctionName
ValidVariableName
WhiteSpace ObjectOperatorIndent
ScopeClosingBrace
ScopeIndent
PSR1 Classes ClassDeclaration
Files SideEffects
Methods CamelCapsMethodName
PSR2 Classes ClassDeclaration
PropertyDeclaration
ControlStructures ControlStructureSpacing
ElseIfDeclaration
SwitchDeclaration
Files ClosingTag
EndFileNewline
Methods FunctionCallSignature
FunctionClosingBrace
MethodDeclaration
Namespaces NamespaceDeclaration
UseDeclaration
PSR12 Classes AnonClassDeclaration
ClassInstantiation
ClosingBrace
ControlStructures BooleanOperatorPlacement
ControlStructureSpacing
Files DeclareStatement
FileHeader
ImportStatement
OpenTag
Functions NullableTypeDeclaration
ReturnTypeDeclaration
Namespaces CompoundNamespaceDepth
Operators OperatorSpacing
Properties ConstantVisibility
Traits UseDeclaration
Squiz Arrays ArrayBracketSpacing
ArrayDeclaration
CSS ClassDefinitionClosingBraceSpace
ClassDefinitionNameSpacing
ClassDefinitionOpeningBraceSpace
ColonSpacing
ColourDefinition
DisallowMultipleStyleDefinitions
DuplicateClassDefinition
DuplicateStyleDefinition
EmptyClassDefinition
EmptyStyleDefinition
ForbiddenStyles
Indentation
LowercaseStyleDefinition
MissingColon
NamedColours
Opacity
SemicolonSpacing
ShorthandSize
Classes ClassDeclaration
ClassFileName
DuplicateProperty
LowercaseClassKeywords
SelfMemberReference
ValidClassName
Commenting BlockComment
ClassComment
ClosingDeclarationComment
DocCommentAlignment
EmptyCatchComment
FileComment
FunctionComment
FunctionCommentThrowTag
InlineComment
LongConditionClosingComment
PostStatementComment
VariableComment
ControlStructures ControlSignature
ElseIfDeclaration
ForEachLoopDeclaration
ForLoopDeclaration
InlineIfDeclaration
LowercaseDeclaration
SwitchDeclaration
Debug JSLint
JavaScriptLint
Files FileExtension
Formatting OperatorBracket
Functions FunctionDeclarationArgumentSpacing
FunctionDeclaration
FunctionDuplicateArgument
GlobalFunction
LowercaseFunctionKeywords
MultiLineFunctionDeclaration
NamingConventions
NamingConventions ValidFunctionName
ValidVariableName
Objects DisallowObjectStringIndex
ObjectInstantiation
ObjectMemberComma
Operators ComparisonOperatorUsage
IncrementDecrementUsage
ValidLogicalOperators
PHP CommentedOutCode
DisallowBooleanStatement
DisallowComparisonAssignment
DisallowInlineIf
DisallowMultipleAssignments
DisallowSizeFunctionsInLoops
DiscouragedFunctions
EmbeddedPhp
Eval
GlobalKeyword
Heredoc
InnerFunctions
LowercasePHPFunctions
NonExecutableCode
Scope MemberVarScope
MethodScope
StaticThisUsage
Strings ConcatenationSpacing
DoubleQuoteUsage
EchoedStrings
WhiteSpace CastSpacing
ControlStructureSpacing
FunctionClosingBraceSpace
FunctionOpeningBraceSpace
FunctionSpacing
LanguageConstructSpacing
LogicalOperatorSpacing
MemberVarSpacing
ObjectOperatorSpacing
OperatorSpacing
PropertyLabelSpacing
ScopeClosingBrace
ScopeKeywordSpacing
SemicolonSpacing
SuperfluousWhitespace

ルール詳細

それぞれのルールがどのようなチェックを行うのかまとめていきます。
随時追加予定。

Generic.Arrays.ArrayIndent

配列の要素をスペース4つでインデントされているかチェックします。
インデントの数はカスタマイズ可能です。

$array = [
    'ok',
  'ng', // Array key not indented correctly; expected 4 spaces but found 2
];

Generic.Arrays.DisallowLongArraySyntax

長い配列構文を禁止します。

$ok = [];
$ng = array(); // Short array syntax must be used to define arrays

Generic.Arrays.DisallowShortArraySyntax

短い配列構文を禁止します。
DisallowLongArraySyntaxの逆です。

$ok = array();
$ng = []; // Short array syntax is not allowed

Generic.Classes.DuplicateClassName

同じクラスまたはインターフェース名が複数のファイルで使用されている場合、エラーとなります。

Generic.Classes.OpeningBraceSameLine

クラス/インターフェース/トレイトの開き括弧がクラス宣言と同じ行にあることを確認します。

class OK {}

class NG
{ // Opening brace should be on the same line as the declaration for class NG
}

Generic.CodeAnalysis.AssignmentInCondition

条件内で行われている変数の割り当てを検出します。
ただし三項演算では検出されません。

if ($ng = true) { // Variable assignment found within a condition. Did you mean to do a comparison ?
}

// Note: this sniff does not detect variable assignments in the conditional part of ternaries!
$note = true ? 'a' : 'b';

Generic.CodeAnalysis.EmptyPHPStatement

空のPHPステートメントに対してチェックします。

  • 間に実行可能コードがない2つのセミコロン
  • 空のPHP open-closeタグの組み合わせ
<?php
; // Empty PHP statement detected: superfluous semi-colon.
?>

<?php ?> // Empty PHP open/close tag combination detected.

Generic.CodeAnalysis.EmptyStatement

空のステートメントを検出します。(try, catch, finally, do, else, elseif, for, foreach, if, switch, while)
完全に空であるか、空文字またはコメントだけの場合も空とみなされます。

if (true) { // Empty IF statement detected
    // empty
}

おそらく特定のステートメントを除外することも可能です。

<rule ref="Generic.CodeAnalysis.EmptyStatement">
    <!-- But allow empty catch -->
    <exclude name="Generic.CodeAnalysis.EmptyStatement.DetectedCatch"/>
</rule>

Generic.CodeAnalysis.ForLoopShouldBeWhileLoop

whileループに簡略化できるforループを検出します。

for (; true; ) { // This FOR loop can be simplified to a WHILE loop
    break;
}

Generic.CodeAnalysis.ForLoopWithTestFunctionCall

テスト式で関数呼び出しを使用するforループを検出します。

$a = [1, 2, 3, 4];

for ($i = 0; $i < count($a); $i++) { // Avoid function calls in a FOR loop test part
    $a[$i] *= $i;
}

Generic.CodeAnalysis.JumbledIncrementer

forループでジャンピングするインクリメンターを検出します。

for ($i = 0; $i < 10; $i++) { // Loop incrementor ($i) jumbling with inner loop
    for ($k = 0; $k < 20; $i++) {
        echo 'Hello';
    }
}

Generic.CodeAnalysis.UnconditionalIfStatement

無条件のifおよびelseifステートメントを検出します。
trueかfalseのみ設定されている条件が検出されます。

if (true) { // Avoid IF statements that are always true or false
    echo 'Hello';
}

Generic.CodeAnalysis.UnnecessaryFinalModifier

finalクラス内の不要なfinal修飾子を検出します。

final class Sample
{
    public final function hoge() // Unnecessary FINAL modifier in FINAL class
    {
    }
}

Generic.CodeAnalysis.UnusedFunctionParameter

未使用の関数パラメーターを検出します。
ただし関数の中身が空、またはコメントのみの場合は検出されません。

function hoge($hoge) { // The method parameter $hoge is never used
    echo 'Hello';
}

function fuga($fuga) {
}

Generic.CodeAnalysis.UselessOverridingMethod

単に親を呼び出す、不要なオーバーライドされたメソッドを検出します。

class Hoge
{
    public function __construct()
    {
        echo 'Hello';
    }
}

class Fuga extends Hoge
{
    public function __construct() // Possible useless method overriding detected
    {
        parent::__construct();
    }
}

Generic.Commenting.DocComment

docブロックが基本的なフォーマットに従っていることを確認します。

/** */

/** example
 */

Generic.Commenting.Fixme

FIXMEコメントについて警告します。

// fixme: hoge

Generic.Commenting.Todo

TODOコメントについて警告します。

// todo: hoge

訂正等

PHP_CodeSnifferのバージョン

3.0 → ^3.5

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

LaravelのUnitテストでRefreshDatabaseを使ったらSAVEPOINT trans2 does not exist が出た話

現象

Laravelを利用した環境で通常のケースではTransactionも機能しており問題なかったが、RefreshDatabaseを用いたPHPUnitでの実行ではエラーが発生していた。

Doctrine\DBAL\Driver\PDOException : SQLSTATE[42000]: Syntax error or access violation: 1305 SAVEPOINT trans2 does not exist
 /src/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:43
 /src/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:260
 /src/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:238
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php:90
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:233
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:149

 Caused by
 PDOException: SQLSTATE[42000]: Syntax error or access violation: 1305 SAVEPOINT trans2 does not exist

 /src/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:41
 /src/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:260
 /src/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:238
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php:90
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:233
 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:149

環境

全てdockerコンテナ内

  • mysql: 5.7
  • Laravel: 6.18.34
  • PHPUnit: 9.3.0

先に結論

調査方法

  • MySQLのクエリログを有効にする
[mysqld]
general_log=ON 
general_log_file=general_query.log
  • テストを実行する
  • /var/lib/mysql/general_query.log を確認
  • QueryとExecuteだけ抜き出し

今回はこの時点でSTART TRANSACTION後にテーブルのtruncateを行っていた事に気づけたが一応MySQLコンソールにクエリ流し込んでエラーになることを確認したりもした。

あとがき

LaravelのRefreshDatabaseでRollback出来ない、Transactionがおかしいみたいな内容はググると結構出てくるが、結局きちんと解決しているものが少ないように思えたので残してみることにした。

はじめはLaravel側でクエリログを出力していたが、SAVEPOINT周りなんかはTransactionRolledBackTransactionCommittedTransactionBeginningで追いきれなかったのでMySQL側でログ出力したほうが確実だったなと後から反省。

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

VSCode Remote-Containers で Laravel7 開発環境 ( CentOS8+PHP7.4+MongoDB4.4 ) を爆速で準備する

はじめに

Docker で開発環境コンテナをサクッと準備。実際の開発は VSCode + Remote-Containers ( 拡張機能 ) を使ってコンテナ内で行うための手順をまとめています。記事内容に間違いなどありましたら、コメント欄にてご指摘お願いいたします。
まだ色々と制約はありますが、とりあえずコンテナを立ち上げるところまでは上手くいったので公開します。

前提条件

  • Docker ( Windows/MacOS の方は Docker Desktop ) が使用できること
  • VSCode が使用できること

ファイル構成

以下の構成でディレクトリとファイルを作成していきます。
とりあえず動かしてみたい方は GitHub から Clone してください。

Project/
  ├─ .devcontainer/
  │    ├─ devcontainer.json
  │    └─ Dockerfile
  └─ mongodb-org.repo

手順.1 : VSCode に Remote-Containers ( 拡張機能 ) をインストール

VSCode に Remote-Containers という拡張機能をインストールしてください。

手順.2 : devcontainer.json 作成

.devcontainer/devcontainer.json
{
    "name": "test",
    "context": "..",
    "dockerFile": "Dockerfile",
    "settings": {
        "terminal.integrated.shell.linux": null,
        "workbench.startupEditor": "newUntitledFile",
        "editor.minimap.enabled": true,
        "vsicons.dontShowNewVersionMessage": true,
        "explorer.confirmDelete": false,
        "files.autoSave": "afterDelay"
    },
    "appPort": [ 8000, 27017 ],
    "remoteUser": "vscode",
    "workspaceFolder": "/var/www/html",
    "extensions": [
        "alefragnani.bookmarks",
        "mikestead.dotenv",
        "mhutchie.git-graph",
        "eamodio.gitlens",
        "onecentlin.laravel-blade",
        "austenc.laravel-blade-spacer",
        "onecentlin.laravel5-snippets",
        "cjhowe7.laravel-blade",
        "felixfbecker.php-pack",
        "vscode-icons-team.vscode-icons"
    ]
}

手順.3 : Dockerfile 作成

.devcontainer/Dockerfile
FROM centos:8

LABEL maintainer="slangsoft"
LABEL version="1.0"
LABEL description="Laravel sample project for trying out the VS Code Remote - Containers extension."

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ARG PATH_LARAVEL=/var/www/html

# Basic settings
RUN set -x \
    && dnf -y upgrade \
    && dnf -y install sudo dnf-utils wget git \
    && groupadd --gid $USER_GID $USERNAME \
    && useradd --shell /bin/bash --uid $USER_UID --gid $USER_GID --create-home $USERNAME \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME

# Install nginx
RUN  dnf -y install nginx

# Install php
RUN set -x \
    && dnf module reset php \
    && dnf -y install epel-release glibc-locale-source glibc-langpack-en \
    && dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm \
    && dnf -y module install php:remi-7.4

# Install Laravel
EXPOSE 8000
RUN set -x \
    && dnf -y install php-mcrypt php-pdo php-bcmath php-tokenizer php-xml php-mysqlnd php-gd php-intl php-zip php-opcache php-pecl-xdebug \
    && wget https://getcomposer.org/installer -O composer-installer.php \
    && php composer-installer.php --filename=composer --install-dir=/usr/local/bin \
    && composer self-update \
    && composer create-project laravel/laravel $PATH_LARAVEL --prefer-dist \
    && groupadd laravel \
    && gpasswd -a $USERNAME laravel \
    && gpasswd -a nginx laravel \
    && find $PATH_LARAVEL -type d -exec chmod 775 \{\} \; \
    && find $PATH_LARAVEL -type f -exec chmod 664 \{\} \; \
    && chown -R :$USERNAME $PATH_LARAVEL \
    && find $PATH_LARAVEL -type d -exec chmod g+s \{\} \; \
    && setfacl -R -d -m g::rwx $PATH_LARAVEL

# Install MongoDB
EXPOSE 27017
COPY ./mongodb-org.repo /etc/yum.repos.d/
RUN dnf -y install mongodb-org

# Set locale
RUN localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG="ja_JP.UTF-8"
ENV LANGUAGE="ja_JP:ja"
ENV LC_ALL="ja_JP.UTF-8"

# Working directory setting
WORKDIR $PATH_LARAVEL

手順.4 : mongodb-org.repo 作成

mongodb-org.repo
[mongodb-org-4.4]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc

手順.5 : コンテナ起動

コマンドパレットを開いて [remote-containers: Open Folder in Container...] を実行するとコンテナのビルドが始まります。無事にビルドが完了すると、コンテナ内で VSCode が起動します。

参考リンク

改訂履歴

  • 2020/08/11
    初版公開 ( ほとんど説明も無い叩き台記事 )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP/Laravel】Class 'Collective\Html\HtmlServiceProvider' not found への対処法

はじめに

Laravelでアプリ開発をしております。
環境構築が完了し、Formを実装した際にエラーが発生。
備忘のために記録しております。
同じような方の助けになれば幸いです。

開発環境

・MacOS:10.15.6 (Catalina)
・PHP7.3.20
・Laravel6.18.35

対処法

①プロジェクト直下の「composer.json」に「"laravelcollective/html": "^6.0"」を追記

※これでもエラーが起きる場合はLaravelのバージョンとlaravelcollective/htmlのバージョンを合わせる(ex. Laravel5.8なら、laravelcollective/htmlの5.8)。

composer.json
 "require": {
        "php": "^7.2",
        "fideloper/proxy": "^4.0",
        "laravel/framework": "^6.2",
        "laravel/tinker": "^2.0" ,
        "laravelcollective/html": "^6.0"
    },

②ターミナルで「composer update」を実行

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 3 updates, 0 removals
  - Updating egulias/email-validator (2.1.18 => 2.1.19): Downloading (100%)       - Installing league/mime-type-detection (1.4.0): Downloading (100%)         
  - Updating league/flysystem (1.0.70 => 1.1.0): Downloading (100%)         
  - Updating nikic/php-parser (v4.7.0 => v4.8.0): Downloading (100%)         
  - Installing laravelcollective/html (v6.1.2): Downloading (100%)         
Package jakub-onderka/php-console-color is abandoned, you should avoid using it. Use php-parallel-lint/php-console-color instead.
Package jakub-onderka/php-console-highlighter is abandoned, you should avoid using it. Use php-parallel-lint/php-console-highlighter instead.
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: laravelcollective/html
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
40 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

③「config/app.php」への下記の追記

※Laravel5.5以上はPackage Auto Discoveryが入っているため不要。

config/app.php
   'providers' => [
        Collective\Html\HtmlServiceProvider::class,
    ],

    'aliases' => [
        'Form' => Collective\Html\FormFacade::class,
        'Html' => Collective\Html\HtmlFacade::class,
    ],

以上となります。

以下、参考にさせていただいたサイトも掲載させていただきます。
ご参考にしていただければ。

参考文献

Laravelで「Formクラスが無い」とエラーが出た時の対処法
【Laravel】Formファサード(laravelcollective/html)をインストールする

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

"Test code or tested code did not (only) close its own output buffers"エラーの調査方法

Cakephp3でControllerのテストをしていたのですが、次のようなメッセージが表示されました

Test code or tested code did not (only) close its own output buffers

エラーメッセージでコードを検索すると、出力バッファの階数を検査している箇所でエラーになっていることが分かりました。出力バッファを開始したもののきちんと終了できていないことが原因でにエラーになっています。

/vendor/phpunit/phpunit/src/Framework/TestCase.php
private function stopOutputBuffering()
{
    if (\ob_get_level() != $this->outputBufferingLevel) {
        while (\ob_get_level() >= $this->outputBufferingLevel) {
            \ob_end_clean();
        }

        throw new RiskyTestError(
            'Test code or tested code did not (only) close its own output buffers'
        );
    }
    ...
    以下続きます

そこで、テスト対象のコードから ob_start() を探して、終了するまでの間に何か起きていないか調べてました。デバッガを止めながら実行してみると、途中でExceptionを吐いている箇所を発見することができました。

まとめるとTest code or tested code did not (only) close its own output buffersのエラーになった場合は、デバッガでステップ実行しながらExceptionを吐き出している箇所を見つけましょう。ということになります。

バージョン

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

PHPでRedis Streams操作のメモ

PHPでStream Consumingしたい、ということでRedis Streamsを使って試してみたという記録です。Swooleを使ってサーバー1台でスケールするのかも試してみました。
関連ドキュメントをざっと読んで試してみた程度の内容なので間違っている箇所などあるかもしれません。

Redis Streams

Redis5で実装されたStreaming API. Kafkaインスパイアな作りになっている。

PHPからRedis Streamsの使用

phpredisの4.2でサポートされた模様。以前は、導入が楽だという理由でpredisを使うことが多かったけど、predisではサポートされていない。predisは完全に開発が止まってそう。

Redis Server

試すだけならDockerでいいですね。

docker run -d --rm -p 6379:6379 redis:5.0.9

一応Streams APIが動くか確認したかったのでredis-cliなど使って

$ redis-cli
> XADD mystream * key value
> XREAD COUNT 1 STREAMS mystream 0-0
1) 1) "mystream"
   2) 1) 1) "1596783209134-0"
         2) 1) "key"
            2) "value"

みたいな結果になることを確認する。コマンドが複雑で手で打つと(私は)だいたい何回かエラーになるのでコピペする方が楽だと思う。
XADD, XREADのコマンドの詳細はredisのサイトを見てください。

Redis StreamsのConsumerにはメッセージングシステムは無く、Pub/SubのようにSubscribeしておけばProduceされたことを通知される、という挙動にはならない。

Consumer

ざっくりこんな感じのコードで動いた。

<?php

$redis = new Redis();
$redis->connect('redis-server', 6379);

$consumer = new Consumer($redis);

while (true) {
    $values = $consumer->consume();
    foreach ($values[$streamName] ?? [] as $consumed) {
        foreach ($consumed as $key => $value) {
            print_r($value);
        }
    }
}

class Consumer {
    private $redis;
    public function __construct(Redis $redis) {
        $this->redis = $redis;
        $this->createGroupIfNotExists();
    }

    private function createGroupIfNotExists() {
        // XREADではなくXREADGROUPを使うため、事前にConsumerGroupが存在することを保証する
        // わざわざxInfoで確認してるけど、XGROUPにはGROUPがなかったときに作成する機能もあり
        // ライブラリもサポートしているので素直にxGroupのMKSTREAMオプションを有効にする方が良いでしょう
        $groupInfo = $this->redis->xInfo('GROUP', 'mystream', 'mygroup');
        if (!$groupInfo) {
            $this->redis->xGroup('CREATE', 'mystream', 'mygroup', '>');
        }
        // これと等価
        // '>' の意味は概要を後述しますが、Redis StreamsのIntroductionを読むと良いです。
        // $this->redis->xGroup('CREATE', 'mystream', 'mygroup', '>', $mkStream = true);
    }

    public function consume() {
        return $this->redis->xReadGroup('group1', 'consumer1', ['mystream' => '>'], $count = 10, $block = 2000);
    }
}

なぜXREADではなくXREADGROUPを使うかというと、スケーラビリティのためと、単純にどこまでのIDを読み取ったか管理するのがめんどくさいから。XREADにはRedis側にどこのキーまで読んだか、というオフセットを記録するような仕組みが無いようなので、Consumer単位で自動的に未取得部分のレコードを取得したい場合はXREADGROUPを使用する必要がありそう。

$this->redis->xReadGroup('group1', 'consumer1', ['mystream' => '>'], $count = 10, $block = 2000);

consumeメソッドのこの行の意味は
Consumer Group group1 の Consumer consumer1 で、mystreamというキーのストリームから、group1で未取得のレコードを最大10件取得する。すべてのレコードを取得済みの場合、レコードが追加されることを最大2,000msec待つ。
という挙動になる。このうち、 未取得のレコードを最大10件取得する を実現するために ['mystream' => '>'] と指定をする。

スケーラビリティ

Consumerがスケールするのかを見てみる。

KafkaだとConsumerをスケールさせるためにはTopicにパーティションを設定し、Consumer GroupにConsumerを追加したり、Fan Outさせたりするのだと思う(実運用したことが無いので自信は無い)。

一方Redis Streamsにはこういうパーティションの概念は無さそう。しかし、XREADGROUP'>' はGROUPに対しての値なので、GROUPにConsumerを追加してXREADGROUPを叩けば自動的にそれぞれのConsumerで取得できる。リバランスも気にする必要は無い。
サーバー台数(プロセス数)が増えてBLOCKのリクエストが溜まったときのレイテンシーどうなんだろう。READリクエストがキューに積まれて順番に配信されるのかな。特定のConsumerが特定のパーティションに紐づくわけでもないのでホットスポットも考えなくて良い、となるのかな。

とりあえずは単純にサーバー台数(プロセス数)を増やせばスケールできるのだと思うが、別プロセスで動かすとなるとConsumerの名前決めがめんどくさそう。名前はGROUP内でユニークにしろという制約があるので、マジメにやると大変そう。同じ名前にしても動きはするので、consumerの状態を見るとか、メトリクスを管理するとか運用上の理由が大きいのかな。

Swoole

Redis Streamsにはいわゆるメッセージングシステムが無く、BLOCKを使いながら同期的に処理できるのでPlain-PHPと相性がいいんじゃないか?と思うけど、Swooleを使って単一プロセスでスケールできるか試してみる。

方針として、XREADGROUPは単一プロセスで十分なスループットだという前提の上で、値を取得したらConsumerのワーカー部分を並行で稼働できようにしている。XREADGROUPが追いつかないようになったらRedisへのリクエストも多重化すれば良さそう。

コードはこんな感じになった。

<?php

$messageBufferChannel = new Co\Channel(3);

// Worker
Co\run(function () use ($messageBufferChannel) {
    // Worker内の処理を多重度を制限するためのChannel
    $concurrencyCapChannel = new Co\Channel(5);

    while (true) {
        $values = $messageBufferChannel->pop();
        if ($values) {
            $concurrencyCapChannel->push(true);
            go(function() use ($values, $concurrencyCapChannel) {
                echo "got values " . count($values['mystream']) . "\n";
                $concurrencyCapChannel->pop();
            });
        }
    }
});

// Consumer
Co\run(function () use ($messageBufferChannel) {
    $redis = new Redis();
    $redis->connect('redis-server', 6379);

    while (true) {
        $values = $redis->xReadGroup('group1', 'consumer10', ['mystream' => '>'], $count = 10, $block = 2000);
        if ($values) {
            $messageBufferChannel->push($values);
        }
    }
});

Goもしばらく書いてなかったのでだいぶ難航した。こんな感じで良かったんだったかな…

ちなみにSwooleをDockerで動かすには https://www.swoole.co.uk/docs/get-started/try-docker を参考にすれば良いのだけど、ここで紹介されているイメージにはphpredisが入っていないので

RUN pecl install redis && docker-php-ext-enable redis

をDockerfileに適当に追加してビルドしてる。

試しに書いてはみたものの、Swooleについての知識が薄くて実際にプロダクションで使うにはもっと調べないと不安で使えないな。

関連ドキュメント

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

SimpleSAMLphpで属性をマッピングする

"属性をマッピングする" とは

SimpleSAMLphp とはなにか、SAML とは、などの説明は割愛します。こちらです。

今回やりたいことは、SAML で認証・認可を行う際に渡される属性の名前、およびその値を別名に変換することです。

属性名をマッピングする

ここを読みます。
あらゆる SP や IdP に対して同じように処理を行う場合は、config/config.php を編集します。個別に設定したい場合は、以下のファイルを編集します。

  • SP において、特定の SP について設定 : config/authsources.php
  • SP において、リモート IdP について設定 : metadata/saml20-idp-remote.php または metadata/shib13-idp-remote.php
  • IdP において、ホスト IdP について設定 : metadata/saml20-idp-hosted.php または metadata/shib13-idp-hosted.php
  • IdP において、特定の SP について設定 : metadata/saml20-sp-remote.php または metadata/shib13-sp-remote.php

記法

属性 "A" を属性 "B" にマッピングするとき、

'authproc' => array(
    50 => array(
        'class' => 'core:AttributeMap',
        'A' => 'B',
    ),
),

50 は 50 じゃなくてもいいです。
複数定義する時は、","区切りで並列に並べます。
authproc 内で属性マッピング以外にも様々なフィルターを設定できます。詳しくはこちら

マッピングのルールを別ファイルで定義することもできます。
addurnprefix.php で定義した場合、(デフォルトで入ってます。 attributemap/addurnprefix.php)

'authproc' => array(
    50 => array(
        'class' => 'core:AttributeMap',
        'addurnprefix',
    ),
),
addurnprefix.php
<?php

$attributemap = array(
    'sn'                         => 'urn:mace:dir:attribute-def:sn',
    'telephoneNumber'            => 'urn:mace:dir:attribute-def:telephoneNumber',
    'facsimileTelephoneNumber'   => 'urn:mace:dir:attribute-def:facsimileTelephoneNumber',
    'postalAddress'              => 'urn:mace:dir:attribute-def:postalAddress',
    'givenName'                  => 'urn:mace:dir:attribute-def:givenName',
    'homePhone'                  => 'urn:mace:dir:attribute-def:homePhone',
    'homePostalAddress'          => 'urn:mace:dir:attribute-def:homePostalAddress',
    'mail'                       => 'urn:mace:dir:attribute-def:mail',
    'mobile'                     => 'urn:mace:dir:attribute-def:mobile',
    'preferredLanguage'          => 'urn:mace:dir:attribute-def:preferredLanguage',
    'eduPersonPrincipalName'     => 'urn:mace:dir:attribute-def:eduPersonPrincipalName',
    'eduPersonAffiliation'       => 'urn:mace:dir:attribute-def:eduPersonAffiliation',
    'eduPersonScopedAffiliation' => 'urn:mace:dir:attribute-def:eduPersonScopedAffiliation',
    'eduPersonEntitlement'       => 'urn:mace:dir:attribute-def:eduPersonEntitlement',
    'eduPersonOrgDN'             => 'urn:mace:dir:attribute-def:eduPersonOrgDN',
    'eduPersonOrgUnitDN'         => 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN',
);

属性値をマッピングする

ここまでは、属性の名前を変換していましたが、次は実際に渡される属性の値を変換します。
詳しくは、ここを読みます。
属性名のマッピング同様に config.php などのファイルを編集します。

記法

属性 "A" が 属性値 "attr_1" または "attr_2" を含む時、属性 "B" に属性値 "attr_12" を加える場合、

'authproc' => array(
    50 => array(
        'class' => 'core:AttributeValueMap',
        'sourceattribute' => 'A',
        'targetattribute' => 'B',
        'values' => array(
            'attr_12' => array(
                'attr_1',
                'attr_2',
            ),
        ),
    ),

この場合、渡される属性のうち、属性 "A" は消去され、属性 "B" はもともと持っていた属性値に加えて "attr_12" が追加されます。
属性 "A" を残したい場合は %keep、属性 "B" がもともと持っていた属性値を新しく "attr_12" に変換したい場合には %replace を使います。

'authproc' => array(
    50 => array(
        'class' => 'core:AttributeValueMap',
        'sourceattribute' => 'A',
        'targetattribute' => 'B',
        '%replace',                  // 追加
        '%keep',                     // 追加
        'values' => array(
            'attr_12' => array(
                'attr_1',
                'attr_2',
            ),
        ),
    ),

values の配列内で複数の属性値について定義することができます。
属性名をマッピングする場合のように別ファイルで定義することができるのかどうかは不明です……。

まだまだ手探りなのでまた追記します。

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

dockerでcomposer updateしようとしたらメモリ不足。。ターミナルからphp.iniでmemoryの設定変更

php.iniの場所を確認

# php --ini

vimで設定変更

# vi /usr/local/etc/php/php.ini

追記

memory_limit = 2047M

確認

# php -r 'phpinfo();' | grep memory_limit
# php -r "echo ini_get('memory_limit').PHP_EOL;"

どちらでもお好きな方を。

以上。

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

【PHP】基礎 FizzBuzz

はじめに

phpの基礎を学ぶ過程でFizzBuzzのプログラムを組みました。

ソース

1~100までの連続する整数を生成し、

3の倍数: Fizz
5の倍数: Buzz
3と5の倍数: FizzBuzz

と出力します。

出力優先度は FizzBuzz > Fizz, Buzz
となっております。

php
<?php

  for ($i = 1; $i < 101; $i++) {
    if ($i % 3 === 0 && $i % 5 === 0) {
      echo $i.":FizzBuzz";
      echo "<br/>";
    } else if ($i % 3 === 0) {
      echo $i.":Fizz";
      echo "<br/>";
    } else if ($i % 5 === 0) {
      echo $i.":Buzz";
      echo "<br/>";
    } else {
      echo $i;
      echo "<br/>";
    }
  }

?>

出力結果

1
2
3:Fizz
4
5:Buzz
6:Fizz
7
8
9:Fizz
10:Buzz
11
12:Fizz
13
14
15:FizzBuzz
16
17
18:Fizz
.
.
.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

忘れやすいがよく使う PHPでログを取る方法

ログの設定 ini_set('log_errors', 'on'); ログを取るか ini_set('error_log', 'php.log'); ログの出力ファイルを指定する ログを吐かせる error_log('SUCCESS');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

三項演算子の世界へようこそ

はじめに

三項演算子、使ってますか?
先日とうとう後輩の @miriwo 氏がめでたく三項演算子に目覚めたということで、これはめでたいということで筆を執りました。

PHP 「?:」 の正体を知ってすっきりした話

まあ、タイトルだけ見るとエルビス演算子に目覚めたのかなって気になってましたけど。
それはそれで大変便利なのです。

三項演算子とは

例を見てみましょう。

image.png
転載元: https://chiicomi.com/smp/r_news_detail?id=19e92c3c-0b86-11e6-8bd0-a0369f7b8e6e

こちらは我らがジェフユナイテッド市原千葉のマスコット、ジェフィとユニティです。
(千葉なのに)秋田犬の双子のわんこなんですが、キャラクターデザイン上の取り計らいで、大きさと背番号以外は区別がつかないようになっています。
ある程度のレベルのジェフサポになると 「キリッとしてるほうがジェフィ、やんちゃな方がユニティ」 とか 「耳がシャキッとしてるほうがジェフィ、丸っこい耳がユニティ」 とか言い出したりするんですが、他サポからは 「ジェフサポは(いろいろな意味で)あたまがおかしい」 と思われているので聞き入れてもらえません。

なので、犬の背番号が2だったらジェフィ、2じゃなかったユニティを name に代入するコードをJavaScriptで書いてみました。

const name = (dog.uniform_number === 2) ? 'Jefy' : 'Unity';

? の左に判定条件、その右に判定が true のときの値、 : を挟んでその右が false のときの値です。
これをたまにいる三項演算子否定派が書くとこんな感じになります。

const name = '';

if(dog.uniform_number === 2){
    name = 'Jefy';
} else {
    name = 'Unity';
}

長い……

要はこういうのをif文ではなく式で簡便に表現できるというのが三項演算子です。
実は本名は「条件演算子」といいます。

https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%BC%94%E7%AE%97%E5%AD%90

三項演算子は結構どの言語にも実装されています。

入れ子の三項演算子

あんまりやってはいけないのですが、三項演算子はネスト(入れ子)して書くことができます。

例を見てみましょう。

image.png

実はジェフのマスコットはジェフィとユニティのオス犬の双子だけではなく、どこからともなく現れた みなちゃん という白い雑種のメス犬もいます。写真の一番右の子ですね。
ちなみにジェフィとユニティは毎年シーズンシートを買って入っている一般サポーターらしいのですが、みなちゃんはれっきとしたジェフユナイテッド株式会社の社員です。

そんな設定の話は置いといて、みなちゃんの背番号はサポーターナンバーと呼ばれる12番です。つまりこんな感じになります。

const name = (dog.uniform_number === 2) ? 'Jefy' : (dog.uniform_number === 9) ? 'Unity' : 'Mina-chang';

どうですか? 見づらくてハゲそうですよね。
見やすくするテクニックとして、記号の前で改行するというのがあります。
外人が書くコードでよく見かけますよね。

const name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang';

ただ、この入れ子の三項演算子、PHPだけなぜか動作が違います。あいつはなぜか 右から評価 するので、こんな感じに書かないと思ったとおりになりません。

$name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : ( (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang');

不毛感がすごい……

おわりに

三項演算子の特殊な書き方として、エルビス演算子とかnull合体演算子の話は次回書きます。
それではお元気で! Enjoy, Summer!!

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

(ジェフユナイテッドで覚える)三項演算子の世界へようこそ

はじめに

三項演算子、使ってますか?
先日とうとう後輩の @miriwo 氏がめでたく三項演算子に目覚めたということで、これはめでたいということで筆を執りました。

PHP 「?:」 の正体を知ってすっきりした話

まあ、タイトルだけ見るとエルビス演算子に目覚めたのかなって気になってましたけど。
それはそれで大変便利なのです。

三項演算子とは

例を見てみましょう。

image.png
転載元: https://chiicomi.com/smp/r_news_detail?id=19e92c3c-0b86-11e6-8bd0-a0369f7b8e6e

こちらは我らがジェフユナイテッド市原千葉のマスコット、ジェフィとユニティです。
(千葉なのに)秋田犬の双子のわんこなんですが、キャラクターデザイン上の取り計らいで、大きさと背番号以外は区別がつかないようになっています。
ある程度のレベルのジェフサポになると 「キリッとしてるほうがジェフィ、やんちゃな方がユニティ」 とか 「耳がシャキッとしてるほうがジェフィ、丸っこい耳がユニティ」 とか言い出したりするんですが、他サポからは 「ジェフサポは(いろいろな意味で)あたまがおかしい」 と思われているので聞き入れてもらえません。

なので、犬の背番号が2だったらジェフィ、2じゃなかったユニティを name に代入するコードをJavaScriptで書いてみました。

const name = (dog.uniform_number === 2) ? 'Jefy' : 'Unity';

? の左に判定条件、その右に判定が true のときの値、 : を挟んでその右が false のときの値です。
これをたまにいる三項演算子否定派が書くとこんな感じになります。

const name = '';

if(dog.uniform_number === 2){
    name = 'Jefy';
} else {
    name = 'Unity';
}

長い……

要はこういうのをif文ではなく式で簡便に表現できるというのが三項演算子です。
実は本名は「条件演算子」といいます。

https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%BC%94%E7%AE%97%E5%AD%90

三項演算子は結構どの言語にも実装されています。

入れ子の三項演算子

あんまりやってはいけないのですが、三項演算子はネスト(入れ子)して書くことができます。

例を見てみましょう。

image.png

実はジェフのマスコットはジェフィとユニティのオス犬の双子だけではなく、どこからともなく現れた みなちゃん という白い雑種のメス犬もいます。写真の一番右の子ですね。
ちなみにジェフィとユニティは毎年シーズンシートを買って入っている一般サポーターらしいのですが、みなちゃんはれっきとしたジェフユナイテッド株式会社の社員です。

そんな設定の話は置いといて、みなちゃんの背番号はサポーターナンバーと呼ばれる12番です。つまりこんな感じになります。

const name = (dog.uniform_number === 2) ? 'Jefy' : (dog.uniform_number === 9) ? 'Unity' : 'Mina-chang';

どうですか? 見づらくてハゲそうですよね。
見やすくするテクニックとして、記号の前で改行するというのがあります。
外人が書くコードでよく見かけますよね。

const name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang';

ただ、この入れ子の三項演算子、PHPだけなぜか動作が違います。あいつはなぜか 右から評価 するので、カッコを使って、こんな感じに書かないと思ったとおりになりません。

$name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : ( (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang');

不毛感がすごい……

※追記
@rana_kualu さんからコメントでいただきましたが、カッコを使わない三項演算子のネストはPHP7.4からは非推奨、PHP8からはエラーになるとのことです。
参考: 【PHP8.0】PHPの三項演算子が他言語の実装に一歩近付く

おわりに

三項演算子の特殊な書き方として、エルビス演算子とかnull合体演算子の話は次回書きます。
それではお元気で! Enjoy, Summer!!

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

GuzzleHttpでのレスポンスをモック化してテストする

まえがき

GuzzleHttpを利用してHTTPリクエストをしている処理のユニットテストを書きたい。
GuzzleHttpにはレスポンスをモック化する機能が用意されているようなので、こちらを利用してみることにします。

実装例

HogeTest.php
<?php

use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Client;

class HogeTest extends \Codeception\Test\Unit
{
    /**
     * @group guzzletest
     */
    public function testGuzzleTest()
    {
        /* Arrange */
        $response = new Response(200, [], 'Response Body');
        $mock = new MockHandler([$response]);
        $handler = HandlerStack::create($mock);
        $client = new Client(['handler' => $handler]);

        $url = 'http://localhost:8080/';
        $params = [
            'token' => 'hogehoge'
        ];

        /* Act */
        $res = $client->post($url, ['form_params' => $params]);

        /* Assert */
        $this->assertEquals('Response Body', $res->getBody());
    }
}
実行結果
$ vendor/bin/codecept run unit --group guzzletest -c common/
Codeception PHP Testing Framework v3.0.2
Powered by PHPUnit 8.2.4 by Sebastian Bergmann and contributors.
Running with seed: 

[Groups] guzzletest 

Common\tests.unit Tests (1) -----------------------------------------------------------
✔ HogeTest: Guzzle test (0.04s)
---------------------------------------------------------------------------------------


Time: 1.96 seconds, Memory: 32.00 MB

OK (1 test, 1 assertion)
  • 上記例では、テストコードで直接クライアントを作成している
  • GuzzleHttpを利用しているServiceクラスやcomponentのテストを行う場合、setClient()などを実装していれば、MockHandlerで生成したClientをテストコードからセットしてレスポンスをモック化する

つまりGuzzleはとても便利。。

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

PHP array_key_exists関数

目的

  • 便利そうな関数だったので自分用に使い方をざっとまとめてみる

前提情報

機能

  • 指定した文字列が指定した配列に格納されていることを判定する関数である。

  • 下記にarray_key_exists関数の例を記載する。

    //配列に文字列が含まれているかを確認する処理、含まれていればtrueが返される。
    array_key_exists(文字列, 配列);
    

具体例

  • 配列$arrayに「str_1」というインデックスが存在していることを判定する処理を記載する。
  • 格納されている場合trueを、格納されていない場合falseを返す。

    array_key_exists('str_1', $array);
    
  • 下記の様な処理が実行された場合「str_1の要素が存在します」と出力される。

    $array = [
        'str_1' => 1,
    ];
    
    if (array_key_exists('str_1', $array)) {
        echo 'str_1 の要素が存在します';
    }
    

参考文献

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

【Wordpress】標準テーマの Powered by Wordpress を書きかえる

Wordpressの標準テーマはシンプルで使いやすいですが、企業のウェブサイトで利用する場合など、フッターの「Powered by Wordpress」を削除したい場合が多々あります。

CSSでパワードバイを非表示にする

これまでは、アップデート時の影響を考慮してテンプレートを直接編集することは避け、追加CSSに以下を記述して非表示にしていました。

.powered-by-wordpress {
    display: none;
}

プラグインでパワードバイを書きかえる

上記の方法だと、非表示にするだけ自由に編集はできません。
また、HTML上には Powerd by Wordpressの文字が残ったままになります。

そこで、パワードバイを書きかえるプラグインを作成しました。
Elvez Edit Powered By(プラグインのダウンロードページ)

screenshot-1.png

設定画面でテキストとURLを入力すると、パワードバイを書きかえてくれます。
何も設定しない場合は非表示になるので、パワードバイの表記が不要な場合にも対応できます。

プラグインの機能はシンプルですが、書きかえるテキストを保存したり、javascriptに値を渡したりと、いくつか実装で工夫をしています。こういう実装もできるというのがあれば、ぜひ教えてください。

他にも社内で利用しているソースコードの一部を、無料プラグインとして公開しています。
https://elvez.co.jp/wordpress/
使ってみて役に立つと思ったら、ぜひ寄付など応援をお願いします。

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

【Laravel】メール送信機能

はじめに

問い合わせフォームで問い合わせがあった場合に、内容をメールで通知するシステムを組んだ際の備忘録。

LaravelのMailableクラスを利用し送信します。

ファイル構成

app
 |- Http
 |  |- SendController.php
 |
 |- Mail
   |- SendMail.php

resources
 |- views
   |- contact
     |- mail.blade.php

.env

コード

Controller

sendメソッドの引数に、Mailableクラスを継承したクラスを渡す。

SendController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;

class SendController extends Controller
{

    public function submit(Request $req){

      //メールを送信
      Mail::to("送信先アドレス")
      ->send(new SendMail($req['message']));

      return view('sample');
    }
}

Mailableクラス

コンストラクタで表示するメッセージリストを受け取る。
buildメソッドで、あらかじめ準備したbladeを利用して本文を作成する。
HTMLメールの場合viewメソッド、通常テキストの場合textメソッドを使う。

SendMail.php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class SendMail extends Mailable
{
    use Queueable, SerializesModels;

    protected $message;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($message)
    {
        //コンストラクタで表示するメッセージを受け取る
        $this->message = $message;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->text('contact.mail')
                    ->subject('件名')
                    ->with(['message' => $this->message]);
    }
}

View

変数を展開して本文を作成する。

mail.blade.php
問い合わせが来ました。

---------------------
名前:{{$message->name}}
アドレス:{{$message->address}}
内容:{{$message->body}}
---------------------

.env

.envにメールサーバの情報を記載する。

.env
MAIL_MAILER=smtp
MAIL_HOST=***
MAIL_PORT=***
MAIL_USERNAME=***
MAIL_PASSWORD=***
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=***
MAIL_FROM_NAME="${APP_NAME}"

参考
Laravelでメール送信する

最後に

利用するサーバの情報が間違っていてはまりました。
処理を何度も見直したことで理解が深まったのでOK!!!

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

nginxの最低限これだけやっとけば安心だろう設定

セキュリティ面や運用面など考え、これだけ設定しておけば一先ず大丈夫だろうと思うものを
書籍や沢山の記事を参考にさせて頂き、まとめました。

※もしこれも設定したほうがいい、ここ間違ってるよなど
アドバイスがあれば是非コメントください
:bow:

アウトライン

  • nginx設定まとめ
  • 各設定の簡易解説
  • 今後の課題

nginx設定まとめ

内容については後述で解説しますが、configの全体としてはこんな感じです。

nginx.conf
user  nginx;
worker_processes  auto;
worker_rlimit_nofile 10000;

error_log  /var/log/nginx/error.log info;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
    multi_accept on;
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer"'
                      '"$http_user_agent" "$http_x_forwarded_for" $request_uri $request_time';

    access_log  /var/log/nginx/access.log  main;
    include /etc/nginx/conf.d/*.conf;

    sendfile  on;
    tcp_nopush  on;
    keepalive_timeout  300;
    server_tokens off;
    server_names_hash_bucket_size 128;
    ssl_protocols TLSv1.2 TLSv1.3;
    add_header "X-XSS-Protection" "1; mode=block";
    add_header X-Frame-Options SAMEORIGIN;
    gzip  on;
    gzip_proxied any;
    gzip_comp_level 1;
    gzip_disable "msie6";
    gzip_min_length 1000;
    gzip_types
      text/plain
      text/xml
      text/css
      application/javascript
      application/json
      text/csv
      application/csv;
}

server {
    listen       80;
    server_name  ~.com;
    charset UTF-8;

    access_log  /var/log/nginx/access.log main;
    error_log   /var/log/nginx/error.log info;

    root   /var/www/html/;
    index  index.php;

    fastcgi_read_timeout 300;
    set_real_ip_from  192.168.1.1/24;
    real_ip_header    X-Forwarded-For;

    client_max_body_size 100M;
    client_body_buffer_size 50M;

    proxy_request_buffering on;
#    proxy_buffer_size ~k;
#    proxy_buffers ~ ~k;
#    proxy_busy_buffers_size ~k;

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt { access_log off; log_not_found off; }
    location ~ .*\.(jpg|JPG|gif|GIF|png|PNG|swf|SWF|css|CSS|js|JS|inc|INC|ico|ICO)$ {
        access_log off;
    }
    location ~ /\. { deny all; }
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index  index.php;
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  HTTPS on;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ){
        return 444;
    }
    if (!-d $request_filename) {
            rewrite     ^/(.+)/$ /$1 permanent;
    }
}

server {
        listen 80 default_server;
        server_name _;
        return 404;
}

※環境
・CentOS7
・nginx-1.16.1
・ロードバランサー(ELB)使用

各設定の簡易解説

・worker_processes

worker_processes  auto;

nginxのworkerプロセス数の設定ができ、autoにすればCPUのコア数をみて自動で設定してくれる。

・worker_rlimit_nofile

worker_rlimit_nofile 10000;

workerプロセスが最大に開けるファイル数の制限。この辺を設定しておけば「too many open files」問題を回避できる。
OSで扱える最大ファイル数÷CPUコア数=最大設定値

・worker_connections

worker_connections  1024;

1つのワーカープロセスが同時に処理できる最大接続数。
worker_rlimit_nofile(ファイルディスクリプタ)値以上を設定することは出来ない。

・multi_accept

multi_accept  on;

同時リクエスト受付on
無効にするとNginxはリクエストを1つずつ処理する

・multi_accept

use epoll;

コネクションの処理方式。
selecet,poll,epollの選択肢のうち、epollが扱うファイルが無制限で高速な処理が可能。

・log_format

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer"'
                      '"$http_user_agent" "$http_x_forwarded_for" $request_uri $request_time';

アクセスログの書式を設定。ログに欲しい情報があればここに追加する。
経験上、$http_x_forwarded_for, $request_uri, $request_timeはログに出力されていたら便利。

・sendfile

sendfile on;

onにするとカーネルのsendfileを使用し、効率良くファイルの内容をクライアントに送信できる。

・tcp_nopush

tcp_nopush on;

onにするとレスポンスヘッダとファイルをまとめて送るようになり、より少ないパケット数で効率よく転送できる。

・keepalive_timeout

keepalive_timeout  300;

HTTP通信をタイムアウトせずに待つ秒数。サービスやインフラ環境と相談。

・server_tokens

server_tokens off;

バージョン情報隠蔽。

・server_names_hash_bucket_size

server_names_hash_bucket_size  64;

仮想ホスト名が長くても大丈夫なように設定。
それでもincrease server_name...のようなエラーがでる場合は、さらに多く設定する。

・ssl_protocols

ssl_protocols TLSv1.2 TLSv1.3;

ダウングレード攻撃やPOODLE対策でSSLのバージョン指定設定。(AWSのELBを使用してる場合は、そこでされるので不要)

・X-XSS-Protection

add_header "X-XSS-Protection" "1; mode=block";

各ブラウザのクロスサイトスクリプティング(XSS)のフィルタ機能を有効化。

・X-Frame-Options

add_header X-Frame-Options SAMEORIGIN;

外部サイトでiframeでの表示を禁止する

・gzip

gzip on;

コンテンツをgzip圧縮して転送する。

・gzip_proxied

gzip_proxied any;

anyにすることで、リクエスト別で圧縮するか判断せず、全てのリクエストを対象とする。

・gzip_comp_level

gzip_comp_level 1;

圧縮レベルを設定。0~9の間で設定可能(0は非圧縮)。

・gzip_disable

gzip_disable "msie6";

gzip圧縮に対応していない場合もあるので、gzip圧縮をしないブラウザを設定。

・gzip_min_length

gzip_min_length 1000;

容量の軽いファイルだと圧縮のほうが時間がかかったりするので、gzip圧縮される最小の容量サイズを設定。

・gzip_types

gzip_types
      text/plain
      text/xml
      text/css
      application/javascript
      application/json
      text/csv
      application/csv;

圧縮対象のファイルタイプを設定。

・fastcgi_read_timeout

fastcgi_read_timeout 300;

FastCGIプロセスへデータを送信するための待ち時間。nginx.confだけでなく、他のタイムアウト設定とも合わせたほうが良い。

・set_real_ip_from

set_real_ip_from  192.168.1.1/24;

nginxの前にELBやプロキシなどを挟んでいる場合、それらのIPを信頼できるものとして設定。

・real_ip_header

real_ip_header  X-Forwarded-For;

ELB等のIPではなく、接続元(クライアント)のIPを $_SERVER ['REMOTE_ADDR']で取得できるようにする。

・client_max_body_size

client_max_body_size 100M;

ファイルアップロードの際の最大サイズ(デフォルト:1m)

・client_body_buffer_size

client_body_buffer_size 50M;

クライアントから受け取ったリクエストボディをどのくらいメモリにバッファリングするかの設定。
無しにするか、エラーが出ない&メモリを食わない程度の値に設定。

・proxy_request_buffering

proxy_request_buffering on;

onにすると、リクエストをプロキシされたサーバーに送信する前に、リクエスト本体全体がクライアントから読み取られる。

・location

location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~ .*\.(jpg|JPG|gif|GIF|png|PNG|swf|SWF|css|CSS|js|JS|inc|INC|ico|ICO)$ { access_log off; }

ファビコンや静的ファイルなどの不要なログは出力しないように設定。

location ~ /\. { deny all; }

「.(ピリオド)」から始まるファイルへのアクセスは禁止するように設定。

location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

指定されたpathファイルを実行するように設定。

・その他

if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ){
       return 444;
}

HTTPメソッドの制限(GET|HEAD|POST|OPTIONSのみ許容している)。

if (!-d $request_filename) {
       rewrite     ^/(.+)/$ /$1 permanent;
}

トレイリングスラッシュを取り除いてリダイレクトする設定。

server {
        listen 80 default_server;
        server_name _;
        return 404;
}

想定外のホストでアクセスがあった場合の処理を設定。
これがないと、設定ファイルの先頭に記述してあるホストに振り分けられるので注意。

今後の課題

今回あまり深く触れなかった、proxy_bufferやworker_connectionsなどの設定値に関しては、
まだ理解が浅いため、適切といえる値は設定することができなかった。
もし、この辺の知見を上げるのに最適な参考資料など、ご存じの方がいれば教えていただけると幸いです:bow_tone2:

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