As you've probably guessed, we will deal with product logic in this lesson and move it into the service class.
API Controller
Create API resources and the Controller for Product Model.
php artisan make:resource Api/V1/Vendor/ProductResource
php artisan make:resource Api/V1/Vendor/ProductCollection
php artisan make:controller Api/V1/Vendor/ProductController
app/Http/Controllers/Api/V1/Vendor/ProductController.php
namespace App\Http\Controllers\Api\V1\Vendor; use App\Http\Controllers\Controller;use App\Http\Requests\Vendor\StoreProductRequest;use App\Http\Requests\Vendor\UpdateProductRequest;use App\Http\Resources\Api\V1\Vendor\ProductResource;use App\Models\Product;use Illuminate\Http\Response; class ProductController extends Controller{ public function store(StoreProductRequest $request): ProductResource { $product = Product::create($request->validated()); return new ProductResource($product); } public function show(Product $product): ProductResource { $this->authorize('product.view'); $product->load('category'); return new ProductResource($product); } public function update(UpdateProductRequest $request, Product $product) { $product->update($request->validated()); return (new ProductResource($product)) ->response() ->setStatusCode(Response::HTTP_ACCEPTED); } public function destroy(Product $product) { $this->authorize('product.delete'); $product->delete(); return response()->noContent(); }}
Service
Now create the ProductService
with methods providing basic actions.
app/Services/ProductService.php
namespace App\Services; use App\Models\Product; class ProductService{ public function createProduct(array $attributes): Product { return Product::create($attributes); } public function updateProduct(Product $product, array $attributes): Product { $product->update($attributes); return $product; } public function deleteProduct(Product $product): void { $product->delete(); }}
Update API Controller
Inject ProductService
into the newly created ProductController
.
app/Http/Controllers/Api/V1/Vendor/ProductController.php
use App\Http\Requests\Vendor\UpdateProductRequest;use App\Http\Resources\Api\V1\Vendor\ProductResource;use App\Models\Product;use App\Services\ProductService; use Illuminate\Http\Response; class ProductController extends Controller{ public function __construct(public ProductService $productService) { } // ...
And update methods to consume ProductService
.
app/Http/Controllers/Api/V1/Vendor/ProductController.php
public function store(StoreProductRequest $request): ProductResource{ $product = Product::create($request->validated()); $product = $this->productService->createProduct($request->validated()); return new ProductResource($product);} public function update(UpdateProductRequest $request, Product $product){ $product->update($request->validated()); $product = $this->productService->updateProduct($product, $request->validated()); return (new ProductResource($product)) ->response() ->setStatusCode(Response::HTTP_ACCEPTED);} public function destroy(Product $product){ $this->authorize('product.delete'); $product->delete(); $this->productService->deleteProduct($product); return response()->noContent();}
Update Web Controller
The same procedure applies to ProductController
handling web routes using Inertia.
app/Http/Controllers/Vendor/ProductController.php
use App\Http\Requests\Vendor\UpdateProductRequest;use App\Models\Category;use App\Models\Product;use App\Services\ProductService; use Illuminate\Http\RedirectResponse;use Inertia\Inertia;use Inertia\Response; class ProductController extends Controller{ public function __construct(public ProductService $productService) { } // ...
app/Http/Controllers/Vendor/ProductController.php
public function create(): Response{ $this->authorize('product.create'); return Inertia::render('Vendor/Products/Create', [ 'categories' => Category::all(['id', 'name']), 'category_id' => request('category_id'), ]);} public function store(StoreProductRequest $request): RedirectResponse{ Product::create($request->validated()); $this->productService->createProduct($request->validated()); return to_route('vendor.menu') ->withStatus('Product created successfully.');} public function edit(Product $product){ $this->authorize('product.update'); return Inertia::render('Vendor/Products/Edit', [ 'categories' => Category::get(['id', 'name']), 'product' => $product, ]);} public function update(UpdateProductRequest $request, Product $product){ $product->update($request->validated()); $this->productService->updateProduct($product, $request->validated()); return to_route('vendor.menu') ->withStatus('Product updated successfully.');} public function destroy(Product $product){ $product->delete(); $this->authorize('product.delete'); $this->productService->deleteProduct($product); return to_route('vendor.menu') ->withStatus('Product deleted successfully.');}
Tests
Create a new test file for Product API endpoints.
php artisan make:test Api/ProductTest
tests/Feature/Api/ProductTest.php
namespace Tests\Feature\Api; use App\Enums\RoleName;use App\Models\Product;use App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase;use Tests\TestCase;use Tests\Traits\WithTestingSeeder; class ProductTest extends TestCase{ use RefreshDatabase; use WithTestingSeeder; public function test_vendor_can_store_product(): void { $vendor = $this->getVendorUser(); $category = $vendor->restaurant->categories()->first(); $response = $this ->actingAs($vendor) ->postJson(route('api.vendor.products.store'), [ 'category_id' => $category->id, 'name' => 'Pizza', 'price' => 2.99, ]); $response->assertCreated(); } public function test_vendor_can_view_product(): void { $vendor = $this->getVendorUser(); $product = $vendor->restaurant->categories()->first() ->products()->first(); $response = $this ->actingAs($vendor) ->get(route('api.vendor.products.show', $product)); $response->assertOk(); } public function test_vendor_can_update_product(): void { $vendor = $this->getVendorUser(); $product = $vendor->restaurant->categories()->first() ->products()->first(); $response = $this ->actingAs($vendor) ->putJson(route('api.vendor.products.update', $product), [ 'category_id' => $product->category_id, 'name' => 'Awesome Pizza', 'price' => 9.99, ]); $response->assertAccepted(); }}
And another one for Product web routes.
php artisan make:test Web/ProductTest
tests/Feature/Web/ProductTest.php
namespace Tests\Feature\Web; use Illuminate\Foundation\Testing\RefreshDatabase;use Inertia\Testing\AssertableInertia;use Tests\TestCase;use Tests\Traits\WithTestingSeeder; class ProductTest extends TestCase{ use RefreshDatabase; use WithTestingSeeder; public function test_vendor_can_view_products_create(): void { $vendor = $this->getVendorUser(); $response = $this ->actingAs($vendor) ->get(route('vendor.products.create')); $response->assertInertia(function (AssertableInertia $page) { return $page->component('Vendor/Products/Create') ->has('categories'); }); } public function test_vendor_can_store_product(): void { $vendor = $this->getVendorUser(); $category = $vendor->restaurant->categories()->first(); $response = $this ->actingAs($vendor) ->post(route('vendor.products.store'), [ 'category_id' => $category->id, 'name' => 'Pizza', 'price' => 2.99, ]); $response->assertRedirectToRoute('vendor.menu'); } public function test_vendor_can_view_products_edit(): void { $vendor = $this->getVendorUser(); $product = $vendor->restaurant->categories()->first() ->products()->first(); $response = $this ->actingAs($vendor) ->get(route('vendor.products.edit', $product)); $response->assertInertia(function (AssertableInertia $page) { return $page->component('Vendor/Products/Edit') ->has('categories') ->has('product'); }); } public function test_vendor_can_update_product(): void { $vendor = $this->getVendorUser(); $product = $vendor->restaurant->categories()->first() ->products()->first(); $response = $this ->actingAs($vendor) ->put(route('vendor.products.update', $product), [ 'category_id' => $product->category_id, 'name' => 'Awesome Pizza', 'price' => 9.99, ]); $response->assertRedirectToRoute('vendor.menu'); } public function test_vendor_can_destroy_product(): void { $vendor = $this->getVendorUser(); $product = $vendor->restaurant->categories()->first() ->products()->first(); $response = $this ->actingAs($vendor) ->delete(route('vendor.products.destroy', $product)); $response->assertRedirectToRoute('vendor.menu'); }}
Finally, we can run the tests.
php artisan test --filter CategoryTest
PASS Tests\Feature\Api\ProductTest✓ vendor can store product 1.13s✓ vendor can view product 0.23s✓ vendor can update product 0.21s PASS Tests\Feature\Web\ProductTest✓ vendor can view products create 0.25s✓ vendor can store product 0.23s✓ vendor can view products edit 0.24s✓ vendor can update product 0.24s✓ vendor can destroy product 0.22s Tests: 8 passed (26 assertions)Duration: 2.84s
No comments yet…