takielias/contract-resolver

A Laravel package that automatically resolves and binds contracts (interfaces) to their implementations, with powerful code generation commands for repositories and services following the Repository and Service pattern.

0.2 2025-07-05 07:28 UTC

This package is auto-updated.

Last update: 2025-07-08 17:25:21 UTC


README

Latest Version Stars Total Downloads Issues Forks GitHub Actions Workflow Status Linkedin

A powerful Laravel package that automatically resolves and binds contracts (interfaces) to their implementations, with comprehensive code generation commands for repositories and services following the Repository and Service pattern.

takielias



โœจ Features

  • ๐Ÿ”„ Auto-binding: Automatically binds interfaces to their implementations
  • ๐ŸŽจ Code Generation: Generate repositories, services, and their interfaces with artisan commands
  • ๐Ÿ“ Convention-based: Follows Laravel naming conventions and directory structure
  • ๐Ÿ”ง Highly Configurable: Customize paths, namespaces, and binding rules
  • โšก Performance Optimized: Efficient file scanning and namespace resolution
  • ๐Ÿงช Well Tested: Comprehensive test suite with high code coverage
  • ๐Ÿ“ฆ Zero Dependencies: Only requires core Laravel components

๐Ÿš€ Installation

You can install the package via composer:

composer require takielias/contract-resolver

The package will automatically register itself via Laravel's package discovery.

Publishing Configuration

Optionally, you can publish the config file:

php artisan vendor:publish --provider="TakiElias\ContractResolver\ContractResolverServiceProvider" --tag="contract-resolver.config"

๐Ÿ“– Usage

Available Commands

Command Description
cr:make Interactive command to create repositories, services, or both
cr:make-repo-interface Generate repository interface
cr:make-repo Generate repository implementation
cr:make-service-interface Generate service interface
cr:make-service Generate service implementation

Auto-binding Contracts

The package automatically scans and binds interfaces to their implementations based on Laravel conventions:

Directory Structure:

app/
โ”œโ”€โ”€ Contracts/
โ”‚   โ”œโ”€โ”€ Repositories/
โ”‚   โ”‚   โ”œโ”€โ”€ UserRepositoryInterface.php
โ”‚   โ”‚   โ””โ”€โ”€ PostRepositoryInterface.php
โ”‚   โ””โ”€โ”€ Services/
โ”‚       โ”œโ”€โ”€ UserServiceInterface.php
โ”‚       โ””โ”€โ”€ PostServiceInterface.php
โ”œโ”€โ”€ Repositories/
โ”‚   โ”œโ”€โ”€ UserRepository.php
โ”‚   โ””โ”€โ”€ PostRepository.php
โ””โ”€โ”€ Services/
    โ”œโ”€โ”€ UserService.php
    โ””โ”€โ”€ PostService.php

Example Interface:

<?php

namespace App\Contracts\Repositories;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function create(array $data): User;
    public function update(int $id, array $data): bool;
    public function delete(int $id): bool;
}

Example Implementation:

<?php

namespace App\Repositories;

use App\Contracts\Repositories\UserRepositoryInterface;
use App\Models\User;

class UserRepository implements UserRepositoryInterface
{
    public function findById(int $id): ?User
    {
        return User::find($id);
    }

    public function create(array $data): User
    {
        return User::create($data);
    }

    public function update(int $id, array $data): bool
    {
        return User::where('id', $id)->update($data);
    }

    public function delete(int $id): bool
    {
        return User::destroy($id);
    }
}

Using in Controllers:

<?php

namespace App\Http\Controllers;

use App\Contracts\Repositories\UserRepositoryInterface;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    public function __construct(
        private UserRepositoryInterface $userRepository
    ) {}

    public function show(int $id)
    {
        $user = $this->userRepository->findById($id);
        
        return response()->json($user);
    }
}

Code Generation Commands

The package provides powerful artisan commands to generate boilerplate code:

Generate Everything

