20201120のPHPに関する記事は15件です。

Laravel の FormRequest を全体的に、実態に近く自動テストする

はじめに

Laravel の FormRequest のテストの話は rules() のテストだけなのが多いので、rules() 以外も含む FormRequest 全体を、より実態に近くテストする話をする

テスト

以降は次を use する前提

use App\Http\Requests\HogeRequest;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

全体なので FormRequest の passedValidation() や failedValidation() の実装の影響を受ける
以降はこれらがデフォルトの実装の前提。上書くときはその処理に従う。

成功することをテストする

FormRequest がサービスコンテナによる解決によって効果を得ることをテストする

public function testPass()
{
    $attributes = ['key' => 'value'];
    // FormRequest が依存する Request に入力値をセット
    $this->app->make(Request::class)->replace($attributes);
    // Controller で DI するのと同じ効果を得る
    $request = $this->app->make(HogeRequest::class);
    // risky になるので assert
    $this->assertInstanceOf(HogeRequest::class, $request);
}

失敗することをテストする

public function testFail()
{
    $attributes = ['key' => null];
    // FormRequest が依存する Request に入力値をセット
    $this->app->make(Request::class)->replace($attributes);
    // Controller で DI すると例外が投げられるのと同じ効果を得る
    $this->expectException(ValidationException::class);
    $this->app->make(HogeRequest::class);
}

おわりに

サービスコンテナによる解決によって、rules() 以外も含む FormRequest 全体を、より実態に近くテストできる

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

Read DoubleのComposerでLaravelインストールで躓いたところ

Read DoubleのComposerでLaravelインストールで躓いたところ

プログラミング学習歴1年未満の初心者がLaravelをインストールするまでの道のり。
laravel公式(日本語ver)と言われるRead Doubleだけではわからなかったため途中、調べたところを追加。

使用環境(動作環境)

  • windows10Home(COREi7)
  • xampp7.3.18

Composerシステム全体のvendor/binディレクトリを$PATHへ登録してくださいって何?

$PATHへ登録する=PATHを通す=環境変数(環境パス)に登録する
環境パスのことすらわからなかった(多分これまでも環境構築で設定してきたはず)

image.png

PATHを通すとは

特定のプログラムを「プログラム名だけで実行できるようにする」こと。
もっと言うと、プログラム名だけで実行できるようにするために、PATH という環境変数(設定の一種)に「このプログラムも名前だけで実行できるようにしてください」という値を追加すること。
「PATHを通す」の意味をできるだけわかりやすく説明する試み

  • もっとわかりやすく言うと、拡張子が「.exe」のファイルの実行を簡単にするための手段みたいだね。

  • 環境パスはどこで設定するの?
    コントロールパネル → システムとセキュリティ → システム → システムの詳細設定

(ちなみにコントロールパネルはwindowsシステムツールの中にあるよ!)

システム詳細設定を押した以下の画面の「環境変数」
image.png

環境変数を押すと以下の画面の「システム環境変数」の「Path」をクリック → 「編集」 → 「新規」で「%USERPROFILE%\AppData\Roaming\Composer\vendor\bin」を追加
(ここの%USERPROFIEL%は「C:\Users\ユーザーネーム」みたいな感じ)
image.png

これでComposerシステム全体のvendor/binディレクトリを$PATHへ登録が完了。

次に躓いたのはpublicディレクトリの設定

image.png

ドキュメントルートがpublicディレクトリになるように設定してくださいということなんだけどそもそもドキュメントルートって何?状態。

ドキュメントルート(DocumentRoot)とはWeb上に公開することのできるディレクトリのルートのことです。
設定方法としては,見せたいディレクトリのパスを指定をします。すると,そのディレクトリ配下のファイルへすべてアクセスができるようになります。
たとえば,DocumentRoot直下にfoo.htmlというファイルを配置をします。するとhttp://servername/foo.html
というURLで対象のファイルにアクセスをすることができます。
第8回 知っておきたいapacheの基礎知識 その4

つまりたくさんディレクトリ(フォルダ)があるんだけど、ドキュメントルートとして設定したディレクトリの中身が公開されますよということ。

この設定方法にはいろいろあるんだけど私は次の方法でドキュメントルートをLaravelプロジェクトの中のpublicに変更しました。
(ちなみに今回紹介する方法ではLaravelプロジェクトごとに(イメージとしてはアプリを作るごと)ドキュメントルートの設定を変えることになります。どうやらlocalhost名?をプロジェクトごとに設定したりもできる(いちいちプロジェクトごとにドキュメントルートを変更しなくても良い)方法があるみたいなのですが私にはまだわかりません;;)

Laravelプロジェクトのpublicをドキュメントルートに設定する方法~apache下で使用

今回はxampp内のhtdocs内にLaravelプロジェクトを生成しています。(推奨されていないようですが)

  • ファイル操作します
    「XAMPP」 → 「apache」 → 「conf」の中の「httpd.conf」を設定変更
    このときhttpd.confファイルはコピーしてhttpd_backup.confなどに名前変更してバックアップを保存しておいてください。

  • httpd.confファイルを開いたら、「DocumentRoot "C:/xampp/htdocs"」という行を探す

変更前:DocumentRoot "C:/xampp/htdocs"
変更後:C:/xampp/htdocs/Laravelプロジェクト作成時の名前/public

  • 次に「ブロック」を探す (2か所あるのですが2か所とも変更で良いのかどなたか教えていただけると助かります)

変更前:

\<Directory "C:/xampp/htdocs">

         ...
\</Directory>

変更後:

\<Directory "C:/xampp/htdocs/Laravelプロジェクト作成時の名前/public">

         ...
\</Directory>

(xamppのドキュメントルートは「htdocs」フォルダ)
(ちなみにxamppの中にはapacheが入っているよ)

  • この2点をLaravelのプロジェクトが変わるごとに変えるという方法です。 (少し手間はかかるし同時にプロジェクトを進行しにくいのであまり良い方法ではないかもしれません。)

その次に躓いたのはディレクトリパーミッションの設定

どうやらフォルダへのアクセス権限を変更せよということらしい。

image.png

聞いたところ、Macだとエラーが出て設定が必要になることがあるのだけれど、windowsだとエラーが出ず、設定が必要ないかもしれないとのこと。

以上、躓きまくりましたがなんとかLaravelをインストールできた。
そしたらなんと、Laravel 8.11.2をインストールしていたので再インストールを検討中...

おまけ

virtual hostの設定もしたのでそれについては別記事にまとめたいと思っています。

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

【PHP】単数系の英語を複数形に直す

PHPの初学者です。

作ったコードをQiitaに書く様にしていきたいと思います。

・末尾がs, sh, ch, o, x である場合英単語の末尾にesを付ける
・末尾がf, fe のいずれかである英単語の末尾の f, fe を除き、末尾に ves を付ける
・末尾の1文字がyで、末尾から2文字目がa, i, u, e, oのいずれでもない英単語の末尾の y を除き、末尾に ies を付ける
・上4つに当てはまらないものにはsを付ける

function SingularToPlural($in)
{
    //不規則変換用の辞書
    $dictionary = [
        'child'      => 'children',
        'crux'       => 'cruces',
        'foot'       => 'feet',
        'knife'      => 'knives',
        'leaf'       => 'leaves',
        'louse'      => 'lice',
        'man'        => 'men',
        'medium'     => 'media',
        'mouse'      => 'mice',
        'oasis'      => 'oases',
        'person'     => 'people',
        'phenomenon' => 'phenomena',
        'seaman'     => 'seamen',
        'snowman'    => 'snowmen',
        'tooth'      => 'teeth',
        'woman'      => 'women',
    ];

    //先に不規則変換をチェックしてから変換を行う
    if (array_key_exists($in, $dictionary)) {
        return $dictionary[$in];
    } else {
        $Singular = $in;
        $Singular = preg_replace("/(s|sh|ch|o|x)$/", "$1es", $Singular);
        $Singular = preg_replace("/(f|fe)$/", "ves", $Singular);
        $Singular = preg_replace("/(a|i|u|e|o)y$/", "$1ys", $Singular);
        $Singular = preg_replace("/y$/", "ies", $Singular);

        if (!preg_match("/s$/", $Singular)) {
            $Singular = $Singular . "s";
        }
        return $Singular;
    }
}

//単語毎に改行してある入力を想定、それをループ処理で取得する
while ($line = fgets(STDIN)) {
    $array[] = trim($line);
}
foreach ($array as $row) {
}
foreach ($array as $value) {
    echo SingulartoPlural($value) . "\n";
}
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ffmpegで動画を連結する備忘録

PHPから実行した際の備忘録。
連結する全動画の画角、コーデックが同じであること。

file_list.txt
file '/var/***/1.mp4'
file '/var/***/2.mp4'
file '/var/***/3.mp4'
file '/var/***/4.mp4'
file '/var/***/5.mp4'
# 動作確認済み
$cmd = "ffmpeg -f concat -safe 0 -i {file_list_path} -c copy {output_local_path}.mp4";
system($cmd);

# こっちは未テスト
$cmd = "ffmpeg -f concat -safe 0 -i {file_list_path} -c:v copy c:a copy {output_local_path}.mp4";
system($cmd);
  • -f concat
    • 連結指示
  • -safe 0
    • file_list.txt 内でファイルパスを絶対パスで指定する際に必要とのこと
  • {file_list_path}
    • 連結するファイルのパスを書いたテキストファイルパス
  • -c copy
    • オリジナルのコーデックを再エンコードしない(無劣化)
  • -c:v copy
    • ビデオコーデックを再エンコードしない(無劣化)
  • -c:a copy
    • オーディオコーデックを再エンコードしない(無劣化)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ffmpegで動画を連結する

PHPから実行した際の備忘録。
連結する全動画の画角、コーデックが同じであること。

