[Laravel8.16.0] カラム追加・削除のmigrationファイルの書き方

usersテーブルで、first_name、last_nameを削除して、client_nameのカラムを作りたい
※「姓」・「名」を「名前」のカラム一つに統一したい

$ php artisan -V
Laravel Framework 8.16.0

migrationファイルの作成
$ php artisan make:migration change_name_columns_to_users_table –table=users

2020_12_04_040045_change_name_columns_to_users_table.php

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            //
            $table->string('client_name')->nullable();
            $table->dropColumn('first_name');
            $table->dropColumn('last_name');
        });
    }

$ php artisan migrate

// migration確認
mysql> describe users;
mysql> select * from users

// git push
$ git add .
$ git commit -m “name columns changed”

‘first_name’、’last_name’もnullableで作っていたが、dropColumnの時は特にnullbaleとかはつけなくて良いみたい。
なるほど。

[laravel8.12.3] テーブル追加の手順

まず適当にテーブルを作ります。
$ php artisan make:migration create_orders1_table

続いて適当にカラムを作ります。
-> カラム数29
2020_11_26_122330_create_orders1_table.php

public function up()
    {
        Schema::create('orders1', function (Blueprint $table) {
            $table->id();
            $table->integer('user_id');
            $table->integer('supplier_num');
            $table->integer('supplier_id1')->nullable();
            $table->integer('supplier_id2')->nullable();
            $table->integer('supplier_id3')->nullable();
            $table->decimal('supplier_subtotal1', 10, 2)->nullable();
            $table->decimal('supplier_subtotal2', 10, 2)->nullable();
            $table->decimal('supplier_subtotal3', 10, 2)->nullable();
            $table->decimal('total', 10, 2);
            $table->integer('product_num');
            $table->integer('product_id1')->nullable();
            $table->integer('product_id2')->nullable();
            $table->integer('product_id3')->nullable();
            $table->integer('product_id4')->nullable();
            $table->integer('product_id5')->nullable();
            $table->integer('qty1')->nullable();
            $table->integer('qty2')->nullable();
            $table->integer('qty3')->nullable();
            $table->integer('qty4')->nullable();
            $table->integer('qty5')->nullable();
            $table->decimal('subtotal1', 10, 2)->nullable();
            $table->decimal('subtotal2', 10, 2)->nullable();
            $table->decimal('subtotal3', 10, 2)->nullable();
            $table->decimal('subtotal4', 10, 2)->nullable();
            $table->decimal('subtotal5', 10, 2)->nullable();
            $table->text('detail')->nullable();
            $table->timestamps();
        });
    }

適当にmigrateします。
$ php artisan migrate
Migrating: 2020_11_26_122330_create_orders1_table
Migrated: 2020_11_26_122330_create_orders1_table (53.41ms)

mysql> describe orders1;
+——————–+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——————–+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| user_id | int | NO | | NULL | |
| supplier_num | int | NO | | NULL | |
| supplier_id1 | int | YES | | NULL | |
| supplier_id2 | int | YES | | NULL | |
| supplier_id3 | int | YES | | NULL | |
| supplier_subtotal1 | decimal(10,2) | YES | | NULL | |
| supplier_subtotal2 | decimal(10,2) | YES | | NULL | |
| supplier_subtotal3 | decimal(10,2) | YES | | NULL | |
| total | decimal(10,2) | NO | | NULL | |
| product_num | int | NO | | NULL | |
| product_id1 | int | YES | | NULL | |
| product_id2 | int | YES | | NULL | |
| product_id3 | int | YES | | NULL | |
| product_id4 | int | YES | | NULL | |
| product_id5 | int | YES | | NULL | |
| qty1 | int | YES | | NULL | |
| qty2 | int | YES | | NULL | |
| qty3 | int | YES | | NULL | |
| qty4 | int | YES | | NULL | |
| qty5 | int | YES | | NULL | |
| subtotal1 | decimal(10,2) | YES | | NULL | |
| subtotal2 | decimal(10,2) | YES | | NULL | |
| subtotal3 | decimal(10,2) | YES | | NULL | |
| subtotal4 | decimal(10,2) | YES | | NULL | |
| subtotal5 | decimal(10,2) | YES | | NULL | |
| detail | text | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+——————–+—————–+——+—–+———+—————-+
29 rows in set (0.00 sec)

続いて、このテーブルにSeederでテストデータを入れたい。

migration:rollbackで二つ以上前のmigrationに戻したい時

-カラムの修正などで、2つ以上前のmigrationにrollbackしたい時