php artisan cr:make

This interactive command will prompt you to:

  1. Choose what to create (Service, Repository, or All)
  2. Enter the name (e.g., "Product")

Generate Repository with Interface

php artisan cr:make-repo-interface Product
php artisan cr:make-repo Product

Generated Interface (app/Contracts/Repositories/ProductRepositoryInterface.php):

<?php

namespace App\Contracts\Repositories;

interface ProductRepositoryInterface
{
    //
}

Generated Repository (app/Repositories/ProductRepository.php):

<?php

namespace App\Repositories;

use App\Contracts\Repositories\ProductRepositoryInterface;

class ProductRepository implements ProductRepositoryInterface
{
    //
}

Generate Service with Interface

php artisan cr:make-service-interface Product
php artisan cr:make-service Product

Generated Service Interface (app/Contracts/Services/ProductServiceInterface.php):

<?php

namespace App\Contracts\Services;

interface ProductServiceInterface
{
    //
}

Generated Service (app/Services/ProductService.php):

<?php

namespace App\Services;

use App\Contracts\Services\ProductServiceInterface;

class ProductService implements ProductServiceInterface
{
    //
}

Advanced Usage

Nested Namespaces

The package supports nested namespaces:

php artisan cr:make-repo Admin\\User

This creates:

  • app/Contracts/Repositories/Admin/UserRepositoryInterface.php
  • app/Repositories/Admin/UserRepository.php

Force Overwrite

Use the --force flag to overwrite existing files:

php artisan cr:make-repo Product --force

Real-world Example

Here's a complete example of a User management system:

1. Generate the files:

php artisan cr:make
# Choose "All" and enter "User"

2. Implement the Repository Interface:

<?php

namespace App\Contracts\Repositories;

use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

interface UserRepositoryInterface
{
    public function paginate(int $perPage = 15): LengthAwarePaginator;
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function create(array $data): User;
    public function update(int $id, array $data): bool;
    public function delete(int $id): bool;
    public function search(string $query): \Illuminate\Database\Eloquent\Collection;
}

3. Implement the Repository:

<?php

namespace App\Repositories;

use App\Contracts\Repositories\UserRepositoryInterface;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

class UserRepository implements UserRepositoryInterface
{
    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return User::paginate($perPage);
    }

    public function findById(int $id): ?User
    {
        return User::find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    public function create(array $data): User
    {
        return User::create($data);
    }

    public function update(int $id, array $data): bool
    {
        return User::where('id', $id)->update($data);
    }

    public function delete(int $id): bool
    {
        return User::destroy($id);
    }

    public function search(string $query): \Illuminate\Database\Eloquent\Collection
    {
        return User::where('name', 'like', "%{$query}%")
            ->orWhere('email', 'like', "%{$query}%")
            ->get();
    }
}

4. Implement the Service Interface:

<?php

namespace App\Contracts\Services;

use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

interface UserServiceInterface
{
    public function getAllUsers(int $perPage = 15): LengthAwarePaginator;
    public function getUserById(int $id): ?User;
    public function createUser(array $data): User;
    public function updateUser(int $id, array $data): bool;
    public function deleteUser(int $id): bool;
    public function searchUsers(string $query): \Illuminate\Database\Eloquent\Collection;
}

5. Implement the Service:

<?php

namespace App\Services;

use App\Contracts\Repositories\UserRepositoryInterface;
use App\Contracts\Services\UserServiceInterface;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Hash;

class UserService implements UserServiceInterface
{
    public function __construct(
        private UserRepositoryInterface $userRepository
    ) {}

    public function getAllUsers(int $perPage = 15): LengthAwarePaginator
    {
        return $this->userRepository->paginate($perPage);
    }

    public function getUserById(int $id): ?User
    {
        return $this->userRepository->findById($id);
    }

    public function createUser(array $data): User
    {
        $data['password'] = Hash::make($data['password']);
        
        return $this->userRepository->create($data);
    }

