[Laravel x Python] exec($command, $output)のトラブルシューティング

execコマンドがvagrantのamazon linux2では問題なく動作するのに、ec2にデプロイして実行すると反応しない。
execコマンドだとダメそうなので、SymphonyのProcessでやってみたが、それでもダメ。
NLPは最も肝となる処理なので諦めるわけにはいかん、とトラブルシューティングを丸一日。Webの記事を探しまくるも一向に解決せず。

該当のcontroller

            $input = $request->input;
            $replace_input = str_replace(" ", "\ ", $input);
            $path = app_path() . "/python/main.py";
            // $command = "python3 " . $path . " ".$replace_input;
            // exec($command, $output);
            $process = new Process(["python3", $path, $replace_input]);
            $process->run();
            $output = $process->getOutput();
            dd($output);

結論から言うと、ProcessFailedExceptionでエラー原因を表示させたらわかった。sudu pip3でpandas, sklearn, MeCabなどをインストールすればよかった。

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
// 省略

            $process = new Process(["python3", $path, $replace_input]);
            $process->run();
            if (!$process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

エラーが起きたら、まず、Exceptionで原因を調べるのが早いですね。
laravelでpythonの処理は変則的なので、かなり焦りました。
もっと上達してええええええええええええ

[laravel8.5.19] LaravelでPython3を実行する書き方

Laravelで作成するアプリケーションで、一部の言語に関する処理のところをPython(自然言語処理)で実行したい。
方法としては、1.PHP組み込み関数のexec関数を使用する、2.symfony/processライブラリを使う の2通りがあるようだ。
今回は「exec関数」を使用する。python実行のテストのみなので、以下簡略化して記述している。

### exec関数とは?
exec関数はPHPの組み込み関数で、外部プログラムを実行できる

### Laravelプロジェクトインストール
$ composer create-project –prefer-dist laravel/laravel laravel-python
$ cd laravel-python
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
$ npm install && npm run dev
$ php artisan migrate
$ php artisan serve –host 192.168.33.10 –port 8000

### Routing
web.php
 L index.bladeからpostした際に、ExecControllerのexecutePython() メソッドでpythonを実行する

use App\Http\Controllers\ExecController;
use App\Http\Controllers\TestController;

Route::post('/python', [ExecController::class, 'executePython']);
Route::get('/test', [TestController::class, 'index']);

### View
index.blade.php
 L フォームのactionから、ExecController@executePythonにpostする

	<h1>Test</h1>
	<form action="python" method="post">
		@csrf
		<input type="submit" name="submit">
	</form>

### Controller
ExecController.php
L exec($command, $output);で実行する。
L $commandは、「python3 ${絶対パス}.py」で実行する
L laravelでは、app_path()でappディレクトリのpathを取得するので、app_path()とする
L execの第二引数である$outputは、コマンドで*.pyを実行した際の出力が返ってくる。

class ExecController extends Controller
{
    //
    public function executePython(Request $request){
    	$path = app_path() . "/Python/app.py";
    	$command = "python3 " . $path;
    	exec($command, $output);
    	dd($output);
    	// return view('index', compact('output'));
    }
}

※テストなのでdd($output)で出力を確認します。

laravel-python/app/Python/app.py
※今回はlaravelプロジェクトのapp/Python/配下にapp.pyを作成した。絶対パスなので、どこれも良いっぽいが、appフォルダに作るのが一般的のよう。今回はテストなので単にprint()するだけにした

print("hello")

### Laravelでpythonを実行した実行結果
bladeで「送信」ボタンを押すと、pythonで*.pyを実行する

実行後
L 配列で返ってくる

返ってきた変数(値)は、viewに再度返却できますね。
うおおおおおおおおおおおおおおおおおおおおおおおおお
やべえええええええええええええええええ
やりたいこと(自然言語処理)の9割くらいはイメージできた!!!!!!!!!!!!!

当初、Pythonの処理(nlpなど)の部分は、サブドメインを取得して別サーバを立ててDjangoで実装しようかと思ってたが、Laravelでpythonを実行できるなら、わざわざそんな冗長なことしなくて良いですね。
よっしゃああああああああああああああああああああああ
設計書作るぞーーーーーーーー

[Laravel8.16.0] class内でメンバ変数(共通の変数)を使いたい

Controllerのメソッドの外で変数を書くと、

$var = "変数";
public function test(){

        return $var;
    }


syntax error, unexpected ‘$var’ (T_VARIABLE), expecting function (T_FUNCTION) or const (T_CONST)

アクセス修飾子をつける

private $var = "変数";
    public function test(){

        return $this->var;
    }

public
どこからでもアクセス可能です。アクセス修飾子がない場合は、publicを指定したものと同じになります。

protected
そのクラス自身と継承クラスからアクセス可能です。つまり非公開ですが、継承は可能となります。

private
同じクラスの中でのみアクセス可能です。非公開で継承クラスからもアクセス不可能となります。
private $var = ["1","2","3"];
    public function test(){

        $var = $this->var;
        return view('admin.test', compact('var'));
    }
{{ $var[0] }}

-> 1

思った通りになる。これで良いのかな。。

### 1冊やっておいても損はない本

Vue.jsでLaravelのリレーション(belongsTo, hasMany)の値を表示

## Messageモデル
– usersテーブルに対し、messagesテーブルがbelongsToのリレーション関係

public function user(){
        return $this->belongsTo('App\User');
    }

## controller

$messages =Message::where(function($q) use ($foo, $hoge){
// 省略
})->orderBy('***','ASC')->get();

### 通常時のblade
– $message->user->name で名前を呼び出す

@foreach($messages as $message)
// 省略
{{ $message->user ? $message->user->name : '削除済ユーザ' }}
// 省略
@endforeach

# Vue.js使用時
## controller
– with(‘user’)でユーザテーブルの情報も取得する

return Message::with('user')->where(function($q) use ($foo, $hoge){
// 省略
})->orderBy('***','ASC')->get();

## blade
– vueで表示する際は、m.user.name と書く

<div v-for="m in messages">
  // 省略
  <span v-text="m.user.name"></span>
  // 省略
</div>

belongsToもVueで表示できるのか!? 表示できないなら、Vueは断念しようかと思ってた。

Laravel + Ajaxでbladeの一部の値(ログインユーザ順のデータ)を自動更新させたい

– bladeの一部の値(ログインユーザ順のデータ)を自動更新させたい

## 初期
controller
-> 自分以外のユーザをログイン順に取得している。

$users = User::where('id', '!=', $user_id)->orderBy('last_login','DESC')->get();

– controllerでjson形式で返せるらしい
-> ルーティングでgetとpostに対応してajaxのメソッドに繋げます
## Route

Route::get('/hoge/new', 'HogesController@create');
Route::match(['get', 'post'], '/hoge/getuser/', 'HogesController@getUser');

## Controller

public function getUser(){
        $user_id = 1;
        $users = User::where('id', '!=', $user_id)->orderBy('last_login','DESC')->get(['id','name','hoge']);
        $json = ["userData" => $userData];
        return response()->json($json);
    }

## view
-setTimeoutで5秒ごとにgetメソッドでcontrollerからデータを取得する
-while( table.rows[ 0 ] ) table.deleteRow( 0 ); でtableのrowsを全て削除し、jsonデータを入れたtd, trをappendする

<table class="table" id="userList">
</table>
// 省略

<script>
$(function(){
			getData()
		})

		function getData(){
			$.ajaxSetup({
				headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
			});
			$.get({
				url: '/hoge/getuser',
				method: 'GET',
				dataType: 'json',
			})
			.done(function(data){
				
				var table = document.getElementById('userList');
				while( table.rows[ 0 ] ) table.deleteRow( 0 );
				var rows = "";
				for(var i = 0; i < data.userData.length; i++){
					rows += "<tr>";
					rows += "<td>";
					rows += "hoge";
					rows += "</td>";
					rows += "</tr>";
				}
				$("#userList").append(rows);
			})
			.fail(function(){

			})
			setTimeout("getData()", 5000);
		}
</script>

これ思ってたより複雑だな。jsonで持ってくる場合、hasOneやbelongsToのデータはController側で整形しないとダメか。

ajaxのjsonデータは、getで取得するURLを叩くと、jsonを見ることが可能で、デバッグには良いが、商用環境で->get() とするとセキュリティ上問題があるので、viewで使用するカラムのみ ->get([‘columName’]) で取得するようにする。

laravel whereで複数の一致条件 ~ a =A, b = B もしくは a = B, b = A

Controllerでa =A, b = B もしくは a = B, b = A を探したい時。

複数のwhereの場合は、whereを繋げれば良いのですが、複数のwhereのどちらかの場合は、where whereをorWhereで繋げれば良い。

$messages = Hotels::where(function($q) use ($id, $user_id){
            $q->where('user_id', $id)->where('second_id', $user_id);
        })->orWhere(function($q) use ($id, $user_id){
            $q->where('user_id', $user_id)->where('second_id', $id);
        })->get();

割と簡単に書けます。

Eloquent query内での条件式(When)の書き方

Eloquent query内で条件判定をする場合は、whenで判定する引数を渡す
– inputsのperiodの値がotherの時は、whereBetweenでdateがinputsの指定した範囲で値を取得する
– inputsのperiodの値がother以外の時は、whereMontheでdateが$dateの値を取得する

$data =  Hoge::where('user_id', $id)->when($inputs, function($query, $inputs) use($date){
                if($inputs['period'] == 'other'){
                    return $query->whereBetween('date', [$inputs['date_start'], $inputs['date_end']]);
                } else {
                    return $query->whereMonth('date', '=', $date);
                }
            })->count('date');

最初、public functionで条件判定する関数を別に書いて呼び出すのかと思ったが、whenを使えば、インラインでいける。
うん、綺麗^^
上記だとコード量は減るんだが、ただこれをforeachで回す場合、foreachの前で条件分岐をした方が、foreachで毎回判定しなくて良いので処理速度は上がる。
コード量が少ない方を優先してしまいがちだが、あくまでユーザの処理スピードを優先して書かないとダメか。
このwhenの書き方は凄く好きなんだけどなー

DBから今月のデータを取得したい時

– DBから今月のデータを取得して、一覧で表示したい時
— 月単位で探す場合はwhereMonthを使う

### whereMonth

$data = Hoge::whereMonth('start_at', '=', date('m'))->get();
dd($data);

whereMonth以外にも、whereDay、whereYearなどがある。
whereDate(‘start_at’, ‘=’, date(‘Y-m’)) だとうまくいかない。

改めてカラムのデータ型の重要性を認識。