Use case
In this section, we will create an api using Valravn
to show how you should use this. let's assume there is a posts
entity and have a BelongsToMany
relationship with categories
entity. the posts
entity is from blog
namespace
and categories
entity is from core
namespace.
Entity
First of all, we should create entity files by entity
command.
1php artisan valravn:entity blog posts
It will create all files we needed. so let's configure the generated files.
Database
To set up related table, edit migration file in database/migration/Blog/{date}_create_posts_table.php
.
1use App\Models\Blog\Post;
2use Illuminate\Database\Migrations\Migration;
3use Illuminate\Database\Schema\Blueprint;
4use Illuminate\Support\Facades\Schema;
5
6return new class extends Migration {
7
8 public function up() {
9 Schema::create( Post::table(), function( Blueprint $table ) {
10 $table->id();
11 $table->string( 'title' );
12 $table->text( 'content' );
13 $table->timestamps();
14 } );
15 }
16
17 public function down() {
18 Schema::dropIfExists( Post::table() );
19 }
20};
As we said, there is a BelongsToMany
relationship with categories
entity, so we should have a pivot table. to create
a pivot migration file, run this command.
1valravn:pivot blog posts core categories
This will create a pivot table in migrations/Blog/
path. if you want some pivot columns, you can define your columns
here.
1// database/migrations/Blog/{data}_create_category_post_table.php
2
3use App\Models\Blog\Post;
4use App\Models\Core\Category;
5use Illuminate\Database\Migrations\Migration;
6use Illuminate\Database\Schema\Blueprint;
7use Illuminate\Support\Facades\Schema;
8
9return new class extends Migration {
10
11 public function up() {
12 Schema::create( 'category_post', function( Blueprint $table ) {
13 $table->foreignIdFor( Post::class )->constrained()->cascadeOnDelete();
14 $table->foreignIdFor( Category::class )->constrained()->cascadeOnDelete();
15
16 $table->primary( [ Post::foreignKey(), Category::foreignKey() ] );
17 } );
18 }
19
20 public function down() {
21 Schema::dropIfExists( 'category_post' );
22 }
23};
There are a factory and seeder classes that you should configure that classes too.
Routing
It's recommended to define your routes in separate files and name the files as your namespaces. so i create a php
file
in routes/app
directory named blog.php
. then, we should register our new file in RouteServiceProvider
.
1// app/Providers/RouteServiceProvider.php
2
3class RouteServiceProvider extends ServiceProvider {
4
5 // ...
6
7 public function boot() {
8 // ...
9 $this->routes( function() {
10 // ...
11
12 Route::prefix( 'api/blog' )
13 ->name( 'blog.' )
14 ->middleware( 'api' )
15 ->group( base_path( 'routes/app/blog.php' ) );
16
17 } );
18 }
19
20 // ...
21
22}
Now, we can register our entity routes.
1// routes/app/blog.php
2
3use Hans\Valravn\Facades\Router;
4
5Router::resource( 'posts', PostCrudController::class )
6 ->withBatchUpdate()
7 ->relations(
8 PostRelationsController::class,
9 function( RelationsRegisterer $relations ) {
10 $relations->belongsToMany( 'categories' );
11 }
12 );
Repository
Next, to handle categories
relationship, add these abstract methods to IPostRepository
.
1// app/Repositories/Contract/Blog/IPostRepository.php
2
3use App\Models\Blog\Post;
4use App\Repositories\Contracts\Repository;
5use Hans\Valravn\DTOs\ManyToManyDto;
6use Illuminate\Contracts\Database\Eloquent\Builder;
7
8abstract class IUserRepository extends Repository {
9
10 abstract public function viewCategories( Post $post ): Builder;
11
12 abstract public function updateCategories( Post $post, ManyToManyDto $dto ): array;
13
14 abstract public function attachCategories( Post $post, ManyToManyDto $dto ): array;
15
16 abstract public function detachCategories( Post $post, ManyToManyDto $dto ): int;
17
18}
Then, implement these methods on PostRepository
.
1// app/Repositories/Blog/PostRepository.php
2
3use App\Models\Core\Category;
4use App\Models\Blog\Post;
5use App\Repositories\Contracts\Blog\IPostRepository;
6use Hans\Valravn\DTOs\ManyToManyDto;
7use Illuminate\Auth\Access\AuthorizationException;
8use Illuminate\Contracts\Database\Eloquent\Builder;
9
10class PostRepository extends IPostRepository {
11
12 protected function getQueryBuilder(): Builder {
13 return Post::query();
14 }
15
16 public function viewCategories( Category $model ): Builder {
17 $this->authorize( $model );
18
19 return $model->categories();
20 }
21
22 public function updateCategories( Category $model, ManyToManyDto $dto ): array {
23 $this->authorizeThisAction( $model, $dto->getData() );
24
25 return $model->categories()->sync( $dto->getData() );
26 }
27
28 public function attachCategories( Category $model, ManyToManyDto $dto ): array {
29 $this->authorizeThisAction( $model, $dto->getData() );
30
31 return $model->categories()->syncWithoutDetaching( $dto->getData() );
32 }
33
34 public function detachCategories( Category $model, ManyToManyDto $dto ): int {
35 $this->authorizeThisAction( $model, $dto->getData() );
36
37 return $model->categories()->detach( $dto->getData() );
38 }
39}
In the end, we should bind IPostRepository
contract to PostRepository
class in the RepositoryServiceProvider
.
1// app/Providers/RepositoryServiceProvider.php
2
3use App\Repositories\Contracts\Blog\IPostRepository;
4use App\Repositories\Blog\PostRepository;
5use Illuminate\Support\ServiceProvider;
6
7class RepositoryServiceProvider extends ServiceProvider {
8
9 public function register() {
10 // ...
11 $this->app->bind( IPostRepository::class, PostRepository::class );
12 }
13
14 public function boot() {
15 //
16 }
17
18}
So, we are done with repositories.
Services
Crud service
In service layer, we should create CRUD actions in PostCrudService
first.
1// app/Services/Blog/Post/PostCrudService.php
2
3use App\Exceptions\Blog\Post\PostException;
4use App\Models\Blog\Post;
5use App\Repositories\Contracts\Blog\IPostRepository;
6use Hans\Valravn\DTOs\BatchUpdateDto;
7use Hans\Valravn\Exceptions\ValravnException;
8use Illuminate\Auth\Access\AuthorizationException;
9use Illuminate\Contracts\Pagination\Paginator;
10use Throwable;
11
12class PostCrudService {
13 private IPostRepository $repository;
14
15 public function __construct() {
16 $this->repository = app( IPostRepository::class );
17 }
18
19 public function all(): Paginator {
20 return $this->repository->all()->applyFilters()->paginate();
21 }
22
23 public function create( array $data ): Post {
24 throw_unless(
25 $model = $this->repository->create( $data ),
26 PostException::failedToCreate()
27 );
28
29 return $model;
30 }
31
32 public function find( int $model ): Post {
33 return $this->repository->find( $model );
34 }
35
36 public function update( Post $model, array $data ): Post {
37 throw_unless(
38 $this->repository->update( $model, $data ),
39 PostException::failedToUpdate()
40 );
41
42 return $model;
43 }
44
45 public function batchUpdate( BatchUpdateDto $dto ): Paginator {
46 if ( $this->repository->batchUpdate( $dto ) ) {
47 return $this->repository->all()
48 ->whereIn( 'id', $dto->getData()->pluck( 'id' ) )
49 ->applyFilters()
50 ->paginate();
51 }
52
53 throw PostException::failedToBatchUpdate();
54 }
55
56 public function delete( Post $model ): Post {
57 throw_unless( $this->repository->delete( $model ), PostException::failedToDelete() );
58
59 return $model;
60 }
61}
Relations service
After Crud service, we should set up PostRelationsService
.
1// app/Services/Blog/Post/PostRelationsService.php
2
3use App\Exceptions\Blog\Post\PostException;
4use App\Models\Blog\Post;
5use App\Repositories\Contracts\Blog\IPostRepository;
6use Hans\Valravn\DTOs\ManyToManyDto;
7use Hans\Valravn\Exceptions\ValravnException;
8use Illuminate\Contracts\Pagination\Paginator;
9
10class PostRelationsService {
11 private IPostRepository $repository;
12
13 public function __construct() {
14 $this->repository = app( IPostRepository::class );
15 }
16
17 public function viewCategories( Post $model ): Paginator {
18 return $this->repository->viewCategories( $model )->applyFilters()->paginate();
19 }
20
21 public function updateCategories( Post $model, ManyToManyDto $dto ): Paginator {
22 if ( $this->repository->updateCategories( $model, $dto ) ) {
23 return $this->viewCategories( $model );
24 }
25
26 throw PostException::failedToUpdateCategories();
27 }
28
29 public function attachCategories( Post $model, ManyToManyDto $dto ): Paginator {
30 if ( $this->repository->attachCategories( $model, $dto ) ) {
31 return $this->viewCategories( $model );
32 }
33
34 throw PostException::failedToAttachCategories();
35 }
36
37 public function detachCategories( Post $model, ManyToManyDto $dto ): Paginator {
38 if ( $this->repository->detachCategories( $model, $dto ) ) {
39 return $this->viewCategories( $model );
40 }
41
42 throw PostException::failedToDetachCategories();
43 }
44
45}
Exceptions
To handle PostRelationsService
needed exceptions, open PostException
class and add these methods.
1// app/Exceptions/Blog/Post/PostException.php
2
3use Hans\Valravn\Exceptions\ValravnException;
4use Symfony\Component\HttpFoundation\Response;
5
6class PostException extends ValravnException {
7
8 public static function failedToUpdateCategories(): ValravnException {
9 return self::make(
10 "Failed to update post's categories!",
11 PostErrorCode::failedToUpdateCategories(),
12 Response::HTTP_INTERNAL_SERVER_ERROR
13 );
14 }
15
16 public static function failedToAttachCategories(): ValravnException {
17 return self::make(
18 "Failed to attach post's categories!",
19 PostErrorCode::failedToAttachCategories(),
20 Response::HTTP_INTERNAL_SERVER_ERROR
21 );
22 }
23
24 public static function failedToDetachCategories(): ValravnException {
25 return self::make(
26 "Failed to detach post's categories!",
27 PostErrorCode::failedToDetachCategories(),
28 Response::HTTP_INTERNAL_SERVER_ERROR
29 );
30 }
31
32}
Then in the PostErrorCode
class, we should add new error codes.
1// app/Exceptions/Blog/Post/PostErrorCode.php
2
3use Hans\Valravn\Exceptions\ErrorCode;
4
5class PostErrorCode extends ErrorCode {
6 protected static string $prefix = 'PECx';
7
8 protected int $FAILED_TO_UPDATE_CATEGORIES = 1; // or failedToUpdateCategories
9 protected int $FAILED_TO_ATTACH_CATEGORIES = 2;
10 protected int $FAILED_TO_DETACH_CATEGORIES = 3;
11}
Controllers
Crud controller
The PostCrudController
created in app/Http/Controllers/V1/Blog/Post/PostCrudController.php
. The created crud
controller has the ability to handling the crud actions. However, if you want to customize the methods, you are free to
make your changes.
Crud requests
In addition, we should set up our PostStoreRequest
and PostUpdateRequest
requests.
1// app/Http/Requests/V1/Blog/Post/PostStoreRequest.php
2
3use Hans\Valravn\Http\Requests\Contracts\ValravnFormRequest;
4
5class PostUpdateRequest extends ValravnFormRequest {
6
7 protected function fields(): array {
8 return [
9 'title' => [ 'required', 'string', 'max:255' ],
10 'content' => [ 'required', 'string' ],
11 ];
12 }
13}
Also, we have these rules for updating request.
1// app/Http/Requests/V1/Blog/Post/PostUpdateRequest.php
2
3use Hans\Valravn\Http\Requests\Contracts\ValravnFormRequest;
4
5class PostUpdateRequest extends ValravnFormRequest {
6
7 protected function fields(): array {
8 return [
9 'title' => [ 'string', 'max:255' ],
10 'content' => [ 'string' ],
11 ];
12 }
13}
Relations controller
As we have a BelongsToMany
relationship, we should create related relation request using below command.
1php artisan valravn:relation blog posts core categories --belongs-to-many
Next, we should add the needed methods to our relations controller.
1// app/Http/Controllers/V1/Blog/Post/PostRelationsController.php
2
3use App\Http\Controllers\Controller;
4use App\Http\Requests\V1\Blog\Post\PostCategoriesRequest;
5use App\Http\Resources\V1\Core\Category\CategoryCollection;
6use App\Models\Blog\Post;
7use App\Models\Core\Category;
8use App\Services\Blog\Post\PostRelationsService;
9use Hans\Valravn\DTOs\ManyToManyDto;
10
11class PostRelationsController extends Controller {
12
13 private PostRelationsService $service;
14
15 public function __construct() {
16 $this->service = app( PostRelationsService::class );
17 }
18
19 public function viewCategories( Post $post ): CategoryCollection {
20 return Category::getResourceCollection( $this->service->viewCategories( $post ) );
21 }
22
23 public function updateCategories( Post $post, PostCategoriesRequest $request ): CategoryCollection {
24 return Category::getResourceCollection(
25 $this->service->updateCategories( $post, ManyToManyDto::make( $request->validated() ) )
26 );
27 }
28
29 public function attachCategories( Post $post, PostCategoriesRequest $request ): CategoryCollection {
30 return Category::getResourceCollection(
31 $this->service->attachCategories( $post, ManyToManyDto::make( $request->validated() ) )
32 );
33 }
34
35 public function detachOwned( Post $post, PostCategoriesRequest $request ): CategoryCollection {
36 return Category::getResourceCollection(
37 $this->service->detachOwned( $post, ManyToManyDto::make( $request->validated() ) )
38 );
39 }
40
41}
Policy
In continue, to register PostPolicy
class to related model, should register it in the AuthServiceProvider
.
1// app/Providers/AuthServiceProvider.php
2
3use App\Models\Blog\Post;
4use App\Policies\Blog\PostPolicy;
5use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
6
7class AuthServiceProvider extends ServiceProvider {
8 /**
9 * The policy mappings for the application.
10 *
11 * @var array<class-string, class-string>
12 */
13 protected $policies = [
14 // ...
15 Post::class => PostPolicy::class,
16 ];
17
18 /**
19 * Register any authentication / authorization services.
20 *
21 * @return void
22 */
23 public function boot() {
24 $this->registerPolicies();
25 }
26
27}