Skip to main content

2 posts tagged with "Laravel"

View All Tags

Laravel Package Development

Β· 3 min read
Michael Isip
Senior Developer

Spatie provides an excellent package skeleton for Laravel developers who want to develop and distribute their own Laravel packages. Here's a step-by-step guide to using Spatie's package skeleton for Laravel package development:

1. Install the Spatie Package Skeleton​

Spatie's skeleton repository can be used as a starting point for your Laravel package.

  1. Clone the repository:
git clone https://github.com/spatie/package-skeleton-laravel.git my-awesome-package
cd my-awesome-package
  1. Use the configure.php script to set up the package:
php ./configure.php
  1. During the setup, you'll be prompted to:
  • Provide author and vendor details.
  • Name your package (e.g., my-awesome-package).
  • Provide a description for your package.

The script will automatically replace placeholders (like :author_name) with your provided details and rename files accordingly.

3. Local Development​

To use your package in a Laravel project, you can set up a local link using Composer.

  1. Create a Local Laravel App for Testing: Create a Laravel application to test your package:
laravel new laravel-package-test
cd laravel-package-test
  1. Link Your Package:

In your Laravel app's root directory, run:

composer config repositories.my-awesome-package path ../path-to-my-awesome-package
composer require your-vendor/my-awesome-package:@dev

This tells Composer to use the local copy of your package instead of downloading it from Packagist.

"repositories": {
"my-awesome-package": {
"type": "path",
"url": "../my-awesome-package"
}
}
  1. Require the package using Composer:
composer require vendor-name/my-awesome-package:@dev

Replace vendor-name/my-awesome-package with the name defined in your package's composer.json file.

  1. Publish Package Assets

If your package includes configuration files, migrations, or views that need to be published, run:

php artisan vendor:publish --provider="YourVendor\AwesomePackage\AwesomePackageServiceProvider"

This will publish the package's assets (e.g., config files) into the Laravel application for testing.

  1. Verify Installation

Check that your package's ServiceProvider is loaded by looking in the config/app.php file (if auto-discovery is disabled, add the provider manually).

5. Test Your Package Locally​

Modify your package code in the src directory.

Make changes visible in the Laravel app:

If you've cached configuration, routes, or views, clear the cache:

php artisan config:clear
php artisan route:clear
php artisan view:clear

If you're making changes to migrations, roll them back and re-run:

php artisan migrate

Write test routes or controllers in the Laravel app to verify that your package works as expected.

6. Publish the Package​

  1. Push to a GitHub Repository:

    • Create a repository for your package.
    • Push your package code to the repository.
  2. Submit to Packagist:

    • Go to Packagist and log in.
    • Submit your repository to make it installable via Composer.

7. Install Your Package in a Laravel App​

After publishing, you can install your package in any Laravel app using Composer:

composer require your-vendor/my-awesome-package

8. Update Your Package​

  • When making updates, increment the version number in composer.json following Semantic Versioning.
  • Push changes to GitHub to update the package.

By using Spatie's package skeleton, you adhere to best practices and make your package modular, testable, and easy to maintain.

Service Interface Pattern in Laravel

Β· 4 min read
Michael Isip
Senior Developer

Think of your Laravel app like a pizza shop. The controller is like the cashierβ€”it takes the customer’s order. But the cashier doesn’t actually make the pizza, right? That’s the chef’s job.

In the Service Pattern, the service class is like the chefβ€”it does the hard work (business logic) like making the pizza, while the controller just talks to the customer (takes the request and gives back the response). This way, the controller stays clean and focused, and the service handles the detailed stuff.

The Service Interface Pattern​

Now let’s say you have two chefs: one for Italian-style pizza and another for New York-style pizza. The shop owner wants the cashier to be able to call either chef without worrying about their style. So, the owner gives both chefs a checklist (an interface) to follow.

In Laravel, an interface is like that checklist. It makes sure that any service (like the Italian or New York chef) can be swapped in and out, as long as they follow the same rules. This is useful for testing, growing your app, or making changes later without breaking everything.