### –step=n
migrationsのレコードが以下の状態で、aaaaaaa_tableに戻りたい。
mysql> select * from migrations;
+—-+————————————————+——-+
| id | migration | batch |
+—-+————————————————+——-+
| 11 | 2014_10_12_000000_create_users_table | 1 |
| 12 | 2014_10_12_100000_create_password_resets_table | 1 |
| 13 | 2019_08_19_000000_create_failed_jobs_table | 1 |
| 14 | 2019_12_29_222614_create_*****_table | 1 |
| 24 | 2020_01_02_003618_create_*****_table | 2 |
| 25 | 2020_01_02_113019_create_********_table | 2 |
| 26 | 2020_01_02_113040_create_******_table | 2 |
| 27 | 2020_01_02_113114_create_********_table | 2 |
| 28 | 2020_01_03_205110_create_*****_table | 2 |
| 29 | 2020_01_03_205139_create_*******_table | 2 |
| 30 | 2020_01_07_164639_create_aaaaaa_table | 3 |
| 34 | 2020_01_07_170922_create_bbbbb_bbbbbbbb_table | 4 |
| 35 | 2020_01_07_170950_create_ccccccc_table | 4 |
| 36 | 2020_01_10_005229_create_ddddddd_table | 4 |
+—-+————————————————+——-+

–step=nで、バッチを指定するのかと思い、–step=3とすると、3つだけ戻る
$ php artisan migrate:rollback –step=3
Rolling back: 2020_01_10_005229_create_ddddddd_table
Rolled back: 2020_01_10_005229_create_ddddddd_table (0.03 seconds)
Rolling back: 2020_01_07_170950_create_ccccccc_table
Rolled back: 2020_01_07_170950_create_ccccccc_table (0.02 seconds)
Rolling back: 2020_01_07_170922_create_bbbbb_bbbbbbbb
Rolled back: 2020_01_07_170922_create_bbbbb_bbbbbbbb_table (0.02 seconds)

$ php artisan migrate:rollback –step=4
Rolling back: 2020_01_10_005229_create_ddddddd_table
Rolled back: 2020_01_10_005229_create_ddddddd_table (0.02 seconds)
Rolling back: 2020_01_07_170950_create_ccccccc_table
Rolled back: 2020_01_07_170950_create_ccccccc_table (0.02 seconds)
Rolling back: 2020_01_07_170922_create_bbbbb_bbbbbbbb
Rolled back: 2020_01_07_170922_create_bbbbb_bbbbbbbb_table (0.02 seconds)
Rolling back: 2020_01_07_164639_create_aaaaaa_table
Rolled back: 2020_01_07_164639_create_aaaaaa_table (0.02 seconds)

–step=n は、テーブル数の指定なので、4つ戻したい時は4と指定する。

間違ってもクリティカルなエラーにはならないが、手順書などを書く場合は間違うと恥ずかしい😂

Laravel hasManyで親レコード削除と同時に子レコード削除の書き方

migration時に$table->foreign(‘user_id’)->references(‘id’)->on(‘users’)->onDelete(‘cascade’);と書く

AdminPostsController.php

public function edit($id)
    {
        //
        $post = Post::findOrFail($id);
        $categories = Category::lists('name', 'id')->all();
        return view('admin.posts.edit', compact('post','categories'));
    }

View: edit.blade.php

