Laravel 6.x系のvagrant & homestead導入手順

今、Laravelを開発するなら、6系以外は考えられない
ということで、vagrant & homesteadで環境構築したいと思います。

Laravel Homestead 公式ドキュメント
https://readouble.com/laravel/6.x/ja/homestead.html

$ vagrant -v
Vagrant 1.9.7

$ vagrant box add laravel/homestead

$ git clone https://github.com/laravel/homestead.git Homestead
$ cd homestead
$ ls
bin/ Homestead.yaml.example phpunit.xml.dist src/
CHANGELOG.md init.bat readme.md tests/
composer.json init.sh* resources/ Vagrantfile
composer.lock LICENSE.txt scripts/
$ git checkout release
$ bash init.sh

### homestead.yaml

---
ip: "192.168.10.10"
memory: 2048
cpus: 2
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:
    - map: ~/workspace/homestead/test
      to: /home/vagrant/test

sites:
    - map: homestead.test
      to: /home/vagrant/test/public

databases:
    - homestead

features:
    - mariadb: false
    - ohmyzsh: false
    - webdriver: false

### vagrant再インストール
$ vagrant -v
Vagrant 2.2.6

$ vagrant up
$ vagrant plugin expunge –reinstall
$ vagrant plugin update

$ vagrant up
$ vagrant status
$ vagrant halt
$ vagrant destroy

$ vagrant ssh

Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-72-generic x86_64)