So, the Service Pattern organizes your code, and the Service Interface Pattern makes it flexible and easier to work with!

Why Use the Service Interface Pattern?​

  1. Abstraction: Decouples the implementation from the code that uses it.
  2. Swappability: You can replace the service implementation without modifying the dependent code.
  3. Testability: Allows easy mocking of services in tests.
  4. Scalability: Facilitates adding new implementations for the same service logic.

Directory Structure​

app/
β”œβ”€β”€ Http/
β”‚ └── Controllers/
β”‚ └── UserController.php
β”œβ”€β”€ Services/
β”‚ β”œβ”€β”€ UserService.php
β”‚ └── Contracts/
β”‚ └── UserServiceInterface.php
└── Providers/
└── AppServiceProvider.php

Steps to Implement the Service Interface Pattern​

1. Create a Service Interface​

Interfaces are usually placed in the App\Contracts or App\Services\Contracts directory.

  • Create the directory for contracts:
mkdir -p app/Services/Contracts
  • Define the service interface:
<?php

namespace App\Services\Contracts;

interface UserServiceInterface
{
public function registerUser(array $data);
}

2. Create a Concrete Implementation of the Interface​

The concrete implementation resides in the app/Services directory.

  1. Create the service class
<?php

namespace App\Services;

use App\Services\Contracts\UserServiceInterface;
use App\Models\User;

class UserService implements UserServiceInterface
{
public function registerUser(array $data)
{
// Business logic for registering a user
$user = User::create($data);

return $user;
}
}

3. Bind the Interface to the Implementation​

Use Laravel’s service container to bind the interface to the concrete class. This ensures the app uses the correct implementation whenever the interface is injected.

  • Add the binding in App\Providers\AppServiceProvider or a dedicated service provider.
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Contracts\UserServiceInterface;
use App\Services\UserService;

class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(UserServiceInterface::class, UserService::class);
}
}

4. Use Dependency Injection in Controllers​

Inject the interface into your controllers instead of the concrete class.

<?php

namespace App\Http\Controllers;

use App\Services\Contracts\UserServiceInterface;
use Illuminate\Http\Request;

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

public function register(RegisterUserRequest $request)
{
$user = $this->userService->registerUser($validated);

return response()->json(['user' => $user], 201);
}
}

5. Testing​

(a) Testing the Real Implementation

Use app()->call() to dynamically resolve the service and call its methods, passing any required parameters. This approach ensures the service is properly resolved and adheres to its interface.

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\User;
use App\Services\UserService;

class UserServiceTest extends TestCase
{
public function test_register_user()
{
// Data for the test
$data = [
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'password' => bcrypt('password'),
];

// Use app()->call() to dynamically call the registerUser method
$user = app()->call([UserServiceInterface::class, 'registerUser'], [
'data' => $data
]);

// Assertions
$this->assertInstanceOf(User::class, $user);
$this->assertEquals('Jane Doe', $user->name);
$this->assertDatabaseHas('users', ['email' => 'jane@example.com']);
}

(b) Testing with a Mock Implementation

To ensure test isolation, you can mock the service implementation and still use app()->call() to interact with the mocked service.

namespace Tests\Unit;

use Tests\TestCase;
use App\Services\Contracts\UserServiceInterface;

class UserServiceTest extends TestCase
{
public function test_register_user_with_mock()
{
// Create a mock for the interface
$mockService = $this->createMock(UserServiceInterface::class);

// Define mock behavior
$mockService->method('registerUser')->willReturn([
'id' => 1,
'name' => 'Mock User',
'email' => 'mock@example.com',
]);

// Bind the mock to the interface
$this->app->instance(UserServiceInterface::class, $mockService);

// Use app()->call() to call the registerUser method
$user = app()->call([UserServiceInterface::class, 'registerUser'], [
'data' => ['name' => 'Mock User', 'email' => 'mock@example.com', 'password' => 'password'],
]);

// Assertions
$this->assertIsArray($user);
$this->assertEquals('Mock User', $user['name']);
$this->assertEquals('mock@example.com', $user['email']);
}
}