Composer Development: Unit Testing and TDD Explained

Dale LantoDale Lanto
4 min read

Composer: Advanced Usage for Dependency Management

Advanced Dependency Management with Composer

Composer is a robust tool for managing PHP dependencies. While most developers are familiar with basic commands like require and install, advanced usage involves controlling dependency versions, managing conflicts, and using custom repositories.

Real-Life Scenario: Managing Dependencies in a Large Project

Imagine you're working on a large project that relies on several third-party libraries (e.g., Laravel, Guzzle, Monolog). Your application is already live, and you need to update a package for a new feature, but you must ensure that other packages that depend on it don’t break.

Example: composer.json for specific version management:

{
    "require": {
        "laravel/framework": "^9.0",
        "guzzlehttp/guzzle": "6.*",
        "monolog/monolog": "~1.0"
    },
    "config": {
        "platform": {
            "php": "8.1.0"
        }
    }
}
  • ^9.0 allows Laravel updates within major version 9, avoiding breaking changes from version 10.

  • 6.* ensures Guzzle only uses version 6.x.x.

  • ~1.0 ensures Monolog remains in version 1.x while allowing minor updates.

Best Practices:
  • Lock dependencies: Use composer.lock to lock versions to prevent conflicts across environments.

  • Use stability flags: When you want to work with unstable packages, use flags like minimum-stability or prefer-stable.

{
    "minimum-stability": "dev",
    "prefer-stable": true
}
  • This configuration allows you to use unstable dependencies but prefers stable releases whenever possible.

Creating Your Own Packages

Creating a reusable Composer package is vital for sharing across multiple projects or with other developers.

Real-Life Scenario: Building a Custom Logger

Assume you’ve written a custom logger for handling application logs in a specific way. Now, you want to package this and use it in several projects.

Steps to create your own package:

  1. Create a composer.json:
{
    "name": "your-vendor/custom-logger",
    "description": "A custom logger for logging app activity",
    "type": "library",
    "autoload": {
        "psr-4": {
            "YourVendor\\CustomLogger\\": "src/"
        }
    },
    "require": {
        "php": ">=8.0"
    }
}
  1. Package structure:
/custom-logger
├── src/
│   └── Logger.php
└── composer.json
  1. Versioning your package: Use Git to manage versions. Tags in Git determine package versions. To release v1.0.0, run:
git tag v1.0.0
git push origin v1.0.0
  1. Publishing your package: You can publish it to Packagist (the default package repository for Composer) or host it privately using Git repositories in your composer.json:
  •     {
            "repositories": [
                {
                    "type": "vcs",
                    "url": "https://github.com/your-vendor/custom-logger"
                }
            ]
        }
    

Unit Testing & TDD: Master PHPUnit for Testing

Mastering PHPUnit for Unit Testing

Unit testing helps ensure individual pieces of your code work as expected. PHPUnit is the most popular testing framework in PHP for writing unit tests.

Real-Life Scenario: Testing a User Model in a Laravel Application

Assume you have a User model that has a method isAdmin(), which returns whether a user is an admin.

class User {
    protected $role;

    public function __construct($role) {
        $this->role = $role;
    }

    public function isAdmin() {
        return $this->role === 'admin';
    }
}

Writing a PHPUnit test:

use PHPUnit\Framework\TestCase;

class UserTest extends TestCase {
    public function testIsAdminReturnsTrueWhenUserIsAdmin() {
        $user = new User('admin');
        $this->assertTrue($user->isAdmin());
    }

    public function testIsAdminReturnsFalseWhenUserIsNotAdmin() {
        $user = new User('user');
        $this->assertFalse($user->isAdmin());
    }
}

Run your test suite:

vendor/bin/phpunit

Test-Driven Development (TDD)

TDD is a software development practice where you write tests before the actual code. The process follows:

  1. Write a test.

  2. Run the test (it should fail).

  3. Write the code to make the test pass.

  4. Refactor the code if necessary.

Real-Life Scenario: Building a Feature with TDD

Imagine you are tasked with implementing a method in the User model that checks if a user has a specific permission.

Step 1: Write the test first

public function testUserHasPermission() {
    $user = new User('admin');
    $this->assertTrue($user->hasPermission('edit-posts'));
}

Step 2: Run the test (it should fail)

Step 3: Write the code to pass the test

class User {
    protected $role;
    protected $permissions = [];

    public function __construct($role) {
        $this->role = $role;
        $this->permissions = ($role === 'admin') ? ['edit-posts', 'delete-posts'] : [];
    }

    public function hasPermission($permission) {
        return in_array($permission, $this->permissions);
    }
}

Step 4: Run the test again

  • The test should now pass. Continue this cycle to develop robust, test-driven code.

Integration Testing

Integration testing focuses on testing multiple components together to ensure they work as expected. In a web app, this could involve testing controllers, models, and views.

Real-Life Scenario: Testing a User Registration Process

Suppose you want to ensure that a user registration works as expected by testing the controller action, database interaction, and view rendering.

Example: Using Laravel’s built-in testing functionality:

public function testUserCanRegister() {
    $response = $this->post('/register', [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertRedirect('/home');
    $this->assertDatabaseHas('users', ['email' => 'john@example.com']);
}

This test verifies that:

  • The registration form works.

  • The user is redirected to /home after registration.

  • The user record is created in the database.


Conclusion

Mastering Composer’s advanced features, creating your own packages, and learning PHPUnit and TDD will significantly improve your PHP development skills. By focusing on versioning, testing, and structured coding, you ensure your applications are reliable, maintainable, and scalable.

0
Subscribe to my newsletter

Read articles from Dale Lanto directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dale Lanto
Dale Lanto

A passionate Full Stack and Backend Web Developer with 7+ years of experience, specializing in PHP, Laravel, and a range of modern web technologies. I enjoy solving complex problems, optimizing systems, and delivering efficient, maintainable code. Check out some of my work at dalelanto.netlify.app or explore my GitHub at github.com/dalelantowork.