file_list.txt
file '/var/***/1.mp4'
file '/var/***/2.mp4'
file '/var/***/3.mp4'
file '/var/***/4.mp4'
file '/var/***/5.mp4'
# 動作確認済み
$cmd = "ffmpeg -f concat -safe 0 -i {file_list_path} -c copy {output_local_path}.mp4";
system($cmd);

# こっちは未テスト
$cmd = "ffmpeg -f concat -safe 0 -i {file_list_path} -c:v copy c:a copy {output_local_path}.mp4";
system($cmd);
  • -f concat
    • 連結指示
  • -safe 0
    • file_list.txt 内でファイルパスを絶対パスで指定する際に必要とのこと
  • {file_list_path}
    • 連結するファイルのパスを書いたテキストファイルパス
  • -c copy
    • オリジナルのコーデックを再エンコードしない(無劣化)
  • -c:v copy
    • ビデオコーデックを再エンコードしない(無劣化)
  • -c:a copy
    • オーディオコーデックを再エンコードしない(無劣化)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 6 画像のアップロード

環境

・Laravel 6.18.43
・PHP 7.3.11
・ブラウザ:クローム

画面構成

フォーム

ファイルと商品名を入力します。

スクリーンショット 2020-11-20 14.07.40.png

確認画面

確認画面を表示します。
input->confirmの際に一度Storage/app/public内のtempディレクトリに仮保存し、
confirm->completeの際にStorage/app/public内のproductImageディレクトリに移動します。

スクリーンショット 2020-11-20 14.17.53.png

完了画面

完了画面です。

スクリーンショット 2020-11-20 14.20.30.png

ソース

まずはルーティングを作成。

web.php
Route::get('/image_input', 'ImageController@getImageInput');
Route::post('/image_confirm', 'ImageController@postImageConfirm');
Route::post('/image_complete', 'ImageController@postImageComplete');

続いてフォームの作成。
formタグ内でenctype="multipart/form-dataを記述する必要があるようです。

imege_input.blade.php
<form action="image_confirm" method="post" enctype="multipart/form-data" id="form">
  @csrf
  <p>ファイル</p>
  <input type="file" name="imagefile" value=""/><br /><br />

  <p>商品名</p><br />
  <input type="text" name="product_name" size="50" value="{{ old('name') }}"/><br /><br />

  <input type="submit" name="confirm" id="button" value="確認" />
</form>

確認画面

image_comfirm.blade.php
<form action="image_complete" method="post">
  @csrf
  <table border="1">
    <tr>
      <td>画像</td>
      <td><img src="{{ $data['read_temp_path'] }}" width="200" height="130"></td>
    </tr>
    <tr>
      <td>商品名</td>
      <td>{{ $data['product_name'] }}</td>
    </tr>
  </table>
  <input type="submit" name="action" value="送信" />
</form>

完了画面

image_complete.blade.php
<p>商品のアップロードが完了しました</p>
<img src="{{ $product->path }}" width="200" height="130">

また、DBは次のmigrationファイルを基にして作り、Modelも作成しておきます。

xxxx_xx_xx_xxxxxx_create_products_table.php
<?php

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

class CreateProductsTable extends Migration
{
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('path');
            $table->string('product_name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('products');
    }
}

コントローラー

フォームから画像と名前を受け取って、画像には一意のファイル名としてtempディレクトリに保存します。
また、sessionに保存して、confirm画面から読み込めるようにします。
useの記述漏れに注意。

imageController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class ImageController extends Controller
{
    public function getImageInput(){
        return view('image_input');
    }

    public function postImageConfirm(Request $request){

        $post_data = $request->except('imagefile');
        $imagefile = $request->file('imagefile');

        $temp_path = $imagefile->store('public/temp');
        $read_temp_path = str_replace('public/', 'storage/', $temp_path); //追加

        $product_name = $post_data['product_name'];

        $data = array(
            'temp_path' => $temp_path,
            'read_temp_path' => $read_temp_path, //追加
            'product_name' => $product_name,
        );
        $request->session()->put('data', $data);

        return view('image_confirm', compact('data') );
    }

    public function postImageComplete(Request $request) {//getをpostに変更

        $data = $request->session()->get('data');
        $temp_path = $data['temp_path'];
        $read_temp_path = $data['read_temp_path'];

        $filename = str_replace('public/temp/', '', $temp_path);
        //ファイル名は$temp_pathから"public/temp/"を除いたもの
        $storage_path = 'public/productimage/'.$filename;
        //画像を保存するパスは"public/productimage/xxx.jpeg"

        $request->session()->forget('data');

        Storage::move($temp_path, $storage_path);
        //Storageファサードのmoveメソッドで、第一引数->第二引数へファイルを移動

        $read_path = str_replace('public/', 'storage/', $storage_path);
        //商品一覧画面から画像を読み込むときのパスはstorage/productimage/xxx.jpeg"
        $product_name = $data['product_name'];

        $product = new Product;
        $product->path = $read_path;
        $product->product_name = $product_name;

        $product->save();

        return view('image_complete', compact('product'));
    }
}

シンボリックリンク

Laravel で作ったアプリケーションが公開されるとき、公開されるのは一番上の階層にある public ディレクトリのみです。public ディレクトリにファイルや処理のすべてが集約されています。なので、保存した画像も public ディレクトリ内に存在しないとアクセスすることができません。そこでシンボリックリンクを利用して storage ディレクトリにアクセスできるようにします。
Laravelの場合、画像を読み込むディレクトリはpublic/storageディレクトリです。
なのでpublic/storageから、storage/app/public内を読み込めるよう(シンボリックリンクを張る)にします。
保存:storage/app/public
読込:public/storage

シンボリックリンクは Artisan コマンドで作成できます。

php artisan storage:link

これで、storage/app/public内を覗けるようになりました。

参考サイト

Laravelで画像ファイルを保存したい
PHP Laravel 6 画像のアップロード

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

laravel/ui リダイレクト先変更方法

特に解説はありません。

app/Providers/RouteServiceProvider.phpを開き

HOMEプロパティに格納している値を変更

