Composer Development: Unit Testing and TDD Explained
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
orprefer-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:
- 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"
}
}
- Package structure:
/custom-logger
├── src/
│ └── Logger.php
└── composer.json
- 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
- 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:
Write a test.
Run the test (it should fail).
Write the code to make the test pass.
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.
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.