Вы уже из-за подхода Laravel начали во всю использовать статики: Card::change Log::error
class CartController extends Controller
{
//...
public function change($productId, $action)
{
try {
$session_id = session()->getId();
$cart = Cart::bySession($session_id)
->firstOrCreate(['session' => $session_id]);
$cart->change($productId, $action);
} catch(\Exception $e) {
// Здесь можно разрешить как не стандартные ситуации, так и стандартные.
// Но у нас пока одно.
Log::error('Cart change: ' . $e->getMessage(), [$productId, $action]);
return back()
->with('message', 'Простите, дяденька, засранца.');
}
// Возвращаемся туда от куда добавился товар или изменилось его количество
return back();
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Cart\ActionException;
class Cart extends Model
{
// ...
public function scopeBySession($query, $sessionId)
{
return $query->where('session', '=', $sessionId);
}
public function change($productId, $action)
{
switch($action) {
case 'append':
$this->processAppend($productId);
break;
case 'reduce':
$this->processReduce($productId);
break;
case 'clear':
$this->processClear($productId);
break;
default:
throw new ActionException($action, $this->session, $productId);
}
}
private function processAppend($productId)
{
// ...
}
// ...
}
Это абсолютно не рашсряемо. Если у вам нужно подменить логгер в зависимости от окружения. Задача, кстати, очень частая, когда подменяют именно логер.
Card::change - вы жестко завязались на метод change который что-то делает.
Проблема целостности логики приложения — тут не видно ни связи текущего пользователя с корзиной. Потенциальная уязвимость того, что вы можете привязать товар ни к той корзине. Если один экшен, это не так критично. А представьте, если таких мест десятки. Как пример амазон. Там различных способов работы с корзиной кучи.
Проблема расширяемости. Вы не можете подменить спокойно реализацию корзины для разных пользователей. Например, если у юриков и у физиков разные способы размещения заказов в ней (если правильно помню, тоже из амзмона пример)
Вы, скажете, что это не проблема Laravel. А я скажу, что именно его. Потому что он не предоставляет своей методики, как правильно получать экземпляры, кроме как через запрос в контроллере через статические методы подключения к БД.
Далее. Как вы собираетесь прикручивать ко всему этому гибкие права. Например, у пользователя с ролью менеджера есть права только на чтение.
class CartChange extends FormRequest
{
// НАПРИМЕР, ОДНА ИЗ ТОЧЕК ПРОВЕРКИ ПРАВ!!!
public function authorize()
{
return true; // Если в данном контексте мы этим не замарачиваемся
}
Теперь возвращаемся к коду. В CartChange много чего завязано на какие то ассоциативные массивы и куча публичных методов. Я не понимаю как это работает. Это вообще процедурное программирование. Тут нет реализации каких то интерфейсов, для понимания, что можно передать, а чего нельзя.
Это сплошное нарушение SOLID, так как CartChange нарушает принцип единой ответственности. Он предоставляет различные данные разной с разной природой.
Даже передавать разный request для каждого action попахивает. Меняем строчку в роутинге и вам приходит другой объект. Потенциальное место огрести кучу неявных ошибок.
<!-- Вариант с параметрами в URL -->
<form action={{ route('cart.change', ['action' => 'append', 'product' => $produt->id]) }} method="post">
{{ csrf_field() }}
<button type="submit">+</button>
</form>
<span>{{ $cart->productQuantity($produt->id) }}</span>
<form action={{ route('cart.change', ['action' => 'reduce', 'product' => $produt->id]) }} method="post">
{{ csrf_field() }}
<button type="submit">-</button>
</form>
<!-- Вариант с CartChange Параметры в форме -->
<form action={{ route('cart.change') }} method="post">
{{ csrf_field() }}
<input type="hidden" name="action" value="append">
<input type="hidden" name="product" value="{{ $produt->id }}">
<button type="submit">+</button>
</form>
<span>{{ $cart->productQuantity($produt->id) }}</span>
<form action={{ route('cart.change']) }} method="post">
{{ csrf_field() }}
<input type="hidden" name="action" value="reduce">
<input type="hidden" name="product" value="{{ $produt->id }}">
<button type="submit">-</button>
</form>
Вы вообще на что это пишите? Я вас спрашиваю, поверх чего создаются слои и для чего.
// ...
Route::post('cart', [
'as' => 'cart.change',
'uses' => 'CartController@change',
]);
<?php namespace App\Http\Controllers;
// Для папки App\Http\Requests\ валидируемые запросы идут из коробки,
// но требуют определения
// Например, реализуем проверку параметров сортировки
use App\Http\Requests\CartGet;
// Здесь проверяем product_id и action
use App\Http\Requests\CartChange;
// Наши реализации для DDD, которые работают с ActiveRecord
use App\Domain\Cart\GetRequest;
use App\Domain\Cart\ChangeRequest;
use Illuminate\Support\Facades\Log;
class CartController extends Controller
{
public function show(CartGet $request)
{
$cart_request = new GetRequest($request);
$cart = $cart_request->process();
return view('cart.show')
->with('cart', $cart);
}
public function change(CartChange $request)
{
try {
$cart_request = new ChangeRequest($request);
$cart_request->process();
} catch(\Exception $e) {
// Здесь можно разрешить как не стандартные ситуации, так и стандартные.
// Но у нас пока одно.
Log::error('Cart change: ' . $e->getMessage(), $request);
return back()
->with('message', 'Простите, дяденька, засранца.');
}
// Возвращаемся туда от куда добавился товар или изменилось его количество
return back();
}
}
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CartChange extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'product_id' => 'required|exists:products,id',
'action' => 'required|in:append|reduce|clear',
];
}
public function messages()
{
return [
'product_id.required' => 'Не выбран товар.',
'product_id.exists' => 'Товар не доступен.',
'action.required' => 'Не выбрано действие над товаром',
'action.in' => 'Не верное действие над товаром',
];
}
}
Вы хоть раз принимали участие в большой разработке?
Route::get('products', [
'as' => 'products.show',
'uses' => 'ProductController@products',
]);
Route::get('product/{product}/', [
'as' => 'product.show',
'uses' => 'ProductController@product',
])->where('product', '[0-9]*');
Route::get('cart', [
'as' => 'cart.show',
'uses' => 'CartController@show',
]);
Route::post('cart/{product}/{action}', [
'as' => 'cart.change',
'uses' => 'CartController@change',
])->where([
'product' => '[0-9]*',
'action' => 'append|reduce|clear',
]);
<?php namespace App\Http\Controllers;
use App\Product;
use App\Cart;
class ProductController extends Controller
{
const ITEMS_ON_PAGE = 10;
public function products()
{
$products = Product::orderBy('price', 'desc')
->paginate(self::ITEMS_ON_PAGE);
if (!count($products)) {
// Как вариант, решать в отображении что сообщить,
// так как может не быть товаров вообще
abort(404);
}
return view('product.list')
->with('products', $products)
->with('cart', Cart::summary());
}
public function product($productId)
{
$product = Product::find($productId);
if (!$product) {
abort(404);
}
return view('product.item')
->with('product', $product)
->with('cart', Cart::summary());
}
}
<?php namespace App\Http\Controllers;
use App\Cart;
use Illuminate\Support\Facades\Log;
class CartController extends Controller
{
public function show()
{
return view('cart.show')
->with('cart', Cart::all());
}
public function change($productId, $action)
{
try {
// Изменение корзины внутреннее дело самой корзины
Cart::change($productId, $action);
} catch(\Exception $e) {
// Здесь можно разрешить как не стандартные ситуации, так и стандартные.
// Но у нас пока одно.
Log::error('Cart change: ' . $e->getMessage(), [$productId, $action]);
return back()
->with('message', 'Простите, дяденька, засранца.');
}
// Возвращаемся туда от куда добавился товар или изменилось его количество.
// А именно страница товара, списка товаров или корзина.
return back();
}
}
> Читайте вопрос автора.
> Поверх чего? Поверх ActiveRecord? Зачем он тогда нужен во фреймворке, если есть DataMapping?
> Значит Larvel не написан на основе Symfony. А написан с использованием компонентов Symfony. Но, суть Фреймворка именно в предложении концепции программисту как строить своё приложение.
Так что решать проблему целостности данных, вы будете частично в приложении. И так делают 99% разработчиков.
Эра быстрых прототипов закончилась, где то в 2012. Сейчас уже нужны комплексные решения.
За счёт DI объект будет создан сам. Но можно передать нечто ручками. По этому можем тестировать имитируя Database. (Для фриланса и плюшевых проектов TDD в игноре, но всё же актуален.)
Даже для плюшевых проектов порой смена Database нужна. Впрочем у меня был доступ не к БД, а к сайту через запрос xml файла с парсингом айтемов. Сделал в виде репозитория. Далее через пару месяцев сервис отдававший (кривой xml) создал API. Правка репозитория оказалась быстрой.
А ещё через неделю потребовалось для других URL другой сервис, но в те же айтемы. Другой репозиторий. Получаем его через фабрику, которая отдаёт и первый, но зависимо от URL ресурса. Часть которая работала с самими айтемами не изменилась нисколько.