RouteServiceProvider.php
.....
class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'App\Http\Controllers';

    /**
     * The path to the "home" route for your application.
     *
     * @var string
     */
    public const HOME = 'HOME';#<=任意のリダイレクト先に変更

    /**

.....


homeからwelcame画面にしたい場合

RouteServiceProvider.php
public const HOME = '/';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel/ui ログイン後のリダイレクト先変更方法

特に解説はありません。

app/Providers/RouteServiceProvider.phpを開き

HOMEプロパティに格納している値を変更

RouteServiceProvider.php
.....
class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'App\Http\Controllers';

    /**
     * The path to the "home" route for your application.
     *
     * @var string
     */
    public const HOME = 'HOME';#<=任意のリダイレクト先に変更

    /**

.....


homeからwelcame画面にしたい場合

RouteServiceProvider.php
public const HOME = '/';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Local by flywheel で、admin押したらThis site can’t be reachedが出て管理画面が出ない

現在存在するwordpressのローカル環境構築方法で一番簡単なのは、Local by flywheelだと思うのですが、手持ちのMac(OS 10.13.6)だと、This site can’t be reachedというエラーが出てしまって、管理画面にもサイト画面にもアクセス不可の状態でした。

Local by flywheelはまだ新しいからか、ネット上にある解決方法も少なめ。

検索能力が低めなので、ググってもしても、Local by flywheelのおすすめ記事にしか到達できず、質問コミュニティでヘルプを求めたところ、色々なアドバイスをいただいた。

結果的には、PC再起動で直りました

しかし、そこにたどり着くまでにプログラミング初心者の自分にはとても思いつかないようなファイルなどをチェックしたので、今後サーバー関連で問題が起こった時はどこをチェックすればいいのかの記録を残したいと思います。

環境

・MacOS 13.10.6 High Sierra

やった事① etc/hosts を確認

って思ったので、とりあえずそのままにググる。
「ホスト名とIPアドレスを対応させるためのファイル」らしい。

Macの場合はterminalを開いて、以下コマンドを実行すれば中身が見れる

$cat /private/etc/hosts

その中に以下のようなLocal by Flywheelの表記があればhosts の更新はできているらしい。

# Local by Flywheel - Start ##
::1 testsitecom.local #Local Site
127.0.0.1 testsitecom.local #Local Site
::1 www.testsitecom.local #Local Site
127.0.0.1 www.testsitecom.local #Local Site

やった事② MAMP, XAMMPなどのApacheサーバーと衝突していないか確認

私の場合は、上述したような表記があったので、hostsの更新ができている状態だった。
とすれば考えられるのは、別ツールで作ったApacheと衝突しているかもとのことで、こちらも助言をいただいた通りにチェックしてみた。

①Local を終了

②アクティビティモニタ (Applications -> Utilities) を開いて Process Name のなかにapache か nginx, httpdがあれば x ボタンで止める。(検索ボタンがあるので、それらを検索してなければok)

私の場合は、それもありませんでした。

やった事③ Localの古いバージョンをインストールしてみる

こちらのサイト様のローカル開発環境 Local by Flywheel エラー検証「Missing hosts entry…」を参考に、古いLocalのバージョンをインストールしてみました。

古いバージョンのインストールはこちら

が、This site can’t be reachedが出るのは変わらず..。

*備考 : ちなみに上記サイトの既存サイトをLocal by Flywheelでローカル開発環境へインポート とかの記事もすごく勉強になります。

やった事④ping testsitecom.localの応答を確認

teminal.
$ping testsitecom.local #コマンド実行で以下のような応答があればok.
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.064 ms
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.069 ms
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.093 ms
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.102 ms

わたしの場合は、これはきちんと応答があったので問題なかったらしい。

やった事⑤ logの確認

Localの画面左側の testsite.com を control + click で Reveal in Finder とするとapp, conf, logsなどのフォルダが開ける。
この logs/nginx/error.log もしくはapache/error.logにはエラーの記録が残ってないか確認

わたしの場合は、apache/error.logの中に以下のような記述があった

apache/error.log
logs/apache/error.log↓
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using MacBook.local. Set the 'ServerName' directive globally to suppress this message
[日付など〜] [mpm_event:notice] [pid 91617:tid 16桁の数字] 〜

Local by flywheelのエラーログってこうやって見ればいいのか..

PC本体を再起動する

前述したように、最終的にはこんな簡単な手段で直りました。

サーバー関連は、再起動を一度してみるといいらしいです。
その際こちらのLocalについて書いてある海外サイトのリンクをいただきました。

今回わたしも質問サイトで調べる前に結構ググったのですが、何日かけても答えにたどり着けなかったのにも関わらず、質問サイトの回答者さんは1日もかからずにこのサイトにたどり着き解決まで導いてくださいました。

プログラミングをしていると、こういったエラーは頻繁に起こります。ネットでググって答えにたどり着ける力をもっとつけないとなと思いました。

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

Local by flywheel で、admin押したらThis site can’t be reachedが出る。

現在存在するwordpressのローカル環境構築方法で一番簡単なのは、Local by flywheelだと思うのですが、手持ちのMac(OS 10.13.6)だと、This site can’t be reachedというエラーが出てしまって、管理画面にもサイト画面にもアクセス不可の状態でした。

Local by flywheelはまだ新しいからか、ネット上にある解決方法も少なめ。

検索能力が低めなので、ググってもしても、Local by flywheelのおすすめ記事にしか到達できず、質問コミュニティでヘルプを求めたところ、etc/hostsファイルをチェックしろとの助言をいただいた。

結論: PC再起動で直った

再起動で直りました。しかし、そこにたどり着くまでに色々なステップを踏みました。今後サーバー関連で問題が起こった時はどこをチェックすればいいのかの記録を残したいと思います。

やった事① etc/hosts を確認

って思ったので、とりあえずそのままにググる。
「ホスト名とIPアドレスを対応させるためのファイル」らしい。

Macの場合はterminalを開いて、以下コマンドを実行すれば中身が見れる

$cat /private/etc/hosts

その中に以下のようなLocal by Flywheelの表記があればhosts の更新はできているらしい。

# Local by Flywheel - Start ##
::1 testsitecom.local #Local Site
127.0.0.1 testsitecom.local #Local Site
::1 www.testsitecom.local #Local Site
127.0.0.1 www.testsitecom.local #Local Site

やった事② MAMP, XAMMPなどのApacheサーバーと衝突していないか確認

私の場合は、上述したような表記があったので、hostsの更新ができている状態だった。
とすれば考えられるのは、別ツールで作ったApacheと衝突しているかもとのことで、こちらも助言をいただいた通りにチェックしてみた。

①Local を終了
②アクティビティモニタ (Applications -> Utilities) を開いて Process Name のなかにapache か nginx があれば x ボタンで止める。

しかし私の場合は、それもなかった。。

やった事③ Localの古いバージョンをインストールしてみる

こちらのサイト様のローカル開発環境 Local by Flywheel エラー検証「Missing hosts entry…」を参考に、古いLocalのバージョンをインストールしてみました。

古いバージョンのインストールはこちら

が、This site can’t be reachedが出るのは変わらず..。

やった事④PC本体を再起動する

サーバー関連は、再起動を一度してみるといいらしいです。
その際こちらのLocalについて書いてある海外サイトのリンクをいただきました。

今回わたしも質問サイトで調べる前に結構ググったのですが、何日かけても答えにたどり着けなかったのにも関わらず、質問サイトの回答者さんは1日もかからずにこのサイトにたどり着き解決まで導いてくださいました。

プログラミングをしていると、こういったエラーは頻繁に起こります。ネットでググって答えにたどり着ける力をもっとつけないとなと思いました。

*備考 : ちなみに上記サイトの既存サイトをLocal by Flywheelでローカル開発環境へインポート とかの記事もすごく勉強になります。

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

Laravel8 CRUD処理を使った投稿アプリを作成する その10 URLをリンク化する

目的

  • 投稿内容の文字列中にURLがあったときにリンク化する方法をまとめる

実施環境

  • 筆者の実施環境を記載する。
  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする
Laravel バージョン 8.6.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • 作成するアプリ名は「laravel8_crud」とする。
  • 作成するMySQLのデータベース名は「laravel8_crud_DB」とする。
  • 下記に今回の作業のあとのソースコードのリモートリポジトリのリンクを記載する。
  • 「http」か「https」から始まるURLをリンク化する。URLと通常文字列の区切りは、全半角スペースと改行とする。

概要

  1. ビューファイルの編集
  2. 確認

詳細

  1. ビューファイルの編集

    1. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを開く。

      $ vi resources/views/contents/output.blade.php
      
    2. 下記のように編集する。

      laravel8_crud/resources/views/contents/output.blade.php
      @extends('layouts.app')
      
      @section('content')
      
      <h1>output</h1>
      
      @foreach ($items as $item)
      <hr>
      @if (isset($item['file_path']))
      <img src="{{asset('storage/' . $item['file_path'])}}" alt="{{asset('storage/' . $item['file_path'])}}">
      @endif
      {{-- 下記を修正する --}}
      <p>{!! nl2br(preg_replace('/(https?:\/\/[^\s]*)/', '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $item['content'])) !!}</p>
      <a href="{{route('detail', ['content_id' => $item['id']])}}">詳細</a>
      <a href="{{route('edit', ['content_id' => $item['id']])}}">編集</a>
      @endforeach
      
      @endsection
      
    3. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを開く。

      $ vi resources/views/contents/detail.blade.php
      
    4. 下記のように編集する。

      laravel8_crud/resources/views/contents/detail.blade.php
      @extends('layouts.app')
      
      @section('content')
      
      <h1>detail</h1>
      
      @if (isset($item['file_path']))
      <img src="{{asset('storage/' . $item['file_path'])}}" alt="{{asset('storage/' . $item['file_path'])}}">
      @endif
      <p>投稿ID: {{$item['id']}}</p>
      {{-- 下記を修正する --}}
      <p>投稿内容: {!! nl2br(preg_replace('/(https?:\/\/[^\s]*)/', '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $item['content'])) !!}</p>
      <p>投稿時間: {{$item['created_at']}}</p>
      <a href="{{route('edit', ['content_id' => $item['id']])}}">編集</a>
      <form action="{{route('delete')}}" method="post">
          @csrf
          <input type="hidden" name="id" value="{{$item['id']}}">
          <input type="submit" value="削除">
      </form>
      
      @endsection
      
  2. 確認

    1. laravel8_crudディレクトリで下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve
      
    2. ブラウザで下記にアクセスする。

    3. 投稿内容に「https://qiita.com/miriwo」と入力し「送信」をクリックする。

      laravel8_crud-5.png

    4. 下記にアクセスする。

    5. URLがリンク化されており、クリックすると新しいタブで筆者のQiitaの画面が表示されることを確認する。

      laravel8_crud-6.png

    6. URLの投稿の「詳細」をクリックする。

      laravel8_crud-7.png

    7. URLがリンク化されており、クリックすると新しいタブで筆者のQiitaの画面が表示されることを確認する。

      laravel8_crud-8.png

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

【PHP】セッションとクッキーを初心者向けに説明

超詳しい参考サイト
Web Design Leaves

クッキーとは?セッションとは?から始まる話は難しいので上記のサイトを参考にしてください。
最高にわかりやすいです。

ここでは使用例とセッションとクッキーの関連性に絞ります。

保存先

セッションの保存先はWebサーバーです。
クッキーの保存先はクライアントのブラウザです。

セッションはブラウザとサーバーが接続された時に session_start()によって生成されます。

セッション クッキー
保存先 Webサーバー ブラウザ
変数名 $_SESSION $_COOKIE

クッキー(クライアント)のセッションID

ブラウザ側にあるクッキーにはPHPSESSIDが保存されます。
例えば下のような値です。

セッション名  
PHPSESSID '682dos0637qu0g1l8clhs9sv7a'

WebサーバーのセッションID

Webサーバーは数多く保存しているセッションがどのクライアントに紐付くかをPHPSESSIDで判断しています。

ブラウザ(PHPSESSID) Webサーバー(SESSION)
'682dos0637qu0g1l8clhs9sv7a' <-----> '682dos0637qu0g1l8clhs9sv7a'
'7qu0g1l8cl682dos063hs9sv7a'
'lhs9sv7a682dos0637qu0g1l8c'

セッション変数

セッションはWebサーバーに保存できる変数です。
Webサーバーに変数が保存できると何が便利なのでしょうか?

セッション変数の利点
ページをまたいで値のやりとりができる。
ブラウザを経由しないため改ざんされづらい。

具体例
send.phpにアクセスすると即時receive.phpに飛んで'51'を画面出力します。

send.php
<?php
session_start();
$_SESSION['id'] = '51';
//リダイレクト
header('location: ./receive.php');
receive.php
<?php
session_start();
echo $_SESSION['id'];
//string -> 51

どうでしょう?うまくいきましたね!
send.phpで$_SESSION['id']保存して、receive.phpでアクセスできました!

session_start();はセッションを使用するときのおまじないです。
各ファイルの先頭に必ず置いてください。

クッキーのセッションIDを確認する

ここで、クッキーに保存されているPHPSESSIDを確認してみましょう。
Chrome:
デベロッパーツール -> Applicationタブ -> Cookies -> URL -> Value

Safari:
Safariの準備 -> 環境設定 -> 詳細 -> メニューバーに“開発”メニューを表示
開発タブ -> Webインスペクタを表示 -> ストレージタブ -> Cookie -> 値

PHPSESSIDが確認できたでしょうか?
PHPSESSIDはそれが破棄されるまで同じ値が使用されます。

セッションの破棄

上で破棄されるまでと言いました。
それでは破棄してみましょう!(公式

delete_session.php
<?php
session_start();
//セッションの初期化
$_SESSION = [];
//セッションクッキーを削除
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time() - 42000, '/');
}
//セッションを破壊
session_destroy();

//ここまででPHPSESSIDもセッションも消去されています。

//リダイレクト -> send.php -> 新たなPHPSESSIDが生成される
header('location: ./send.php');

send.php, receive.php, delete_session.phpは同じフォルダに入れてください。

デベロッパーツールでPHPSESSIDを観察しながら、
send.php動作 -> delete_session.php動作で。
PHPSESSIDが生成 -> 変更 されているのが見えます!

ぜひデベロッパーツールで確認してみてください。

どうでしたでしょうか?
セッション変数の利用の一助になれば幸いです。

LGTMお願いします!
ストックのついでにお願いします!
モチベーションがあがります!

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

RubyエンジニアがPHPを勉強し始めるときに見る記事

この記事 is 何

RubyエンジニアがPHPを勉強し始めるときに見る記事です。

動機

今までRubyをやってきていて、PHPを使うことになったというケースは少なくないと思っています。
特に実務未経験の方々では、このケースが多いのではないでしょうか?
というのは、実務未経験が学習している言語は、Rubyがファーストチョイスである一方で、PHPを使う企業は少なくないためです。
ちなみに、僕もそのケースでした。
そういう方々が過度に不安を持ったりせず、スムーズに転向できるようにしたいと思い、この記事を書きました。

この記事の主なターゲット

  • 今までRubyを勉強してたが、PHPを使うことになった人
  • というか半年前の自分

この記事で書くつもりのないこと

  • 技術選定時・初学者が学ぶ言語としてRuby or PHPはどっちが良いのかということ
  • 網羅的・辞書的にRubyとPHPを比較した結果
  • RubyとPHPの文法の正確な対応関係

学習のし始め

RubyからPHPへの転向コスト

Rubyをちゃんと習得できた方であれば、転向・学習コストにおいて問題はほぼ起きないと思います。
文法を調べながらとりあえず書き始められる、というレベルに達するには半日〜3日程度の勉強で済むと思います。

文法を調べるときに何を使うか

php.net
PHPは公式ドキュメントが神なので、基本的にこのサイトを見ればPHPの文法的な解決してくれるはずです。

Screen Shot 2020-11-15 at 18.55.08.png

ググラビリティも良好で、「array_merge php」などと検索すれば公式ドキュメントが先頭に出てくれます。

本題

動作確認バージョン
$ php -v
PHP 7.3.11 ...

$ ruby -v
ruby 2.6.3p62 ...

基本的なオブジェクトの概念

出力

Ruby PHP
改行なし出力 print echo
型情報付き出力 p var_dump()
配列に対し改行付き出力 puts echo, PHP_EOL

Rubyのprintは、PHPでいうとechoに近いです。
Rubyのpは、PHPでいうとvar_dumpに近いです。

Rubyのputsは?と言われると、対応したものはありません。
echoPHP_EOLを組み合わせる必要があります。

objects/print.rb
print 'sample!'
# sample!

sample_array = [1, 'sample']
print sample_array
# [1, "sample"]

p sample_array
# [1, "sample"]

puts sample_array
# 1
# sample
objects/class.php
<?php
echo 'sample!';
// sample!

$sample_array = [1, 'sample'];
echo $sample_array;
// Array

var_dump($sample_array);
// array(2) {
//   [0]=>
//   int(1)
//   [1]=>
//   string(6) "sample"
// }

foreach ($sample_array as $value) {
    echo $value . PHP_EOL;
}
// 1
// sample

プリミティブ型のクラス

Ruby PHP
プリミティブ型は クラス ただの型
文字列への変換 to_s "{$変数}"
数値への変換 to_i intval()

RubyではString, Integer, Arrayといったプリミティブ型も全てがオブジェクトです。
一方、PHPにはプリミティブ型においてはクラスの概念はありません。

PHPではstringへの変換は、ダブルクウォートで囲った上で変数展開することによって実現します。
また、intに変換するにはintval()を使います。

objects/class.rb
int3 = 3

string3 = int3.to_s
p string3
# "3"

p string3.class
# String

re_int3 = string3.to_i
p re_int3
# 3

p re_int3.class
# Integer
objects/method.php
<?php
$int3 = 3;

$string3 = "{$int3}";
var_dump($string3);
// string(1) "3"

get_class($string3);
// Warning: get_class() expects parameter 1 to be object, string given in ...

$re_int3 = intval($string3);
var_dump($re_int3);
// int(3)

get_class($re_int3);
// Warning: get_class() expects parameter 1 to be object, int given in ...

値・オブジェクトに対する基本的な操作・変換

Ruby PHP
基本的な操作・変換は オブジェクトメソッド 標準関数の引数に渡す
文字数を調べる length / size mb_strlen()
配列を合計する sum array_sum()

Rubyでは値・オブジェクトに対する基本的な操作・変換はオブジェクトメソッドで実現します。
参考: class Object(Ruby 2.7.0 リファレンスマニュアル)

一方、PHPは標準関数が充実しており、その標準関数の引数として対象を渡すのが一般的です。

objects/method.rb
address = '千代田区千代田1-1'
puts address.length
# 10

fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13]
puts fibonacci_numbers.sum
# 33
objects/method.php
<?php
$address = '千代田区千代田1-1';
echo mb_strlen($address);
// 10

$fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13];
echo array_sum($fibonacci_numbers);
// 33

配列・ハッシュと、連想配列

Ruby PHP
配列の概念 配列 連想配列
連想配列の概念 ハッシュ 連想配列
配列の要素の取得 配列[インデックス] 連想配列[インデックス]
連想配列の要素の取得 配列[シンボル] 連想配列[キー]

Rubyでは配列と連想配列(ハッシュ)を使い分けます。
一方で、PHPはちょっと特殊で、連想配列しかありません。PHPにおける(連想配列でない)配列は、キーが数値である連想配列です。
また、Rubyでは連想配列のキーにはシンボルを使うことが多いと思いますが、PHPでは文字列をキーとすることが多いです。

objects/array.rb
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
p days_of_week
# ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

puts days_of_week[0]
# Monday

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon"}

puts fruits[:red]
# apple
objects/array.php
<?php
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var_dump($days_of_week);
// array(7) {
//   [0]=>
//   string(6) "Monday"
//   [1]=>
//   string(7) "Tuesday"
//   [2]=>
//   string(9) "Wednesday"
//   [3]=>
//   string(8) "Thursday"
//   [4]=>
//   string(6) "Friday"
//   [5]=>
//   string(8) "Saturday"
//   [6]=>
//   string(6) "Sunday"
// }

echo $days_of_week[0];
// Monday

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
var_dump($fruits);
// array(3) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//}

echo $fruits['red'];
// apple

配列への操作

Ruby PHP
配列への追加 配列 << 新要素 配列[] = 新要素
連想配列への追加 配列[シンボル] = 新要素 配列[キー] = 新要素
繰り返し処理 each foreach
繰り返し処理(返り値使用) map array_map()
繰り返し処理内での行いたい処理 ブロック コールバック関数

配列への追加は、Rubyは<<によってできる一方、PHPでは配列[] = 新要素という書き方を使います。
連想配列への追加は、書き方的にほぼ違いは無いかと思います。

手続き的な繰り返し処理は、Rubyならeach, PHPならforeachです。
返り値を使用したいときはRubyならmap, PHPならarray_map()を使います。

繰り返し処理中に行いたい処理を表現するのは、Rubyならブロック、PHPならコールバック関数を使います。
array_map()はよく使う割にはちょっと複雑な見た目をしていますね。

objects/array_function.rb
sequence_numbers = [1, 2, 3, 4, 5]
sequence_numbers << 6
p sequence_numbers
# [1, 2, 3, 4, 5, 6]

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
fruits[:orange] = 'pumpkin'
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon", :orange=>"pumpkin"}

sequence_numbers.each do |number|
  multiple_of_3 = number * 3
  puts multiple_of_3
end
# 3
# 6
# 9
# 12
# 15
# 18

multiples = sequence_numbers.map do |number|
  number * 3
end
p multiples
# [3, 6, 9, 12, 15, 18]
objects/array_function.php
<?php
$sequence_numbers = [1, 2, 3, 4, 5];
$sequence_numbers[] = 6;
var_dump($sequence_numbers);
// array(6) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
//   [3]=>
//   int(4)
//   [4]=>
//   int(5)
//   [5]=>
//   int(6)
// }

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
$fruits['orange'] = 'pumpkin';
var_dump($fruits);
// array(4) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//   ["orange"]=>
//   string(7) "pumpkin"
// }

foreach ($sequence_numbers as $number) {
    $multiple_of_3 = $number * 3;
    echo $multiple_of_3 . PHP_EOL;
}
// 3
// 6
// 9
// 12
// 15
// 18

$multiples = array_map(
    function ($number) {
        return $number * 3;
    },
    $sequence_numbers
);
var_dump($multiples);
// array(6) {
//   [0]=>
//   int(3)
//   [1]=>
//   int(6)
//   [2]=>
//   int(9)
//   [3]=>
//   int(12)
//   [4]=>
//   int(15)
//   [5]=>
//   int(18)
// }

クラス関連

クラスは重要なので、重点的に解説します。

インスタンス変数=プロパティ

Ruby PHP
メンバ変数 インスタンス変数 プロパティ
privateなメンバ変数の定義 不要 private $変数;
publicなメンバ変数の定義 @変数に代入し、attr_accessor public $変数;
コンストラクタ def initialize function __construct()
メンバ変数への代入 @変数に代入 $this->変数に代入
メンバ変数の呼び出し . ->

Rubyのインスタンス変数は、PHPのプロパティに該当します。
Rubyはインスタンス変数の宣言は特に必要なく、コンストラクタで@変数に代入してしまえば完了です。
一方、PHPだと宣言が必要で、private $変数;のように定義します。

また、メンバ変数を公開可能にしたいのであれば、Rubyではattr_accessorを使う一方、PHPではpublic $変数によって定義します。

classes/Person.rb
class Person
  attr_accessor :name

  def initialize name, age
    @name = name
    @age = age
  end
end

person = Person.new '一郎', 10
p person
# #<Person:0x00007ff39f158750 @name="一郎", @age=10>

puts person.name
# 一郎

puts person.age
# Traceback (most recent call last):
# classes/Person.rb:16:in `<main>': undefined method `age' for #<Person:0x00007ff39f158750 @name="一郎", @age=10> (NoMethodError)
classes/Person.php
<?PHP

class Person
{
    public $name;
    private $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

$person = new Person('一郎', 10);
var_dump($person);
// object(Person)#1 (2) {
//   ["name"]=>
//   string(6) "一郎"
//   ["age":"Person":private]=>
//   int(10)
// }

echo $person->name;
// 一郎

echo $person->age;
// Fatal error: Uncaught Error: Cannot access private property Person::$age in ...

クラスの定数

Ruby PHP
定数の定義 大文字 const 大文字
クラス外からの定数の呼び出し クラス::定数 クラス::定数
クラス内からの定数の呼び出し 定数 self::定数

PHPだと定数はconstを付けて宣言します。
クラス外からの呼び出しは、RubyでもPHPでも同じ書き方です。
PHPだとクラス外からの呼び出しではself::をつけます。

classes/Sample.rb
class Sample
  TEST_CODE = 'test_code'

  def print_code
    puts "コードは#{TEST_CODE}です。";
  end
end

puts Sample::TEST_CODE
# test_code

sample = Sample.new
sample.print_code
# コードはtest_codeです。
classes/Sample.php
<?PHP

class Sample
{
    const TEST_CODE = 'test_code';

    public function printCode()
    {
        $code = self::TEST_CODE;
        echo "コードは「{$code}」です。";
    }
}

echo Sample::TEST_CODE;
// test_code

$sample = new Sample();
$sample->printCode();
// コードは「test_code」です。

インスタンスメソッド

Ruby PHP
publicメソッドの定義方法 ただのdef public function()
privateメソッドの定義方法 privateの下でdef private function()

Rubyではクラス内で何も行わずにdefをするとpublicメソッドとなり、privateの下で定義されるとprivateメソッドになります。
一方、PHPではメソッドの定義時に必ずpublic/privateを定義します。
(本当はもっと色々種類がありますが割愛)

classes/Product.rb
class Product
    def initialize price, cost
        @price = price
        @cost = cost
    end

    def print_judgment
        if calc_profit > 0
            puts 'この商品は利益が出ます!どんどん売りましょう!';
        else
            puts 'この商品は利益が出ません...売るべきではないかもしれません...'
        end
    end

    private

    def calc_profit
        @price - @cost
    end
end

product = Product.new 120, 100
product.print_judgment
# この商品は利益が出ます!どんどん売りましょう!

product =  Product.new 90, 100
product.print_judgment
# この商品は利益が出ません...売るべきではないかもしれません...

product.calc_profit
# Traceback (most recent call last):
# classes/Product.rb:30:in `<main>': private method `calc_profit' called for #<Product:0x00007fc9f19dc408 @price=120, @cost=100> (NoMethodError)
classes/Product.php
<?php

class Product
{
    private $price;
    private $cost;

    public function __construct($price, $cost)
    {
        $this->price = $price;
        $this->cost = $cost;
    }

    public function printJudgment()
    {
        if ($this->calcProfit() > 0) {
            echo 'この商品は利益が出ます!どんどん売りましょう!';
        } else {
            echo 'この商品は利益が出ません...売るべきではないかもしれません...';
        }
    }

    private function calcProfit()
    {
        return $this->price - $this->cost;
    }
}

$product = new Product(120, 100);

$product->printJudgment();
// この商品は利益が出ます!どんどん売りましょう!

$product = new Product(90, 100);
$product->printJudgment();
// この商品は利益が出ません...売るべきではないかもしれません...

$product->calcProfit();
// Fatal error: Uncaught Error: Call to private method Product::calcProfit() from context '' in ...

クラスメソッドとstaticメソッド

Ruby PHP
静的メソッド クラスメソッド staticメソッド
インスタンスメソッド インスタンスメソッド staticでないメソッド
静的メソッドの定義方法 self static function()
静的メソッドの呼び出し クラス::メソッド クラス::メソッド

Rubyにおけるクラスメソッドは、PHPではstaticメソッドに該当します。
逆に、インスタンスメソッドはstaicでないメソッドということになります。

Rubyでクラスメソッドを定義するときは、class << selfの中で定義することが多いと思います。
一方で、PHPではメソッドの定義ごとにstaticを付与することで定義します。

また、public/privateはどんなメソッドでも付けるので、staticメソッドにもpublic/privateを付けて定義します。
静的メソッドの呼び出しの書き方は、RubyとPHPとでほぼ差は無いかと思います。

classes/VertebrateAnimal.rb
class VertebrateAnimal
  class << self
    def is_vertebrate_animal group
      get_vertebrate_animals.include? group
    end

    private

    def get_vertebrate_animals
      %w[fishes amphibians reptiles birds mammals]
    end
  end
end

is_vertebrate_animal1 = VertebrateAnimal::is_vertebrate_animal 'fishes'
puts is_vertebrate_animal1
# true

is_vertebrate_animal2 = VertebrateAnimal::is_vertebrate_animal 'insects'
puts is_vertebrate_animal2
# false
classes/VertebrateAnimal.php
<?PHP

class VertebrateAnimal
{
    public static function isVertebrateAnimal($group)
    {
        $vertebrate_animals = self::getVertebrateAnimals();
        return in_array($group, $vertebrate_animals, true);
    }

    private static function getVertebrateAnimals()
    {
        return ['fishes', 'amphibians', 'reptiles', 'birds', 'mammals'];
    }
}

$is_vertebrate_animal1 = VertebrateAnimal::isVertebrateAnimal('fishes');
var_dump($is_vertebrate_animal1);
// bool(true)

$is_vertebrate_animal2 = VertebrateAnimal::isVertebrateAnimal('insects');
var_dump($is_vertebrate_animal2);
// bool(false)

ゲッターとセッター

Ruby PHP
ゲッター attr_reader getXX()
セッター attr_writer setXX()

Rubyにおけるattr_readerおよびattr_writerに該当するような文法は、PHPにはありません。
よってgetXX, setXXという命名で自作することになります。

classes/Capsule.rb
class Capsule
  attr_reader :element
  attr_writer :element

  def initialize element
    @element = element
  end
end

capsule = Capsule.new '有効成分'
puts capsule.element
# 有効成分

p capsule
# #<Capsule:0x00007f8d909ac660 @element="有効成分">

capsule.element = '毒'
p capsule
# #<Capsule:0x00007f8d909ac660 @element="毒">
classes/Capsule.php
<?PHP

class Capsule
{
    private $element;

    public function __construct($element)
    {
        $this->element = $element;
    }

    public function getElement()
    {
        return $this->element;
    }

    public function setElement($element)
    {
        $this->element = $element;
    }
}

$capsule = new Capsule('有効成分');
echo $capsule->getElement();
// 有効成分

var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(12) "有効成分"
// }

$capsule->setElement('毒');
var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(3) "毒"
// }

その他ハマりポイント

ライブラリの対応関係

Ruby PHP
ライブラリ管理 Gem Composer
テスト Rspec PHPUnit
Linter Rubocop PHP-CS-Fixerなど
コメント RDoc PHPDoc
フレームワーク Ruby on Rails Laravel, CakePHPなど

有名どころのライブラリなどの対応関係のみを記載しました。
Rubyと言ったらRails、Railsと言ったらRubyという印象ですが、PHPではフレームワークは複数の選択肢があります。
シェアが高いという意味であればLaravelが近く、MVCの思想に忠実という意味ではCakePHPに近いかなという印象を持っています。

真偽判定・nil/null判定

Ruby PHP
== そこそこ使う 絶対に使わない
Null nil null
オブジェクトのNull確認 ! empty()

PHPの==はあまりにもヤバいことで有名だと思いますので、比較のときには必ず===を使うようにします。
Rubyでのnilは、PHPだとnullに該当します。

Rubyでは全てがオブジェクトであり、真偽値判定・nil判定がうまく設計されていて、オブジェクトのみで直感的に真偽値判定・nil判定をできることも多いです。
一方、PHPの場合は直感的に真偽値判定・null判定を行うのは危険なので、型を意識して判定した方が安全です。
オブジェクトがnullや空かどうかを確認するなら、empty()を使うのが有効です。

others/User.rb
class User
  class << self
    def find_by_id id
      # IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします
      if id == 1
        User.new '最初のユーザ'
      else
        nil
      end
    end
  end

  def initialize name
    @name = name
  end
end

def print_user user
  # ユーザオブジェクトが存在するかを確認する
  if !user
    puts 'ユーザはいません'
  else
    p user
  end
end

user2 = User::find_by_id 2
print_user user2
# ユーザはいません

p user2
# nil

user1 = User::find_by_id 1
print_user user1
# #<User:0x00007fe70f173f88 @name="最初のユーザ">
others/User.php
<?php

class User
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    static function findById($id)
    {
        /** IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします */
        if ($id === 1) {
            return new static('最初のユーザ');
        } else {
            return null;
        }
    }
}

