Создание интернет-витрины на laravel. Часть 3
.
Капустин Яков
оглавление
- 01 Добавление UsersTableSeeder
- 02 Массовое заполнение
- 03 Создание модели Comment и миграции
- 04 Eloquent отношения hasMany и belongsTo
- 05 Создание ProductCommentsController и правка отображения
- 06 Изменения в ProductsController
- 07 Обновление товаров
- 08 Косметические изменения в файлах отображения
- 09 Проверка удаления комментариев удаляемого товара.
- 10 Результаты
01Добавление UsersTableSeeder
Для отмены необходимости создавать пользователя каждый раз после полной перестройки базы данных создадим UsersTableSeeder и добавим класс UsersTableSeeder в файл 'database/seeds/DatabaseSeeder.php':
bash:vagrant@homestead:~/projects/kk$ php artisan make:seeder UsersTableSeeder Seeder created successfully.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert([
'name' => 'Admin Name',
'email' => 'admin@gmail.com',
'password' => bcrypt('111111'),
]);
}
}
и добавим класс UsersTableSeeder в файл 'database/seeds/DatabaseSeeder.php':
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
// $this->call(UsersTableSeeder::class);
$this->call([
ProductsTableSeeder::class,
UsersTableSeeder::class,
]);
}
}
02Массовое заполнение
Сделаем все атрибуты Product массово назначаемыми, определив свойство $guarded как пустой массив:
02
03
04
05
06
07
08
09
10
11
12
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $guarded = [];
}
03Создание модели Comment и миграции
Создадим модель Comment вместе с фабрикой(?) и миграцией.
bash:vagrant@homestead:~/projects/kk$ php artisan make:model Comment -m -f Model created successfully. Factory created successfully. Created Migration: 2019_05_20_010314_create_comments_table
Опишем и выполним миграцию.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCommentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('product_id');
$table->unsignedInteger('user_id')->nullable();
$table->string('name_guest')->nullable();
$table->text('comment_string');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('comments');
}
}
bash:vagrant@homestead:~/projects/kk$ php artisan migrate Migrating: 2019_05_20_010314_create_comments_table Migrated: 2019_05_20_010314_create_comments_table vagrant@homestead:~/projects/kk$
04Eloquent отношения hasMany и belongsTo
Опишем отношения в моделях Product и Comment.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $guarded = [];
public function comments() {
return $this->hasMany(Comment::class);
}
}
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function product() {
return $this->belongsTo(Product::class);
}
}
05Создание ProductCommentsController и правка отображения
Создадим контроллер ProductCommentsController и опишем в нем необходимые методы:
bash:vagrant@homestead:~/projects/kk$ php artisan make:controller ProductCommentsController Controller created successfully. vagrant@homestead:~/projects/kk$
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Comment;
use App\Product;
use Illuminate\Support\Facades\Validator;
class ProductCommentsController extends Controller
{
public function index() { // testing deletion of product comments upon product removal
return $comments = Comment::all();
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Product $product) {
// $validator = Validator::make($request->all(), [
// 'product_id' => 'required|integer',
// 'user_id' => 'required|integer',
// 'guest_name' => 'nullable|string',
// 'comment_string' => 'required|string',
// ]);
// if ($validator->fails()) {
// return back()->withErrors($validator)->withInput();
// }
$comment = Comment::create([
'product_id' => $product->id,
'user_id' => request('user_id'),
'guest_name' => request('guest_name'),
'comment_string' => request('comment_string'),
]);
// return back();
return redirect('/products/' . $product->id . '#comment_' . $comment->id);
}
public function update(Comment $comment) {
// dd($comment);
// dd(request()->all());
$comment->update([
'comment_string' => request('comment_string'),
]);
// return redirect()->route('productsShow', ['product' => $comment->product_id]);
return redirect('/products/' . $comment->product_id . '#comment_' . $comment->id);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Comment $comment
* @return \Illuminate\Http\Response
*/
public function destroy(Comment $comment)
{
// dd($comment);
$product_id = $comment->product_id;
$comment->delete();
return redirect()->route('productsShow', ['product' => $product_id]);
}
}
Дополним файл отображения для вывода комментариев:
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
@extends('layouts.app')
@section('title')
{{ $product->name }}
@endsection
@section('content')
<div class="container">
<h1>{{ $product->name }}</h1>
<div class="row">
<div class="col-md-4 wrap_b_image">
@if($product->image)
<div class="card-img-top b_image" style="background-image: url({{ asset('storage') }}/images/products/{{$product->id}}/{{$product->image}});">
@else
<div class="card-img-top b_image" style="background-image: url({{ asset('storage') }}/images/default/no-img.jpg);">
@endif
<div class="dummy"></div><div class="element"></div>
</div>
</div>
<div class="col-md-8">
<h2>specification product</h2>
<span class="grey">manufacturer: </span>{{ $product->manufacturer }}<br>
<span class="grey">materials: </span>{{ $product->materials }}<br>
<span class="grey">year_manufacture: </span>{{ $product->year_manufacture }}<br>
<span class="grey">артикул: </span>{{ $product->id }}<br>
@if($product->price)
<span class="grey">price: </span>{{ $product->price }} ₽
@else
<span class="grey">priceless</span>
@endif
<div class="row product_buttons">
<div class="col-sm-4">
<a href="#" class="btn btn-outline-primary">
<i class="fas fa-shopping-cart"></i> buy now
</a>
</div>
<div class="col-sm-4">
<a href="{{ route('productsEdit', ['product' => $product->id]) }}" class="btn btn-outline-success">
<i class="fas fa-pen-nib"></i> edit
</a>
</div>
<div class="col-sm-4">
<!-- form delete product -->
<form action="{{ route('productsDestroy', ['product' => $product->id]) }}" method='POST'>
@csrf
@method('DELETE')
<button type="submit" class="btn btn-outline-danger">
<i class="fas fa-trash"></i> delete
</button>
</form>
</div>
</div>
</div>
</div><br>
<div class="row">
<div class="col-md-12">
<h2>description {{ $product->name }}</h2>
<p>{{ $product->description }}</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h2>comments for {{ $product->name }}</h2>
@if($product->comments->count())
<ul class='content list-group'>
@foreach ($product->comments as $comment)
<li class="list-group-item" id="comment_{{ $comment->id }}" >
<div class="comment_header">
@if($comment->guest_name)
Guest {{ $comment->guest_name }}
@else
User #{{ $comment->user_id }}
@endif
<!-- created_at/updated_at -->
@if($comment->updated_at == $comment->created_at)
wrote {{ $comment->created_at }}:
@else
wrote {{ $comment->created_at }} (edited: {{ $comment->updated_at }}):
@endif
<div class="comment_buttons">
<div class="comment_num">#{{ $comment->id }}</div>
<!-- button edit -->
<button type="button" class="btn btn-outline-success" data-toggle="collapse"
data-target="#collapse_{{ $comment->id }}" aria-expanded="false" aria-controls="coll" class="edit"
>
<i class="fas fa-pen-nib"></i>
</button>
<!-- delete comment -->
<!-- <form action='/comments/destroy/{{ $comment->id }}' method='POST'> -->
<form action="{{ route('commentsDestroy', ['comment' => $comment->id]) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-outline-danger"><i class="fas fa-trash"></i></button>
</form>
</div>
</div>
<div class="comment_str">{{$comment->comment_string}}</div>
<!-- form edit -->
<form action="/comments/{{ $comment->id }}"
method="POST" class="collapse" id="collapse_{{ $comment->id }}"
>
@method('PATCH')
@csrf
<textarea id="comment_string_{{ $comment->id }}" name="comment_string"
cols="30" rows="4" class="form-control card" placeholder="Add a comment"
>{{$comment->comment_string}}</textarea>
<button type="submit" class="btn btn-success">edit comment</button>
</form>
</li>
@endforeach
</ul>
@else
<p class="grey">no comments for this product.</p>
@endif
</div>
</div>
<div class="row">
<div class="col-md-12">
<h2>leave your comment</h2>
<form method="POST" action="/products/{{ $product->id }}/comments">
@csrf
<!-- <input type="hidden" name="product_id" value="{{ $product->id }}"> -->
@auth
<input type="hidden" name="user_id" value="{{ Auth::user()->id }}">
<input type="hidden" name="guest_name" value="">
@else
<input type="hidden" name="user_id" value="0">
<div class="form-group">
<!-- <label for="guest_name">Your name</label> -->
<input type="text" id="guest_name" name="guest_name" class="form-control" placeholder="Your name" value="{{ old('guestName') }}" required>
</div>
@endauth
<div class="form-group">
<!-- <label for="comment_string">Add a comment</label> -->
<textarea id="comment_string" name="comment_string" cols="30" rows="4" class="form-control" placeholder="Add a your comment" required>{{ old('comment_string') }}</textarea>
</div>
<button type="submit" class="btn btn-primary">comment on</button>
</form>
</div>
</div>
</div>
@endsection
Изменим существующие и добавим роуты для комментариев:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
/* products*/
Route::get('/products', 'ProductsController@index')->name('products');
Route::get('/products/create', 'ProductsController@create')->name('productsCreate');
Route::get('/products/{product}', 'ProductsController@show')->name('productsShow');
Route::post('/products', 'ProductsController@store')->name('productsStore');
Route::get('/products/edit/{product}', 'ProductsController@edit')->name('productsEdit');
Route::patch('/products/{product}', 'ProductsController@update')->name('productsUpdate');
Route::delete('/products/{product}', 'ProductsController@destroy')->name('productsDestroy');
/* comments*/
Route::get('/comments', 'ProductCommentsController@index');
Route::get('/comments/create', 'ProductCommentsController@create');
Route::get('/comments/{comment}', 'ProductCommentsController@show');
// Route::post('/comments', 'ProductCommentsController@store');
Route::post('/products/{product}/comments', 'ProductCommentsController@store');
Route::get('/comments/edit/{comment}', 'ProductCommentsController@edit');
Route::patch('/comments/{comment}', 'ProductCommentsController@update');
// Route::delete('/comments/destroy/{comment}', 'ProductCommentsController@destroy');
Route::delete('/comments/{comment}', 'ProductCommentsController@destroy')->name('commentsDestroy');
06Изменения в ProductsController
Добавим методы storeImage() и update(), изменим return, используя именованные маршруты и допишем удаление комментариев к удаляемому товару.
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Storage;
use App\Product;
class ProductsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index() {
$products = DB::table('products')->orderBy('id', 'desc')->simplePaginate(6);
return view('products.index', compact('products'));
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id) {
$product = Product::find($id);
return view('products.show', compact('product'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('products.create');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|max:255',
'manufacturer' => 'nullable|string',
'materials' => 'nullable|string',
'description' => 'nullable|string',
'image' => 'image',
'year_manufacture' => 'nullable|integer',
'price' => 'nullable|integer',
'added_by_user_id' => 'required|integer',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$product = new Product;
$product->name = $request->get('name');
$product->manufacturer = $request->get('manufacturer') ?? '';
$product->materials = $request->get('materials') ?? '';
$product->description = $request->get('description') ?? '';
$product->year_manufacture = $request->get('year_manufacture') ?? 0;
$product->price = $request->get('price') ?? 0;
$product->added_by_user_id = $request->get('added_by_user_id');
if (!$product->save()) {
return back()->withErrors(['something wrong!'])->withInput();
}
if (request()->file('image')) {
$product->image = $this->storeImage(request()->file('image'), $product->id);
if (!$product->image or !$product->update()) {
return back()->withErrors(['something wrong. err' . __line__])->withInput();
}
}
return redirect()->route('productsShow', ['product' => $product->id]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Product $product)
{
$validator = Validator::make(request()->all(), [
'name' => 'required|max:255',
'manufacturer' => 'nullable|string',
'materials' => 'nullable|string',
'description' => 'nullable|string',
'image' => 'image',
'year_manufacture' => 'nullable|integer',
'price' => 'nullable|integer',
'edited_by_user_id' => 'required|integer',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$product->update([
'name' => request('name'),
'manufacturer' => request('manufacturer'),
'materials' => request('materials'),
'description' => request('description'),
'year_manufacture' => request('year_manufacture'),
'edited_by_user_id' => request('edited_by_user_id'),
]);
if (request()->file('image')) {
$product->image = $this->storeImage(request()->file('image'), $product->id);
if (!$product->image or !$product->update()) {
return back()->withErrors(['something wrong. err' . __line__])->withInput();
}
}
return redirect()->route('productsShow', ['product' => $product->id]);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$product = Product::find($id);
// destroy product images
if ($product->image) {
$directory = 'public/images/products/' . $product->id;
Storage::deleteDirectory($directory);
}
// destroy product comments
$product->comments()->delete();
// destroy product
$product->delete();
return redirect()->route('products');
}
/**
* Store image product.
*
* @param \Illuminate\Http\Request $request
* @return string $filename or false
*/
private function storeImage($image, $product_id) {
$directory = 'public/images/products/' . $product_id;
$filename = $image->getClientOriginalName();
if (Storage::makeDirectory($directory)) {
if (Storage::putFileAs($directory, $image, $filename)) {
return $filename;
}
}
return FALSE;
}
}
07Обновление товаров
Добавим поле 'edited_by_user_id' в таблицу 'products':
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
...
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('manufacturer')->nullable();
$table->string('materials')->nullable();
$table->text('description')->nullable();
$table->string('image')->nullable()->charset('utf8');
$table->integer('year_manufacture')->nullable();
$table->float('price', 8, 2)->nullable();
$table->unsignedInteger('added_by_user_id');
$table->unsignedInteger('edited_by_user_id')->nullable(); // upd
$table->timestamps();
});
...
и опишем файлы отображения для создания и редактирования товаров:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@extends('layouts.app')
@section('title')
Creating new product
@endsection
@section('content')
<div class="container">
<div class="row justify-content-center">
<h1>Creating new product</h1>
</div>
<div class="row">
<div class="col-sm-12 product_card_bm">
<div class="card">
<form method="POST" action="{{ route('productsStore') }}" enctype="multipart/form-data">
@csrf
<input type="hidden" name="added_by_user_id" value="0">
<div class="form-group">
<!-- <input type="file" id="image" name="image" accept="image/png, image/jpeg, jpg, pdf"> -->
<input type="file" name="image" accept=".jpg, .jpeg, .png" value="{{ old('image') }}">
</div>
<div class="form-group">
<!-- <label for="name">name</label> -->
<input type="text" id="name" name="name" class="form-control" placeholder="Name Product" value="{{ old('name') }}" required>
</div>
<div class="form-group">
<!-- <label for="manufacturer">manufacturer</label> -->
<input type="text" id="manufacturer" name="manufacturer" class="form-control" placeholder="manufacturer" value="{{ old('manufacturer') }}">
</div>
<div class="form-group">
<!-- <label for="materials">materials</label> -->
<input type="text" id="materials" name="materials" class="form-control" placeholder="materials" value="{{ old('materials') }}">
</div>
<div class="form-group">
<!-- <label for="year_manufacture">year_manufacture</label> -->
<input type="number" id="year_manufacture" name="year_manufacture" class="form-control" placeholder="year_manufacture" value="{{ old('year_manufacture') }}">
</div>
<div class="form-group">
<!-- <label for="price">price</label> -->
<input type="number" id="price" name="price" class="form-control" placeholder="price" value="{{ old('price') }}">
</div>
<!-- <input type="hidden" name="added_by_user_id" value=""> -->
<div class="form-group">
<!-- <label for="description">Add a comment</label> -->
<textarea id="description" name="description" cols="30" rows="10" class="form-control" placeholder="description">{{ old('description') }}</textarea>
</div>
<button type="submit" class="btn btn-primary form-control">Create new product!</button>
</form>
</div>
</div>
</div>
</div>
@endsection
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@extends('layouts.app')
@section('title')
Creating new product
@endsection
@section('content')
<div class="container">
<div class="row justify-content-center">
<h1>edit {{ $product->name }}</h1>
</div>
<div class="row">
<div class="col-sm-12 product_card_bm">
<div class="card">
<form method="POST" action="{{ route('productsUpdate', ['product' => $product->id]) }}" enctype="multipart/form-data">
@csrf
@method('PATCH')
<input type="hidden" name="edited_by_user_id" value="0">
@if($product->image)
<div class="card-img-top b_image col-sm-4" style="background-image: url({{ asset('storage') }}/images/products/{{$product->id}}/{{$product->image}});">
<div class="dummy"></div><div class="element"></div>
</div>
<div class="form-group"> заменить изображение
<input type="file" name="image" accept=".jpg, .jpeg, .png"
value="{{ old('image') }}">
</div>
@else
<div class="form-group">
<input type="file" name="image" accept=".jpg, .jpeg, .png"
value="{{ old('image') }}">
</div>
@endif
<div class="form-group">
<!-- <label for="name">name</label> -->
<input type="text" id="name" name="name" class="form-control" placeholder="Name Product"
value="{{ old('name') ?? $product->name }}" required>
</div>
<div class="form-group">
<!-- <label for="manufacturer">manufacturer</label> -->
<input type="text" id="manufacturer" name="manufacturer" class="form-control"
placeholder="manufacturer" value="{{ old('manufacturer') ?? $product->manufacturer }}">
</div>
<div class="form-group">
<!-- <label for="materials">materials</label> -->
<input type="text" id="materials" name="materials" class="form-control"
placeholder="materials" value="{{ old('materials') ?? $product->materials }}">
</div>
<div class="form-group">
<!-- <label for="year_manufacture">year_manufacture</label> -->
<input type="number" id="year_manufacture" name="year_manufacture" class="form-control"
placeholder="year_manufacture" value="{{ old('year_manufacture') ?? $product->year_manufacture }}">
</div>
<div class="form-group">
<!-- <label for="price">price</label> -->
<input type="number" id="price" name="price" class="form-control" placeholder="price"
value="{{ old('price') ?? $product->price }}">
</div>
<!-- <input type="hidden" name="added_by_user_id" value=""> -->
<div class="form-group">
<!-- <label for="description">Add a comment</label> -->
<textarea id="description" name="description" cols="30" rows="10" class="form-control"
placeholder="description">{{ old('description') ?? $product->description }}</textarea>
</div>
<button type="submit" class="btn btn-primary form-control">edit product!</button>
</form>
</div>
</div>
</div>
</div>
@endsection
08Косметические изменения в файлах отображения
Слегка подправим файлы отображения и стили; подключим fontawesome.
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') {{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset('css/yo.css') }}" rel="stylesheet">
<!-- Favicon -->
<link rel="icon" type="image/png" sizes="128x128" href="/favicon.png">
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-dark">
<div class="container">
<a class="navbar-brand grey" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
@include('menu.main')
@if ($errors->any())
<div class="container">
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>Holy guacamole!</strong> You should check in on some of those fields below.
<ol>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ol>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
</div>
@endif
<main class="py-4">
@yield('content')
</main>
@include('menu.main')
<div class="container">
<div class="footer">
<a aria-label="Homepage" title="GitHub" class="footer-octicon d-none d-lg-block mx-lg-4" href="https://github.com/yakoffka/kk">
<svg height="24" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="24" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
</a>
</div>
</div>
</div>
</body>
</html>
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@extends('layouts.app')
@section('title')
catalog
@endsection
@section('content')
<div class="container">
<h1>Products</h1>
<div class="row">
<!-- pagination block -->
@if($products->links())
<div class="row col-sm-12 pagination">{{ $products->links() }}</div>
@endif
@foreach($products as $product)
<div class="col-lg-4 product_card_bm">
<div class="card">
<h5><a href="{{ route('productsShow', ['product' => $product->id]) }}">{{ $product->name }}</a></h5>
<a href="{{ route('productsShow', ['product' => $product->id]) }}">
@if($product->image)
<div class="card-img-top b_image" style="background-image: url({{ asset('storage') }}/images/products/{{$product->id}}/{{$product->image}});">
@else
<div class="card-img-top b_image" style="background-image: url({{ asset('storage') }}/images/default/no-img.jpg);">
@endif
<div class="dummy"></div><div class="element"></div>
</div>
</a>
<div class="card-body">
<p class="card-text col-sm-12">
<span class="grey">
@if($product->price)
price: {{ $product->price }} ₽
@else
priceless
@endif
</span><br>
</p>
<div class="row product_buttons">
<div class="col-sm-4">
<a href="{{ route('productsShow', ['product' => $product->id]) }}" class="btn btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
</div>
<div class="col-sm-4">
<a href="{{ route('productsEdit', ['product' => $product->id]) }}" class="btn btn-outline-success">
<i class="fas fa-pen-nib"></i>
</a>
</div>
<div class="col-sm-4">
<!-- form delete product -->
<form action="{{ route('productsDestroy', ['product' => $product->id]) }}" method='POST'>
@csrf
@method('DELETE')
<button type="submit" class="btn btn-outline-danger">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endforeach
<!-- pagination block -->
@if($products->links())
<div class="row col-sm-12 pagination">{{ $products->links() }}</div>
@endif
</div>
</div>
@endsection
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
h1{
text-align:center;
color: #636b6f;
}
h2{
color: #09c;
}
.center{
text-align:center;
}
.grey {
color: #636b6f;
}
.row {
margin:1em 0;
}
/* /app.blade */
nav.navbar.navbar-dark {
background-color: #09c;
}
a.navbar-brand.grey, .navbar-dark .navbar-nav .nav-link{
/* color: rgba(0,0,0,.7); */
color: hsla(0,0%,100%,.75);
font-size: .9rem;
}
a.navbar-brand.grey:hover, .navbar-dark .navbar-nav .nav-link:hover{
/* color: rgba(0,0,0,.7); */
color: hsla(0,0%,100%,.95);
font-size: .9rem;
}
/* /app.blade */
/* main menu */
.main_menu {
text-align: center;
margin: 1em 0;
}
.main_menu > a {
color: #636b6f;
padding: 0 25px;
font-size: 13px;
font-weight: 600;
letter-spacing: .1rem;
text-decoration: none;
text-transform: uppercase;
}
/* /main menu */
/* products */
.card .row.product_buttons .col-sm-4{
padding: 2%;
}
.product_card_bm {
margin-bottom: 2em;
}
.product_card_bm .card{
padding: 1em;
min-height: 5em;
}
.product_card_bm .card h5{
text-align:center;
}
.product_card_bm .card h5 a:hover {
color: #3490dc;
text-decoration: none;
}
.pagination {
padding: 0 1em;
}
.row.product_buttons a.btn,
.row.product_buttons form button
{
width: 100%;
}
/* /products */
/* product */
.card img.img_lr {
width:auto;
max-height:280px;
}
.row.product_buttons .col-sm-4{
padding-left: 0;
}
.card .product_buttons a.btn,
.card .product_buttons form button
{
width: 100%;
padding-left: 0;
padding-right: 0;
}
.comment_str {
padding-top: .75rem;
}
.comment_header {
position: relative;
background-color: #e1e4e8;
margin: -.75rem -1.25rem 0;
padding: .75rem 1.25rem;
}
.comment_header .comment_buttons {
position: absolute;
right: 0;
top: 0;
margin: .26em 0;
}
.comment_header .comment_buttons button,
.comment_header .comment_buttons form,
.comment_header .comment_buttons .comment_num
{
float: left;
margin: 0 .25em;
/* background-color: #fff; */
}
.comment_header .comment_buttons form {
margin-left: 0;
margin-right: 0;
}
.comment_header .comment_buttons .comment_num {
padding: .375rem 0rem;
font-size: .9rem;
line-height: 1.6;
}
.comment_header .comment_buttons button{
background-color: #fff;
}
.comment_header .comment_buttons button.btn-outline-info:hover {
color: #212529;
background-color: #6cb2eb;
border-color: #6cb2eb;
}
.comment_header .comment_buttons button.btn-outline-danger:hover {
color: #fff;
background-color: #e3342f;
border-color: #e3342f;
}
form.collapse textarea {
margin-bottom: .25rem;
}
.wrap_b_image {
background-color: #fff;
border: 1px solid rgba(0,0,0,.125);
border-radius: .25rem;
padding:1em;
}
/* /product */
/* b_image https://htmler.ru/2016/06/07/css-height-dynamic-width/*/
.b_image{
width:100%;
background-repeat:no-repeat;
background-position:center center;
background-size:contain;
display: inline-block;
position: relative;
}
.b_image .dummy {
margin-top: 100%; /* соотношение сторон*/
}
.b_image .element {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
/* b_image */
/* footer */
.footer {
margin: 1em 0;
padding: 1em 0;
color: #e1e4e8;
border-top: 1px solid #e1e4e8 !important;
text-align: center;
}
.footer .footer-octicon {
color: #c6cbd1;
}
.footer .footer-octicon .octicon {
display: inline-block;
fill: currentColor;
}
/* /footer */
09Проверка удаления комментариев удаляемого товара.
перестроим базу данных.
bash:vagrant@homestead:~/projects/kk$ php artisan migrate:refresh --seed Rolling back: 2019_05_20_010314_create_comments_table Rolled back: 2019_05_20_010314_create_comments_table Rolling back: 2019_05_17_163750_create_products_table Rolled back: 2019_05_17_163750_create_products_table Rolling back: 2014_10_12_100000_create_password_resets_table Rolled back: 2014_10_12_100000_create_password_resets_table Rolling back: 2014_10_12_000000_create_users_table Rolled back: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2019_05_17_163750_create_products_table Migrated: 2019_05_17_163750_create_products_table Migrating: 2019_05_20_010314_create_comments_table Migrated: 2019_05_20_010314_create_comments_table Seeding: ProductsTableSeeder Seeding: UsersTableSeeder Database seeding completed successfully. vagrant@homestead:~/projects/kk$
Напишем три комментария (два к товару с id=24).
Удалим товар с id=24 и проверим результат.
Капустин Яков (2019.05.19 22:46)