<h1>Edit Post</h1>
	{!! Form::Model($post, ['method'=>'PATCH', 'action'=>['AdminPostsController@update', $post->id], 'files'=>true]) !!}
        {{ csrf_field()}}
 
        <div class="form-group">
            {!! Form::label('title', 'Title') !!}
            {!! Form::text('title', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('category_id', 'Category') !!}
            {!! Form::select('category_id', $categories, null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('photo_id', 'Photo') !!}
            {!! Form::file('photo_id', null,['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('body', 'Description') !!}
            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
        </div>


        
        <div class="form-group">
            {!! Form::submit('Create Post', ['class'=>'btn btn-primary']) !!}
        </div>
    {!! Form::close() !!}

    </div>

    <div class="row">

    @include('includes.form_error')

	</div>

index.blade.php

<tr>
			  <td>{{$post->id}}</td>
			  <td><img height="50" src="{{$post->photo ? '/' . $post->photo->file : 'https://placehold.it/50x50'}}"></td>
			  <td><a href="{{route('admin.posts.edit', $post->id)}}">{{$post->user->name}}</a></td>
			  <td>{{$post->category ? $post->category->name : 'Uncategorized'}}</td>
			  <td>{{$post->title}}</td>
			  <td>{{$post->body}}</td>
			  <td>{{$post->created_at->diffForHumans()}}</td>
			  <td>{{$post->updated_at->diffForHumans()}}</td>
			</tr>

AdminPhotosController.php

public function update(Request $request, $id)
    {
        //
        $input = $request->all();

        if($file = $request['photo_id']){
            $name = time(). $file->getClientOriginalName();
            $file->move('images', $name);
            $photo = Photo::create(['file'=>$name]);

            $input['photo_id'] = $photo->id;
        }

        Auth::user()->posts()->whereId($id)->first()->update($input);

        return redirect('/admin/posts');
    }

### helper
laravel helper string functions
https://laravel.com/docs/master/helpers

string limit
https://laravel.com/docs/master/helpers#method-str-limit

 <td>{{str_limit($post->body, 10)}}</td>

### delete
edit.blade.php

{!! Form::open(['method'=>'DELETE', 'action'=>['AdminPostsController@destroy', $post->id]]) !!}
        {{ csrf_field()}}
        
        <div class="form-group">
            {!! Form::submit('Delete Post', ['class'=>'btn btn-danger col-sm-6']) !!}
        </div>
        {!! Form::close() !!}

AdminPostsController.php

public function destroy($id)
    {
        //
        $post = Post::findOrFail($id);
        unlink(public_path() . '/' .  $post->photo->file);
        $post->delete();

        return redirect('/admin/posts');
    }

### ユーザ削除するとpostsもdelete
Post migration file

Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned()->index();
            $table->integer('category_id')->unsigned()->index();
            $table->integer('photo_id')->unsigned()->index();
            $table->string('title');
            $table->text('body');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });

$ php artisan migrate:refresh

insert into roles (name) values (‘Administrator’);
insert into roles (name) values (‘Author’);
insert into roles (name) values (‘Subscriber’);

insert into categories (name) values (‘PHP’);
insert into categories (name) values (‘Laravel’);
insert into categories (name) values (‘JavaScript’);
insert into categories (name) values (‘Photoshop’);

update posts set user_id=10 where id=1;
update users set role_id=1 where id=1;

update posts set user_id=2 where id=2;

AdminUserController.php

public function destroy($id)
    {
        //
        $user = User::findOrFail($id);

        if($user->photo !== NULL){
            unlink(public_path() ."/". $user->photo->file);
        }
        $user->delete();

        Session::flash('deleted_user', 'The user has benn deleted');
        return redirect('/admin/users');
    }

edit.blade.php

<div class="col-sm-6">
		<img src="{{$post->photo ? $post->photo->file : 'https://placehold.it/400x400'}}" alt="" class="img-responsive">

	</div>

実際のアプリケーションでは、ユーザはsoftdeleteとして、更にユーザを削除しても、hasManyの子レコードは残したいケースが多いように思いますが、一緒に削除も可能です。

helperはstr_limit以外にも使えそうなものは使いたい。

migration後にカラムを追加したい時

カラム追加の場合は、table nameを指定してmake:migration
php artisan make:migration add_is_admin_column_to_posts_table –table=”posts”

dropも忘れずに書く
unsignedはnot negative number

public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            //
            $table->integer('is_admin')->unsigned();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            //
            $table->dropColumn('is_admin');
        });
    }

mysql> describe posts;
+————+——————+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————+——————+——+—–+———+—————-+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| content | text | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| is_admin | int(10) unsigned | NO | | NULL | |
+————+——————+——+—–+———+—————-+
6 rows in set (0.01 sec)

public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            //
            $table->tinyInteger('is_admin')->default('0');
        });
    }

migrate:resetはall rollback
$ php artisan migrate:reset
Rolled back: 2019_12_05_083227_add_is_admin_column_to_posts_table
Rolled back: 2019_12_05_072639_create_posts_table
Rolled back: 2014_10_12_100000_create_password_resets_table
Rolled back: 2014_10_12_000000_create_users_table

migrate:refreshはresetとmigrateを同時に行う
$ php artisan migrate:refresh

ステータス表示
$ php artisan migrate:status
+——+——————————————————+
| Ran? | Migration |
+——+——————————————————+
| Y | 2014_10_12_000000_create_users_table |
| Y | 2014_10_12_100000_create_password_resets_table |
| Y | 2019_12_05_072639_create_posts_table |
| Y | 2019_12_05_083227_add_is_admin_column_to_posts_table |
+——+——————————————————+

カラムの編集・追加の度にmigration fileを追加するのか、resetで対応するかはどうなんだろう?
resetの方が、tableごとに管理できるので管理しやすそうだが、migration fileを追加していくのは、時系列で追えるメリットがある。

migration

public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

mysql> describe posts;
+————+——————+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————+——————+——+—–+———+—————-+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| body | text | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+————+——————+——+—–+———+—————-+
5 rows in set (0.00 sec)

rollback
$ php composer.phar dump-autoload
$ php artisan migrate:rollback

mysql> show tables;
+————————+
| Tables_in_laravel_test |
+————————+
| migrations |
| password_resets |
| users |
+————————+
3 rows in set (0.00 sec)

ロールバックというと、deployのロールバックを思い浮かべますが、tableに対しても、rollback(drop)できるのですね。

migration fileの修正・アップデート

public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

カラムを追加して、もう一度migrateしたい時

public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

一度migrateした後、修正してphp artisan migrateとしてもnothing to doとなる
$ php artisan migrate
Nothing to migrate.

そのような場合は、
$ php artisan migrate:reset
$ php artisan migrate