function printUser($user)
{
    // ユーザオブジェクトが存在するかを確認する
    if (empty($user)) {
        echo 'ユーザはいません';
    } else {
        var_dump($user);
    }
}

$user2 = User::findById(2);
printUser($user2);
// ユーザはいません

var_dump($user2);
// NULL

$user1 = User::findById(1);
printUser($user1);
// object(User)#1 (1) {
//   ["name":"User":private]=>
//   string(18) "最初のユーザ"
// }

if文の返り値

Ruby PHP
if文の返り値 最後の値 なし
分岐によって値を生成する場合 ifの返り値に入れる 一度値を初期化し、再代入する

PHPはif文の返り値が返ってきません。
そのため、分岐によって値を生成させるケースでは一度値を初期化するなどしてから再代入する、みたいな方法を取る必要があります。

どうしても再代入がいやだ、イミュータブルにしたい、という場合は三項演算子という方法もあります。
ただし、三項演算子は多くの場合は処理が分かりにくくなるだけなので、非推奨です。

others/day.rb
def print_ymd is_beginning_of_month
  today = Time.now

  datetime = if is_beginning_of_month
               Time.new today.year, today.month, 1 # is_beginning_of_month = falseでは現在日を返す
             else
               today # 月初を返す
             end

  puts datetime.strftime "%Y年%m月%d日です"
