20210106のlaravelに関する記事は9件です。

composer require実行時につまずいたエラー

laravelでAuthを導入しようと下記コマンドを実行

composer require laravel/ui:2.2

すると下記エラーが出る

Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes) in phar:///usr/local/bin/composer/src/Composer/DependencyResolver/RuleWatchGraph.php on line 52
Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors

どうやらメモリーが足りず実行できないよう。
laravel/uiに限らずcomposer requireを実行しようとするとこうなる。

解決方法

COMPOSER_MEMORY_LIMIT=-1 composer 〇〇

これで一時的にメモリを確保できるようだ。

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

Cloud9でLaravelの環境構築の方法

手順

PHPをインストール

$ sudo yum -y update
$ sudo yum -y install php72 php72-mbstring php72-pdo php72-intl php72-pdo_mysql php72-pdo_pgsql php72-xdebug php72-opcache php72-apcu
$ sudo unlink /usr/bin/php
$ sudo ln -s /etc/alternatives/php7 /usr/bin/php
$ php -v

composerをインストール

$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/bin/composer

Laravelをインストール

$ composer global require "laravel/installer"
$ composer create-project laravel/laravel アプリ名 6.* --prefer-dist
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

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

Problem

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

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

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

Test result:

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

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

Solution

Create a function likes a bellow.

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

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

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

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

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

The origin code

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

Refactoring 1: clarify array type

Util Classes
use Illuminate\Support\Collection;

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

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

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

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

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

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

    //...some ultil functions in here
}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Laravelコードを見直す

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

Content

1. Use default params when getting value from request

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

It is verbose

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

2. Use Eloquent when function

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

We can remove temp $query variable

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

3. Use Eloquent updateOrCreate

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

It is verbose

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

4. Use map instead of bundle if/else

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

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

        return 'draft';
    }

It is long

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

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

5. Use collection when you can

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

    return $names;
}

It is long

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

6. Stop calling query in loop

Ex1:

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

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

N + 1 queries problem

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

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

Ex2;

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

N + 1 queries problem

Refactoring

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

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

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

Total: 2 queries

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

7. Stop printing raw user inputted values in blade.

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

There is a XSS security issue

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

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

8. Be careful when running javascript with user input.

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

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

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

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

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

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

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

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

9. Stop abusing Morph

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

The original code
    posts
        id - integer
        name - string

    videos
        id - integer
        name - string

    tags
        id - integer
        name - string

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

    videos
        id - integer
        name - string

    tags
        id - integer
        name - string

    posts_tags
        tag_id - integer
        post_id - integer

    videos_tags
        tag_id - integer
        video_id - integer

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

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

Laravel6以降のbootstrapの導入方法

Laravel6以降はbootstrapを手動で導入する必要がある。

ターミナルでコマンドを打つ手順

composer require laravel/ui 1.*
php artisan ui bootstrap
npm install && npm run dev

この順にコマンドを打っていく。

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

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

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

FormRequestとは

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

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

使ってみる

artisanコマンドで作成できる

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

use Illuminate\Foundation\Http\FormRequest;

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

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

バリデーションルール

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

こんな感じで書く

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

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

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

他にも色々な機能がある

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

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

Laravelマイグレーションをやり直す方法

php artisan migrate

を実行したけど、やり直したい時は、

php artisan migrate:rollback

で一つ前の状態に戻すことができる。

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