Thanks for using
_ _ _
| | | | | |
| |__ ___ _ __ ___ ___ ___| |_ ___ __ _ __| |
| ‘_ \ / _ \| ‘_ ` _ \ / _ \/ __| __/ _ \/ _` |/ _` |
| | | | (_) | | | | | | __/\__ \ || __/ (_| | (_| |
|_| |_|\___/|_| |_| |_|\___||___/\__\___|\__,_|\__,_|

* Homestead v10.0.0 released
* Settler v9.1.0 released

$ php -v
PHP 7.4.0 (cli) (built: Nov 28 2019 07:27:06) ( NTS )
$ php artisan –version
Laravel Framework 6.9.0
$ git –version
git version 2.17.1
$ node -v
v12.13.1

こいつは強力!!!
あれ、でもこれ、EC2にデプロイする際には、EC2でのミドルウェアインストールは必須な訳でしょ。。

そう考えると、本当に時間がない時などに限られるか。

Amazon Linux 2のVagrant boxでshutdownできない時

vagrant boxesからamazon linux 2を入れて、環境構築をする
https://app.vagrantup.com/gbailey/boxes/amzn2

>mkdir amzn2
>cd amzn2
>vagrant init gbailey/amzn2

Vagrantfile

config.vm.network "private_network", ip: "192.168.33.10"

>vagrant up
>vagrant status
>vagrant ssh
$ exit

>vagrant halt

>vagrant status
default running (virtualbox)

なにいいいいいいいいいいいいいいいいいいいい?

Vagrantfile
config.vm.guest = :amazonを追加

config.vm.box = "gbailey/amzn2"
  config.vm.guest = :amazon

>vagrant halt
>vagrant status
default poweroff (virtualbox)

beautiful!

vagrant awslinuxとLarave update6.0

– php7.1は2019/12より積極的にメンテナンスされなくなる
– その為、php7.2以上が要求

$ php -v
PHP 7.1.7 (cli) (built: Sep 14 2017 15:47:38) ( NTS )
$ cat /etc/issue
Amazon Linux AMI release 2017.03

### PHP7.4へのアップデート
// repositoryのインストール
$ sudo yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm
$ sudo yum install -y –enablerepo=remi-php74 php which
Loaded plugins: priorities, update-motd, upgrade-helper
Error getting repository data for remi-php74, repository not found

あれ、vagrantboxesのawslinuxだと、php7.1以上は入れられない?
ってことは、vagrant init は、mvbcoding/awslinuxではなく、centos/7でやらないと駄目ってこと??
今のタイミングでlaravel6.0以外で開発するってのは絶対ありえんからなー

$ exit
logout
Connection to 127.0.0.1 closed.
> vagrant halt

Laravel update 5.7&5.8

### laravel 5.7
upgradeページを確認
https://readouble.com/laravel/5.7/ja/upgrade.html

composer.json

"require": {
        "php": ">=7.1.3",
        "laravel/framework": "5.7.*",
        "laravelcollective/html": "5.7.*",
        "cviebrock/eloquent-sluggable": "^4.3",
        "unisharp/laravel-filemanager": "^1.9",
        "intervention/image": "^2.5"
    },

$ php composer.phar update
$ php artisan –version
->エラー
-> Unresolvable dependency resolving [Parameter #0 [ $app ]] in class Illuminate\Support\Manager

config/app.php

Illuminate\Notifications\NotificationServiceProvider::class,

$ php artisan –version

### laravel 5.8
https://readouble.com/laravel/5.8/ja/upgrade.html

composer.json

"require": {
        "php": ">=7.1.3",
        "laravel/framework": "5.8.*",
        "laravelcollective/html": "5.8.*",
        "cviebrock/eloquent-sluggable": "^4.3",
        "unisharp/laravel-filemanager": "^1.9",
        "intervention/image": "^2.5"
    },

$ php artisan –version

criticalな変更はそこまでありませんが、公式のupgrade guideを読むと、セキュリティパッチが多く、やはり最新版に保った方が良さそうだ。

Laravel JavasScriptへデータを渡す書き方

HTML同様、scriptタグ内でも{{$variable}}と書くとデータが渡る

$ php artisan make:controller AdminController

web.php

Route::get('/', 'HomeController@index');
Route::get('/admin', 'AdminController@index');

AdminController.php

class AdminController extends Controller
{
    //
    public function index(){
    	return view('admin/index');
    }
}

AdminController.php

public function index(){
    	$postsCount = Post::count();
    	$categoriesCount = Category::count();
    	$commentsCount = Comment::count();

    	return view('admin/index',compact('postsCount','categoriesCount','commentsCount'));
    }

index.blade.php

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
<script>
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Posts', 'Categories', 'Comments'],
        datasets: [{
            label: 'Data of CMS',
            data: [{{$postsCount}},{{$categoriesCount}},{{$commentsCount}}],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)',
                'rgba(255, 159, 64, 0.2)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)',
                'rgba(255, 159, 64, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            yAxes: [{
                ticks: {
                    beginAtZero: true
                }
            }]
        }
    }
});
</script>

controllerのdataの取得は、count()以外にも、JSで表現したい内容に合わせてControllerで取得・整形する
JS側ではなくController側で整えるのが一般的か

Laravel upgrade to 5.6

### laravel 5.5
$ npm install

"require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.5.*",
        "laravelcollective/html": "5.5.*",
        "cviebrock/eloquent-sluggable": "^4.3",
        "unisharp/laravel-filemanager": "^1.9",
        "intervention/image": "^2.5"
    },
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~6.0",
        "symfony/css-selector": "2.8.*|3.0.*",
        "symfony/dom-crawler": "2.8.*|3.0.*"
    },

### laravel 5.6
composer.json

"scripts": {
        "post-root-package-install": [
            "php -r \"copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "php artisan key:generate"
        ],
        "post-install-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postInstall",
        ],
        "post-update-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postUpdate",
        ]
    },

$ php composer.phar update
$ php artisan –version

Pythonでも何だかんだ言われながらpytho2.7から3に変わっていきましたし、常に最新バージョンで開発する癖をつけた方が良さそうです。

Laravel Seeder & Factoryの使い方

DatabaseSeederの初期
./database/seeds/DatabaseSeeder.php

public function run()
    {
        $this->call(UsersTableSeeder::class);
    }

### make:seeder
$ php artisan make:seeder UsersTableSeeder
./database/seeds/UsersTableSeeder.php

public function run()
    {
        //
        DB::table('users')->insert([
        	'name'=>str_random(10),
        	'role_id'=>1,
        	'is_active'=>1,
        	'email'=>str_random(10).'@gmail.com',
        	'password'=>bcrypt('secret')
        ]);
    }

$ php artisan db:seed
> select * from users;
1件追加される

### factory
./database/factories/ModelFactory.php

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->safeEmail,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
});

./database/seeds/DatabaseSeeder.php

public function run()
    {
        // $this->call(UsersTableSeeder::class);
        factory(App\User::class, 10)->create();
    }
public function run()
    {
        // $this->call(UsersTableSeeder::class);
        factory(App\User::class, 10)->create()->each(function($user){
        	$user->posts()->save(factory(App\Post::class)->make());
        });
    }

### users以外でfactoryの利用
ModelFactory.php

$factory->define(App\Post::class, function (Faker\Generator $faker) {
    return [
        'category_id' => $faker->numberBetween(1,0),
        'photo_id' => 1,
        'title' => $faker->sentence(7, 11),
        'body' => $faker->paragraphs(rand(10,15), true),
    ];
});

$ php artisan db:seed

1件だけ追加するならrow sql queryのinsert intoで十分ですが、ページネーションのテストなどでデータ数百件入れたいときなどはFactoryが使えます。

あれ、これ、$fakerって英語だけ?
laravel 5.5以上だと、config/app.phpで’faker_locale’ => ‘ja_JP’,とすると、日本語化されるとのことです。

larave 5.3 -> 5.4 & Webpack

– 5.3 -> 6.0など一気に上げず、一つ一つバージョンを上げていく
– 5.4からcompileはgulpではなくwebpackになっており、基本的にwebpackを使う

"require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.4.*",
        "laravelcollective/html": "5.4.*",
        "cviebrock/eloquent-sluggable": "^4.0",
        "unisharp/laravel-filemanager": "^1.9",
        "intervention/image": "^2.5"
    },
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~5.7",
        "symfony/css-selector": "2.8.*|3.0.*",
        "symfony/dom-crawler": "2.8.*|3.0.*"
    },

webpack.mix.js

const { mix } = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js')
    .sass('resources/assets/sass/app.scss', 'public/css');

mix.styles([
    'resources/assets/css/libs/blog-post.css',
    'resources/assets/css/libs/bootstrap.css',
    'resources/assets/css/libs/font-awesome.css',
    'resources/assets/css/libs/metisMenu.css',
    'resources/assets/css/libs/sb-admin-2.css'

], 'public/css/libs.css');

mix.scripts([
    'resources/assets/js/libs/jquery.js',
    'resources/assets/js/libs/bootstrap.js',
    'resources/assets/js/libs/metisMenu.js',
    'resources/assets/js/sb-admin-2.js',
    'resources/assets/js/libs/jquery.js',
    'resources/assets/js/libs/scripts.js'

], 'public/js/libs.js');

package.json

{
  "private": true,
  "scripts": {
    "dev": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "hot": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "production": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
  },
  "devDependencies": {
    "axios": "^0.15.3",
    "bootstrap-sass": "^3.3.7",
    "jquery": "^3.1.1",
    "laravel-mix": "^0.8.1",
    "lodash": "^4.17.4",
    "vue": "^2.2.2"
  }
}

$ npm install
$ npm run dev
$ npm run watch

webpackはちょっと取っつきにくいイメージがありましたが、build inもありますし、そんなことありませんね。

Laravel checkbox複数一括処理の書き方

view側でinput type=”checkbox” class=”checkBoxes” name=”checkBoxArray[]” value=”{{$photo->id}}” と書いて、配列でcontrollerに送り、Photo::findOrFail($request->checkBoxArray)で受け取る。

Route

Route::delete('/delete/media', 'AdminMediasController@deleteMedia');
@if($photos)

		<form action="/delete/media" method="post" class="form-inline">
{{csrf-field()}}
			{{method_field('delete')}}
			<div class="form-group">
				<select name="checkBoxArray" id="" class="form-control">
					<option value="delete">Delete</option>
				</select>
			</div>
			<div class="form-group">
				<input type="submit" class="btn btn-primary">
			</div>

		<table class="table table-striped">
			<thead>
			<tr>
			  <th><input type="checkbox" id="options"></th>
			  <th>Id</th>
			  <th>Name</th>
			  <th>Created date</th>
			</tr>
			</thead>
			<tbody>
				@foreach($photos as $photo)
				<tr>
				  <th><input type="checkbox" class="checkBoxes" name="checkBoxArray&#91;&#93;" value="{{$photo->id}}"></th>
				  <td>{{$photo->id}}</td>
				  <td><img height="50" src="/{{$photo->file}}"></td>
				  <td>{{$photo->created_at ? $photo->created_at : 'no date'}}</td>
				  <td>
				  	{!! Form::open(['method'=>'DELETE', 'action'=>['AdminMediasController@destroy', $photo->id]]) !!}
			        {{ csrf_field()}}
			        
			        <div class="form-group">
			            {!! Form::submit('Delete', ['class'=>'btn btn-danger']) !!}
			        </div>
			        {!! Form::close() !!}

				  </td>
				</tr>
				@endforeach			
			</tbody>
		</table>

		</form>
		@endif
public function deleteMedia(Request $request){

        if(isset($request->delete_single)){
            $this->destroy($request->photo);
            return redirect()->back();
        }

        if(isset($request->delete_all) && !empty($request->checkBoxArray)){
            $photos = Photo::findOrFail($request->checkBoxArray);
            foreach($photos as $photo){
                $photo->delete();
            }
            return redirect()->back();
        } else {
            return redirect()->back();
        }

        
    }
	$(document).ready(function(){

	$('#options').click(function(){
		if(this.checked){
			$('.checkBoxes').each(function(){
				this.checked = true;
			});
		} else {
			$('.checkBoxes').each(function(){
				this.checked = false;
			});
		}
	});
});

一括削除はUX上、あまり宜しくないと思ったが、よく考えたら複数一括処理はselectで一括ステータス変更などにも使えるので、書き方自体は覚えておきたい。

LaravelでtinyMCE & filemanagerの使い方

tinyMCEだと、textareaで文字の装飾はもちろん画像の挿入ができる

### viewの作成
views/includes/tinyeditor.blade.php

<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
<script>tinymce.init({selector:'textarea'});</script>

posts/create.blade.php

@extends('layouts.admin')

@section('content')

    @include('includes.tinyeditor')
...

### filemanager install
https://unisharp.github.io/laravel-filemanager/installation

$ php composer.phar require unisharp/laravel-filemanager
$ php composer.phar require intervention/image

Unisharp\Laravelfilemanager\LaravelFilemanagerServiceProvider::class,
        Intervention\Image\ImageServiceProvider::class,

'Image' => Intervention\Image\Facades\Image::class,

$ php artisan vendor:publish –tag=lfm_config
$ php artisan vendor:publish –tag=lfm_public

### tinyeditor.blade.php

<script src="//cdn.tinymce.com/4/tinymce.min.js"></script>
<script>
  var editor_config = {
    path_absolute : "/",
    selector: "textarea",
    plugins: [
      "advlist autolink lists link image charmap print preview hr anchor pagebreak",
      "searchreplace wordcount visualblocks visualchars code fullscreen",
      "insertdatetime media nonbreaking save table contextmenu directionality",
      "emoticons template paste textcolor colorpicker textpattern"
    ],
    toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media",
    relative_urls: false,
    file_browser_callback : function(field_name, url, type, win) {
      var x = window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth;
      var y = window.innerHeight|| document.documentElement.clientHeight|| document.getElementsByTagName('body')[0].clientHeight;

      var cmsURL = editor_config.path_absolute + 'laravel-filemanager?field_name=' + field_name;
      if (type == 'image') {
        cmsURL = cmsURL + "&type=Images";
      } else {
        cmsURL = cmsURL + "&type=Files";
      }

      tinyMCE.activeEditor.windowManager.open({
        file : cmsURL,
        title : 'Filemanager',
        width : x * 0.8,
        height : y * 0.8,
        resizable : "yes",
        close_previous : "no"
      });
    }
  };

  tinymce.init(editor_config);
</script>

### config
lfm.php

'base_directory' => 'public',
'images_folder_name' => 'images',

### view
post.blade.php

<p>{!! $post->body !!}</p>

{{$post->body}}だと画像が表示されないので注意が必要
殆ど公式に書いてあるので、公式との闘いになりそう