end

print_ymd false
# 2020年12月07日です

print_ymd true
# 2020年12月01日です
others/day.php
<?php

function printYmd($is_beginning_of_month)
{
    $today = new DateTimeImmutable();

    $datetime = null;
    if ($is_beginning_of_month) {
        // 月初を返す
        $datetime = new DateTimeImmutable($today->format('Y-m-01'));
    } else {
        // $is_beginning_of_month = falseでは現在日を返す
        $datetime = $today;
    }

    echo $datetime->format('Y年m月d日です');
}

printYmd(false);
// 2020年12月07日です

printYmd(true);
// 2020年12月01日です

/**
 * 別の書き方。コード量は少ないが分かりにくい
 */
function printYmd2($is_beginning_of_month)
{
    $today = new DateTimeImmutable();
    $datetime = $is_beginning_of_month ? new DateTimeImmutable($today->format('Y-m-01')) : $today;

    echo $datetime->format('Y年m月d日です');
}

名前付き引数がない(PHP7以前)

Ruby PHP7以前 PHP8以後
名前付き引数 引数名: なし 引数名:

PHP7以前では名前付き引数がありません。
よって、オプション引数と言いながらも順番を意識して代入する必要があります(辛い)。
このため、依存性注入が凄まじくやりづらいです。

PHP8には名前付き引数が導入されています。
【PHP8.0】PHPに名前付き引数が実装される