    public function updateUser(int $id, array $data): bool
    {
        if (isset($data['password'])) {
            $data['password'] = Hash::make($data['password']);
        }

        return $this->userRepository->update($id, $data);
    }

    public function deleteUser(int $id): bool
    {
        return $this->userRepository->delete($id);
    }

    public function searchUsers(string $query): \Illuminate\Database\Eloquent\Collection
    {
        return $this->userRepository->search($query);
    }
}

6. Use in Controller:

<?php

namespace App\Http\Controllers;

use App\Contracts\Services\UserServiceInterface;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\UpdateUserRequest;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function __construct(
        private UserServiceInterface $userService
    ) {}

    public function index(Request $request)
    {
        $users = $this->userService->getAllUsers($request->get('per_page', 15));
        
        return response()->json($users);
    }

    public function show(int $id)
    {
        $user = $this->userService->getUserById($id);
        
        if (!$user) {
            return response()->json(['message' => 'User not found'], 404);
        }

        return response()->json($user);
    }

    public function store(StoreUserRequest $request)
    {
        $user = $this->userService->createUser($request->validated());
        
        return response()->json($user, 201);
    }

    public function update(UpdateUserRequest $request, int $id)
    {
        $updated = $this->userService->updateUser($id, $request->validated());
        
        if (!$updated) {
            return response()->json(['message' => 'User not found'], 404);
        }

        return response()->json(['message' => 'User updated successfully']);
    }

    public function destroy(int $id)
    {
        $deleted = $this->userService->deleteUser($id);
        
        if (!$deleted) {
            return response()->json(['message' => 'User not found'], 404);
        }

        return response()->json(['message' => 'User deleted successfully']);
    }

    public function search(Request $request)
    {
        $query = $request->get('q', '');
        $users = $this->userService->searchUsers($query);
        
        return response()->json($users);
    }
}

โš™๏ธ Configuration

The package works out of the box with sensible defaults. You can customize the behavior by publishing and editing the configuration file:

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Auto-binding Paths
    |--------------------------------------------------------------------------
    |
    | Define the paths where the package should scan for interfaces and
    | their implementations. The package will automatically bind
    | interfaces to their corresponding implementations.
    |
    */
    'paths' => [
        'contracts' => [
            'repositories' => app_path('Contracts/Repositories'),
            'services' => app_path('Contracts/Services'),
        ],
        'implementations' => [
            'repositories' => app_path('Repositories'),
            'services' => app_path('Services'),
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Naming Conventions
    |--------------------------------------------------------------------------
    |
    | Define the naming conventions used for automatic binding.
    | The package will use these patterns to match interfaces
    | with their implementations.
    |
    */
    'naming' => [
        'interface_suffix' => 'Interface',
        'remove_segments' => ['Contracts'],
    ],

    /*
    |--------------------------------------------------------------------------
    | Auto-binding
    |--------------------------------------------------------------------------
    |
    | Enable or disable automatic binding of interfaces to implementations.
    | When enabled, the package will automatically scan and bind interfaces
    | during the service provider registration.
    |
    */
    'auto_binding' => [
        'enabled' => true,
        'cache_bindings' => true,
    ],
];

๐Ÿงช Testing

composer test

Run tests with coverage:

composer test-coverage

๐Ÿ“ˆ Changelog

Please see CHANGELOG for more information on what has changed recently.

๐Ÿค Contributing

Please see CONTRIBUTING for details.

๐Ÿ”’ Security Vulnerabilities

If you discover a security vulnerability within this package, please send an e-mail to Taki Elias via taki.elias@gmail.com. All security vulnerabilities will be promptly addressed.

๐Ÿ† Credits

๐Ÿ“„ License

The MIT License (MIT). Please see License File for more information.

๐ŸŒŸ Show Your Support

If this package helped you, please consider giving it a โญ on GitHub!

๐Ÿ”— Links

Made with โค๏ธ by Taki Elias