others/argument.rb
def calc_goku_scouter original_power, kaio_ken: 1, is_suppressed: false, is_super_saiyan: false
  power = original_power * kaio_ken

  if power > 5000 && is_suppressed
    power = 5000
  end

  if is_super_saiyan
    power += 100000000
  end

  power
end

puts calc_goku_scouter 85000
# 85000

puts calc_goku_scouter 85000, kaio_ken: 2
# 170000

puts calc_goku_scouter 85000, is_suppressed: true
# 5000

puts calc_goku_scouter 85000, kaio_ken: 20, is_super_saiyan: true
# 101700000
others/argument.php
<?php

function calcGokuScouter($original_power, $kaio_ken = 1, $is_suppressed = false, $is_super_saiyan = false)
{
    $power = $original_power * $kaio_ken;

    if ($power > 5000 && $is_suppressed) {
        $power = 5000;
    }

    if ($is_super_saiyan) {
        $power += 100000000;
    }

    return $power;
}

echo calcGokuScouter(85000);
// 85000

echo calcGokuScouter(85000, 2);
// 170000

echo calcGokuScouter(85000, 1, true);
// 5000

echo calcGokuScouter(85000, 20, false, true);
// 101700000

interfaceとabstract

Ruby PHP
インターフェース なし interface
抽象クラス なし abstract

Rubyには無い概念として、PHPにはinterface, abstractというものがあります。

特にinterfaceは重要なのですが、中々概念の理解が難しいかもしれません。
とても勉強になった記事があるので、貼っておきます。
脱PHP初心者!インターフェイスを理解しよう

abstractはGoFのデザインパターンでいうTemplate Methodパターンを実装するのに必要です。
参考になった記事を貼っておきます。
PHPによるデザインパターン入門 - TemplateMethod〜処理を穴埋めする - Do You PHP はてブロ

結び

いかがでしたか?
もし誤記・間違い・より良い書き方などあれば、ご指摘いただけると嬉しいです。

一応GitHubにもコードをまとめておきました。
https://github.com/kumackey/php-ruby

他参考になりそうなもの

PHPer向けのRuby入門
この記事の逆パターンです。演習問題が付いてる優しさ。

PHPerがRubyを勉強するのに役に立つTips
同じくこの記事の逆パターン。

元PHPエンジニアがPHPとRubyを比較してみた
PHPとRubyの文法の違いなど。

PHPからRubyへ
Rubyの公式が出している、PHPとの違いというドキュメント

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

【PHP入門】RubyエンジニアがPHPを勉強し始めるときに見る記事

この記事 is 何

RubyエンジニアがPHPを勉強し始めるときに見る、PHPの入門記事です。

動機

今までRubyをやってきていて、PHPを使うことになったというケースは少なくないと思っています。
特に実務未経験の方々では、このケースが多いのではないでしょうか?
というのは、実務未経験が学習している言語は、Rubyがファーストチョイスである一方で、PHPを使う企業は少なくないためです。
ちなみに、僕もそのケースでした。
そういう方々が過度に不安を持ったりせず、スムーズに転向できるようにしたいと思い、この記事を書きました。

この記事の主なターゲット

  • 今までRubyを勉強してたが、PHPを使うことになった人
  • というか半年前の自分

この記事で書くつもりのないこと

  • 技術選定時・初学者が学ぶ言語としてRuby or PHPはどっちが良いのかということ
  • 網羅的・辞書的にRubyとPHPを比較した結果
  • RubyとPHPの文法の正確な対応関係

学習のし始め

RubyからPHPへの転向コスト

Rubyをちゃんと習得できた方であれば、転向・学習コストにおいて問題はほぼ起きないと思います。
文法を調べながらとりあえず書き始められる、というレベルに達するには半日〜3日程度の勉強で済むと思います。

文法を調べるときに何を使うか

php.net
PHPは公式ドキュメントが神なので、基本的にこのサイトを見ればPHPの文法的な解決してくれるはずです。

Screen Shot 2020-11-15 at 18.55.08.png

ググラビリティも良好で、「array_merge php」などと検索すれば公式ドキュメントが先頭に出てくれます。

本題

動作確認バージョン
$ php -v
PHP 7.3.11 ...

$ ruby -v
ruby 2.6.3p62 ...

基本的なオブジェクトの概念

出力

Ruby PHP
改行なし出力 print echo
型情報付き出力 p var_dump()
配列に対し改行付き出力 puts echo, PHP_EOL

Rubyのprintは、PHPでいうとechoに近いです。
Rubyのpは、PHPでいうとvar_dumpに近いです。

Rubyのputsは?と言われると、対応したものはありません。
echoPHP_EOLを組み合わせる必要があります。

objects/print.rb
print 'sample!'
# sample!

sample_array = [1, 'sample']
print sample_array
# [1, "sample"]

p sample_array
# [1, "sample"]

puts sample_array
# 1
# sample
objects/class.php
<?php
echo 'sample!';
// sample!

$sample_array = [1, 'sample'];
echo $sample_array;
// Array

var_dump($sample_array);
// array(2) {
//   [0]=>
//   int(1)
//   [1]=>
//   string(6) "sample"
// }

foreach ($sample_array as $value) {
    echo $value . PHP_EOL;
}
// 1
// sample

プリミティブ型のクラス

Ruby PHP
プリミティブ型は クラス ただの型
文字列への変換 to_s "{$変数}"
数値への変換 to_i intval()

RubyではString, Integer, Arrayといったプリミティブ型も全てがオブジェクトです。
一方、PHPにはプリミティブ型においてはクラスの概念はありません。

PHPではstringへの変換は、ダブルクウォートで囲った上で変数展開することによって実現します。
また、intに変換するにはintval()を使います。

objects/class.rb
int3 = 3

string3 = int3.to_s
p string3
# "3"

p string3.class
# String

re_int3 = string3.to_i
p re_int3
# 3

p re_int3.class
# Integer
objects/method.php
<?php
$int3 = 3;

$string3 = "{$int3}";
var_dump($string3);
// string(1) "3"

get_class($string3);
// Warning: get_class() expects parameter 1 to be object, string given in ...

$re_int3 = intval($string3);
var_dump($re_int3);
// int(3)

get_class($re_int3);
// Warning: get_class() expects parameter 1 to be object, int given in ...

値・オブジェクトに対する基本的な操作・変換

Ruby PHP
基本的な操作・変換は オブジェクトメソッド 標準関数の引数に渡す
文字数を調べる length / size mb_strlen()
配列を合計する sum array_sum()

Rubyでは値・オブジェクトに対する基本的な操作・変換はオブジェクトメソッドで実現します。
参考: class Object(Ruby 2.7.0 リファレンスマニュアル)

一方、PHPは標準関数が充実しており、その標準関数の引数として対象を渡すのが一般的です。

objects/method.rb
address = '千代田区千代田1-1'
puts address.length
# 10

fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13]
puts fibonacci_numbers.sum
# 33
objects/method.php
<?php
$address = '千代田区千代田1-1';
echo mb_strlen($address);
// 10

$fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13];
echo array_sum($fibonacci_numbers);
// 33

配列・ハッシュと、連想配列

Ruby PHP
配列の概念 配列 連想配列
連想配列の概念 ハッシュ 連想配列
配列の要素の取得 配列[インデックス] 連想配列[インデックス]
連想配列の要素の取得 配列[シンボル] 連想配列[キー]

Rubyでは配列と連想配列(ハッシュ)を使い分けます。
一方で、PHPはちょっと特殊で、連想配列しかありません。PHPにおける(連想配列でない)配列は、キーが数値である連想配列です。
また、Rubyでは連想配列のキーにはシンボルを使うことが多いと思いますが、PHPでは文字列をキーとすることが多いです。

objects/array.rb
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
p days_of_week
# ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

puts days_of_week[0]
# Monday

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon"}

puts fruits[:red]
# apple
objects/array.php
<?php
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var_dump($days_of_week);
// array(7) {
//   [0]=>
//   string(6) "Monday"
//   [1]=>
//   string(7) "Tuesday"
//   [2]=>
//   string(9) "Wednesday"
//   [3]=>
//   string(8) "Thursday"
//   [4]=>
//   string(6) "Friday"
//   [5]=>
//   string(8) "Saturday"
//   [6]=>
//   string(6) "Sunday"
// }

echo $days_of_week[0];
// Monday

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
var_dump($fruits);
// array(3) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//}

echo $fruits['red'];
// apple

配列への操作

Ruby PHP
配列への追加 配列 << 新要素 配列[] = 新要素
連想配列への追加 配列[シンボル] = 新要素 配列[キー] = 新要素
繰り返し処理 each foreach
繰り返し処理(返り値使用) map array_map()
繰り返し処理内での行いたい処理 ブロック コールバック関数

配列への追加は、Rubyは<<によってできる一方、PHPでは配列[] = 新要素という書き方を使います。
連想配列への追加は、書き方的にほぼ違いは無いかと思います。

手続き的な繰り返し処理は、Rubyならeach, PHPならforeachです。
返り値を使用したいときはRubyならmap, PHPならarray_map()を使います。

繰り返し処理中に行いたい処理を表現するのは、Rubyならブロック、PHPならコールバック関数を使います。
array_map()はよく使う割にはちょっと複雑な見た目をしていますね。

objects/array_function.rb
sequence_numbers = [1, 2, 3, 4, 5]
sequence_numbers << 6
p sequence_numbers
# [1, 2, 3, 4, 5, 6]

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
fruits[:orange] = 'pumpkin'
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon", :orange=>"pumpkin"}

sequence_numbers.each do |number|
  multiple_of_3 = number * 3
  puts multiple_of_3
end
# 3
# 6
# 9
# 12
# 15
# 18

multiples = sequence_numbers.map do |number|
  number * 3
end
p multiples
# [3, 6, 9, 12, 15, 18]
objects/array_function.php
<?php
$sequence_numbers = [1, 2, 3, 4, 5];
$sequence_numbers[] = 6;
var_dump($sequence_numbers);
// array(6) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
//   [3]=>
//   int(4)
//   [4]=>
//   int(5)
//   [5]=>
//   int(6)
// }

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
$fruits['orange'] = 'pumpkin';
var_dump($fruits);
// array(4) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//   ["orange"]=>
//   string(7) "pumpkin"
// }

foreach ($sequence_numbers as $number) {
    $multiple_of_3 = $number * 3;
    echo $multiple_of_3 . PHP_EOL;
}
// 3
// 6
// 9
// 12
// 15
// 18

$multiples = array_map(
    function ($number) {
        return $number * 3;
    },
    $sequence_numbers
);
var_dump($multiples);
// array(6) {
//   [0]=>
//   int(3)
//   [1]=>
//   int(6)
//   [2]=>
//   int(9)
//   [3]=>
//   int(12)
//   [4]=>
//   int(15)
//   [5]=>
//   int(18)
// }

クラス関連

クラスは重要なので、重点的に解説します。

インスタンス変数=プロパティ

Ruby PHP
メンバ変数 インスタンス変数 プロパティ
privateなメンバ変数の定義 不要 private $変数;
publicなメンバ変数の定義 @変数に代入し、attr_accessor public $変数;
コンストラクタ def initialize function __construct()
メンバ変数への代入 @変数に代入 $this->変数に代入
メンバ変数の呼び出し . ->

Rubyのインスタンス変数は、PHPのプロパティに該当します。
Rubyはインスタンス変数の宣言は特に必要なく、コンストラクタで@変数に代入してしまえば完了です。
一方、PHPだと宣言が必要で、private $変数;のように定義します。

また、メンバ変数を公開可能にしたいのであれば、Rubyではattr_accessorを使う一方、PHPではpublic $変数によって定義します。

classes/Person.rb
class Person
  attr_accessor :name

  def initialize name, age
    @name = name
    @age = age
  end
end

person = Person.new '一郎', 10
p person
# #<Person:0x00007ff39f158750 @name="一郎", @age=10>

puts person.name
# 一郎

puts person.age
# Traceback (most recent call last):
# classes/Person.rb:16:in `<main>': undefined method `age' for #<Person:0x00007ff39f158750 @name="一郎", @age=10> (NoMethodError)
classes/Person.php
<?PHP

class Person
{
    public $name;
    private $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

$person = new Person('一郎', 10);
var_dump($person);
// object(Person)#1 (2) {
//   ["name"]=>
//   string(6) "一郎"
//   ["age":"Person":private]=>
//   int(10)
// }

echo $person->name;
// 一郎

echo $person->age;
// Fatal error: Uncaught Error: Cannot access private property Person::$age in ...

クラスの定数

Ruby PHP
定数の定義 大文字 const 大文字
クラス外からの定数の呼び出し クラス::定数 クラス::定数
クラス内からの定数の呼び出し 定数 self::定数

PHPだと定数はconstを付けて宣言します。
クラス外からの呼び出しは、RubyでもPHPでも同じ書き方です。
PHPだとクラス外からの呼び出しではself::をつけます。

classes/Sample.rb
class Sample
  TEST_CODE = 'test_code'

  def print_code
    puts "コードは#{TEST_CODE}です。";
  end
end

puts Sample::TEST_CODE
# test_code

sample = Sample.new
sample.print_code
# コードはtest_codeです。
classes/Sample.php
<?PHP

class Sample
{
    const TEST_CODE = 'test_code';

    public function printCode()
    {
        $code = self::TEST_CODE;
        echo "コードは「{$code}」です。";
    }
}

echo Sample::TEST_CODE;
// test_code

$sample = new Sample();
$sample->printCode();
// コードは「test_code」です。

インスタンスメソッド

Ruby PHP
publicメソッドの定義方法 ただのdef public function()
privateメソッドの定義方法 privateの下でdef private function()

Rubyではクラス内で何も行わずにdefをするとpublicメソッドとなり、privateの下で定義されるとprivateメソッドになります。
一方、PHPではメソッドの定義時に必ずpublic/privateを定義します。
(本当はもっと色々種類がありますが割愛)

classes/Product.rb
class Product
    def initialize price, cost
        @price = price
        @cost = cost
    end

    def print_judgment
        if calc_profit > 0
            puts 'この商品は利益が出ます!どんどん売りましょう!';
        else
            puts 'この商品は利益が出ません...売るべきではないかもしれません...'
        end
    end

    private

    def calc_profit
        @price - @cost
    end
end

product = Product.new 120, 100
product.print_judgment
# この商品は利益が出ます!どんどん売りましょう!

product =  Product.new 90, 100
product.print_judgment
# この商品は利益が出ません...売るべきではないかもしれません...

product.calc_profit
# Traceback (most recent call last):
# classes/Product.rb:30:in `<main>': private method `calc_profit' called for #<Product:0x00007fc9f19dc408 @price=120, @cost=100> (NoMethodError)
classes/Product.php
<?php

class Product
{
    private $price;
    private $cost;

    public function __construct($price, $cost)
    {
        $this->price = $price;
        $this->cost = $cost;
    }

    public function printJudgment()
    {
        if ($this->calcProfit() > 0) {
            echo 'この商品は利益が出ます!どんどん売りましょう!';
        } else {
            echo 'この商品は利益が出ません...売るべきではないかもしれません...';
        }
    }

    private function calcProfit()
    {
        return $this->price - $this->cost;
    }
}

$product = new Product(120, 100);

$product->printJudgment();
// この商品は利益が出ます!どんどん売りましょう!

$product = new Product(90, 100);
$product->printJudgment();
// この商品は利益が出ません...売るべきではないかもしれません...

$product->calcProfit();
// Fatal error: Uncaught Error: Call to private method Product::calcProfit() from context '' in ...

クラスメソッドとstaticメソッド

Ruby PHP
静的メソッド クラスメソッド staticメソッド
インスタンスメソッド インスタンスメソッド staticでないメソッド
静的メソッドの定義方法 self static function()
静的メソッドの呼び出し クラス::メソッド クラス::メソッド

Rubyにおけるクラスメソッドは、PHPではstaticメソッドに該当します。
逆に、インスタンスメソッドはstaicでないメソッドということになります。

Rubyでクラスメソッドを定義するときは、class << selfの中で定義することが多いと思います。
一方で、PHPではメソッドの定義ごとにstaticを付与することで定義します。

また、public/privateはどんなメソッドでも付けるので、staticメソッドにもpublic/privateを付けて定義します。
静的メソッドの呼び出しの書き方は、RubyとPHPとでほぼ差は無いかと思います。

classes/VertebrateAnimal.rb
class VertebrateAnimal
  class << self
    def is_vertebrate_animal group
      get_vertebrate_animals.include? group
    end

    private

    def get_vertebrate_animals
      %w[fishes amphibians reptiles birds mammals]
    end
  end
end

is_vertebrate_animal1 = VertebrateAnimal::is_vertebrate_animal 'fishes'
puts is_vertebrate_animal1
# true

is_vertebrate_animal2 = VertebrateAnimal::is_vertebrate_animal 'insects'
puts is_vertebrate_animal2
# false
classes/VertebrateAnimal.php
<?PHP

class VertebrateAnimal
{
    public static function isVertebrateAnimal($group)
    {
        $vertebrate_animals = self::getVertebrateAnimals();
        return in_array($group, $vertebrate_animals, true);
    }

    private static function getVertebrateAnimals()
    {
        return ['fishes', 'amphibians', 'reptiles', 'birds', 'mammals'];
    }
}

$is_vertebrate_animal1 = VertebrateAnimal::isVertebrateAnimal('fishes');
var_dump($is_vertebrate_animal1);
// bool(true)

$is_vertebrate_animal2 = VertebrateAnimal::isVertebrateAnimal('insects');
var_dump($is_vertebrate_animal2);
// bool(false)

ゲッターとセッター

Ruby PHP
ゲッター attr_reader getXX()
セッター attr_writer setXX()

Rubyにおけるattr_readerおよびattr_writerに該当するような文法は、PHPにはありません。
よってgetXX, setXXという命名で自作することになります。

classes/Capsule.rb
class Capsule
  attr_reader :element
  attr_writer :element

  def initialize element
    @element = element
  end
end

capsule = Capsule.new '有効成分'
puts capsule.element
# 有効成分

p capsule
# #<Capsule:0x00007f8d909ac660 @element="有効成分">

capsule.element = '毒'
p capsule
# #<Capsule:0x00007f8d909ac660 @element="毒">
classes/Capsule.php
<?PHP

class Capsule
{
    private $element;

    public function __construct($element)
    {
        $this->element = $element;
    }

    public function getElement()
    {
        return $this->element;
    }

    public function setElement($element)
    {
        $this->element = $element;
    }
}

$capsule = new Capsule('有効成分');
echo $capsule->getElement();
// 有効成分

var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(12) "有効成分"
// }

$capsule->setElement('毒');
var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(3) "毒"
// }

その他ハマりポイント

ライブラリの対応関係

Ruby PHP
ライブラリ管理 Gem Composer
テスト Rspec PHPUnit
Linter Rubocop PHP-CS-Fixerなど
コメント RDoc PHPDoc
フレームワーク Ruby on Rails Laravel, CakePHPなど

有名どころのライブラリなどの対応関係のみを記載しました。
Rubyと言ったらRails、Railsと言ったらRubyという印象ですが、PHPではフレームワークは複数の選択肢があります。
シェアが高いという意味であればLaravelが近く、MVCの思想に忠実という意味ではCakePHPに近いかなという印象を持っています。

真偽判定・nil/null判定

Ruby PHP
== そこそこ使う 絶対に使わない
Null nil null
オブジェクトのNull確認 ! empty()

PHPの==はあまりにもヤバいことで有名だと思いますので、比較のときには必ず===を使うようにします。
Rubyでのnilは、PHPだとnullに該当します。

Rubyでは全てがオブジェクトであり、真偽値判定・nil判定がうまく設計されていて、オブジェクトのみで直感的に真偽値判定・nil判定をできることも多いです。
一方、PHPの場合は直感的に真偽値判定・null判定を行うのは危険なので、型を意識して判定した方が安全です。
オブジェクトがnullや空かどうかを確認するなら、empty()を使うのが有効です。

others/User.rb
class User
  class << self
    def find_by_id id
      # IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします
      if id == 1
        User.new '最初のユーザ'
      else
        nil
      end
    end
  end

  def initialize name
    @name = name
  end
end

def print_user user
  # ユーザオブジェクトが存在するかを確認する
  if !user
    puts 'ユーザはいません'
  else
    p user
  end
end

user2 = User::find_by_id 2
print_user user2
# ユーザはいません

p user2
# nil

user1 = User::find_by_id 1
print_user user1
# #<User:0x00007fe70f173f88 @name="最初のユーザ">
others/User.php
<?php

class User
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    static function findById($id)
    {
        /** IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします */
        if ($id === 1) {
            return new static('最初のユーザ');
        } else {
            return null;
        }
    }
}

function printUser($user)
{
    // ユーザオブジェクトが存在するかを確認する
    if (empty($user)) {
        echo 'ユーザはいません';
    } else {
        var_dump($user);
    }
}

$user2 = User::findById(2);
printUser($user2);
// ユーザはいません

var_dump($user2);
// NULL

$user1 = User::findById(1);
printUser($user1);
// object(User)#1 (1) {
//   ["name":"User":private]=>
//   string(18) "最初のユーザ"
// }

if文の返り値

Ruby PHP
if文の返り値 最後の値 なし
分岐によって値を生成する場合 ifの返り値に入れる 一度値を初期化し、再代入する

PHPはif文の返り値が返ってきません。
そのため、分岐によって値を生成させるケースでは一度値を初期化するなどしてから再代入する、みたいな方法を取る必要があります。

どうしても再代入がいやだ、イミュータブルにしたい、という場合は三項演算子という方法もあります。
ただし、三項演算子は多くの場合は処理が分かりにくくなるだけなので、非推奨です。

others/day.rb
def print_ymd is_beginning_of_month
  today = Time.now

  datetime = if is_beginning_of_month
               Time.new today.year, today.month, 1 # is_beginning_of_month = falseでは現在日を返す
             else
               today # 月初を返す
             end

  puts datetime.strftime "%Y年%m月%d日です"
end

print_ymd false
# 2020年12月07日です

print_ymd true
# 2020年12月01日です
others/day.php
<?php

function printYmd($is_beginning_of_month)
{
    $today = new DateTimeImmutable();

    $datetime = null;
    if ($is_beginning_of_month) {
        // 月初を返す
        $datetime = new DateTimeImmutable($today->format('Y-m-01'));
    } else {
        // $is_beginning_of_month = falseでは現在日を返す
        $datetime = $today;
    }

    echo $datetime->format('Y年m月d日です');
}

printYmd(false);
// 2020年12月07日です

printYmd(true);
// 2020年12月01日です

/**
 * 別の書き方。コード量は少ないが分かりにくい
 */
function printYmd2($is_beginning_of_month)
{
    $today = new DateTimeImmutable();
    $datetime = $is_beginning_of_month ? new DateTimeImmutable($today->format('Y-m-01')) : $today;

    echo $datetime->format('Y年m月d日です');
}

名前付き引数がない(PHP7以前)

Ruby PHP7以前 PHP8以後
名前付き引数 引数名: なし 引数名:

PHP7以前では名前付き引数がありません。
よって、オプション引数と言いながらも順番を意識して代入する必要があります(辛い)。
このため、依存性注入が凄まじくやりづらいです。

PHP8には名前付き引数が導入されています。
【PHP8.0】PHPに名前付き引数が実装される

others/argument.rb
def calc_goku_scouter original_power, kaio_ken: 1, is_suppressed: false, is_super_saiyan: false
  power = original_power * kaio_ken

  if power > 5000 && is_suppressed
    power = 5000
  end

  if is_super_saiyan
    power += 100000000
  end

  power
end

puts calc_goku_scouter 85000
# 85000

puts calc_goku_scouter 85000, kaio_ken: 2
# 170000

puts calc_goku_scouter 85000, is_suppressed: true
# 5000

puts calc_goku_scouter 85000, kaio_ken: 20, is_super_saiyan: true
# 101700000
others/argument.php
<?php

function calcGokuScouter($original_power, $kaio_ken = 1, $is_suppressed = false, $is_super_saiyan = false)
{
    $power = $original_power * $kaio_ken;

    if ($power > 5000 && $is_suppressed) {
        $power = 5000;
    }

    if ($is_super_saiyan) {
        $power += 100000000;
    }

    return $power;
}

echo calcGokuScouter(85000);
// 85000

echo calcGokuScouter(85000, 2);
// 170000

echo calcGokuScouter(85000, 1, true);
// 5000

echo calcGokuScouter(85000, 20, false, true);
// 101700000

interfaceとabstract

Ruby PHP
インターフェース なし interface
抽象クラス なし abstract

Rubyには無い概念として、PHPにはinterface, abstractというものがあります。

特にinterfaceは重要なのですが、中々概念の理解が難しいかもしれません。
とても勉強になった記事があるので、貼っておきます。
脱PHP初心者!インターフェイスを理解しよう

abstractはGoFのデザインパターンでいうTemplate Methodパターンを実装するのに必要です。
参考になった記事を貼っておきます。
PHPによるデザインパターン入門 - TemplateMethod〜処理を穴埋めする - Do You PHP はてブロ

結び

いかがでしたか?
もし誤記・間違い・より良い書き方などあれば、ご指摘いただけると嬉しいです。

一応GitHubにもコードをまとめておきました。
https://github.com/kumackey/php-ruby

他参考になりそうなもの

PHPer向けのRuby入門
この記事の逆パターンです。演習問題が付いてる優しさ。

PHPerがRubyを勉強するのに役に立つTips
同じくこの記事の逆パターン。

元PHPエンジニアがPHPとRubyを比較してみた
PHPとRubyの文法の違いなど。

PHPからRubyへ
Rubyの公式が出している、PHPとの違いというドキュメント

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

ほぼ未経験Webエンジニアが入社半年でしたことまとめ(2020/11)

言語

・PHP
・HTML
・JavaScript
(・MySQL)

やったこと

・Web開発用の開発環境構築(XAMPP設定など)(なんだかんだ3,4回した気がする)
・変数や配列など基礎的な内容を理解
・Ajax勉強
・オブジェクト指向の勉強
・セキュリティ少し学んだ
・DBについて少し学んだ
・より良いコードが書けるようにリーダブルコードを読んだ
・リーダブルコードを英語で読みたいと思い、原書を購入(まだ読んでない…)
・mapboxのチュートリアル的なページを確認
・HTMLとJavaScriptにmapboxAPIを組み合わせて、マップ情報を活用したHTMLファイル作成(指定場所の面積計算など)
・エンジニア数十人が書いたエッセイ集みたいな本を買って読んだ
・メルカリ創業の本を読んだ(サクセスストーリー感があって非常に面白かった)
・kotlinとswiftをすこし内容みてみた
・UdemyでswiftとJavaScriptのコース(英語)を受講(現在進行中)

感想

・幅広く興味を持っていい経験ができた気がする
・mapboxに触れられたのはよかった
・Udemyの英語コースは、英語の勉強にもなってとてもよい
・ちょっと時間の使い方があまりうまくなかった
・もう少しやりたいことに時間を使えばよかった
・面白いものをもっと探求したかった、開発できていればよかった
・もっと本を読みたい

今後やりたいこと

・kotlinまたswiftに本格的に手を出す
・何か1つスマホアプリをリリースする
・フレームワークに手を出す
・JavaScriptでアニメーションっぽいものを駆使して何か面白いものをつくる
・mapboxを駆使して一風変わったワクワクするものをつくりたい
・mapboxでARのAPIが活用出来たら面白そうなので、ARで何かできないか模索する

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