Composer and Packagist
Introduction to Composer
Composer is the dependency manager for PHP, similar to npm for Node.js or pip for Python. It allows you to declare the libraries your project depends on and manages (installs/updates) them for you. Packagist is the main Composer repository, hosting thousands of PHP packages.
Composer revolutionized PHP development by solving dependency management problems and establishing autoloading standards, making modern PHP development more modular and maintainable.
Installation and Setup
Installing Composer
# Download and install Composer globally (Linux/macOS)
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Verify installation
composer --version
# On Windows, download from https://getcomposer.org/Composer-Setup.exe
# Alternative: Install via Homebrew (macOS)
brew install composer
# Update Composer
composer self-update
Project Initialization
# Initialize a new Composer project
composer init
# Create composer.json manually
touch composer.json
# Install dependencies from existing composer.json
composer install
# Update dependencies
composer update
Composer.json Configuration
Basic Structure
{
"name": "your-vendor/your-package",
"description": "A sample PHP package",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "[email protected]",
"homepage": "https://yourwebsite.com",
"role": "Developer"
}
],
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": "^8.0",
"monolog/monolog": "^3.0",
"guzzlehttp/guzzle": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.0",
"squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
"YourVendor\\YourPackage\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"YourVendor\\YourPackage\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"analyse": "phpstan analyse"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
}
}
Advanced Configuration
{
"name": "example/advanced-package",
"description": "Advanced Composer configuration example",
"keywords": ["php", "library", "example"],
"homepage": "https://github.com/example/advanced-package",
"readme": "README.md",
"time": "2024-01-15",
"support": {
"issues": "https://github.com/example/advanced-package/issues",
"source": "https://github.com/example/advanced-package",
"docs": "https://docs.example.com"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/yourname"
},
{
"type": "patreon",
"url": "https://patreon.com/yourname"
}
],
"require": {
"php": "^8.0",
"ext-json": "*",
"ext-mbstring": "*",
"psr/log": "^1.0 || ^2.0 || ^3.0"
},
"suggest": {
"ext-redis": "For Redis cache support",
"ext-memcached": "For Memcached cache support"
},
"provide": {
"psr/cache-implementation": "1.0"
},
"conflict": {
"symfony/cache": "<5.0"
},
"replace": {
"old-vendor/old-package": "*"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/private/repo"
},
{
"type": "path",
"url": "../local-package"
}
],
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}
Dependency Management
Version Constraints
{
"require": {
"vendor/package": "1.0.0",
"vendor/package": ">=1.0",
"vendor/package": ">=1.0,<2.0",
"vendor/package": ">=1.0,<1.1|>=1.2",
"vendor/package": "~1.2",
"vendor/package": "^1.2.3",
"vendor/package": "1.2.*",
"vendor/package": "dev-master",
"vendor/package": "dev-feature-x"
}
}
Managing Dependencies
# Install a new package
composer require monolog/monolog
# Install with specific version
composer require "monolog/monolog:^2.0"
# Install development dependency
composer require --dev phpunit/phpunit
# Remove a package
composer remove monolog/monolog
# Show installed packages
composer show
# Show package info
composer show monolog/monolog
# Show outdated packages
composer outdated
# Update specific package
composer update monolog/monolog
# Update all packages
composer update
# Install without dev dependencies (production)
composer install --no-dev
# Check for security issues
composer audit
Autoloading
PSR-4 Autoloading
{
"autoload": {
"psr-4": {
"App\\": "src/",
"MyVendor\\MyPackage\\": "lib/MyPackage/",
"": "fallback-directory/"
}
}
}
<?php
// Directory structure:
// src/
// Models/
// User.php -> App\Models\User
// Post.php -> App\Models\Post
// Services/
// UserService.php -> App\Services\UserService
// src/Models/User.php
namespace App\Models;
class User
{
private string $name;
private string $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
}
// src/Services/UserService.php
namespace App\Services;
use App\Models\User;
class UserService
{
public function createUser(string $name, string $email): User
{
return new User($name, $email);
}
}
// Usage in your application
require_once 'vendor/autoload.php';
use App\Models\User;
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser('John Doe', '[email protected]');
?>
Alternative Autoloading Methods
{
"autoload": {
"psr-0": {
"Acme": "src/",
"Vendor\\Namespace\\": "src/"
},
"classmap": [
"database",
"legacy/classes"
],
"files": [
"src/helpers.php",
"config/constants.php"
]
}
}
<?php
// src/helpers.php - Functions to be autoloaded
if (!function_exists('app_version')) {
function app_version(): string
{
return '1.0.0';
}
}
if (!function_exists('config')) {
function config(string $key, $default = null)
{
static $config = null;
if ($config === null) {
$config = require __DIR__ . '/../config/app.php';
}
return $config[$key] ?? $default;
}
}
// After running composer dump-autoload, these functions are available globally
echo app_version(); // Outputs: 1.0.0
echo config('app.name'); // Outputs configured app name
?>
Custom Packages Development
Creating a Package Structure
# Create package directory structure
mkdir my-package
cd my-package
# Initialize composer
composer init
# Create standard directories
mkdir -p src tests examples docs
touch README.md CHANGELOG.md LICENSE
# Create basic files
echo "<?php" > src/MyClass.php
echo "<?php" > tests/MyClassTest.php
Example Package Implementation
<?php
// src/StringHelper.php
namespace MyVendor\StringHelper;
class StringHelper
{
public static function camelCase(string $string): string
{
return lcfirst(self::studlyCase($string));
}
public static function studlyCase(string $string): string
{
return str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $string)));
}
public static function kebabCase(string $string): string
{
return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string)));
}
public static function snakeCase(string $string): string
{
return strtolower(preg_replace('/([A-Z])/', '_$1', lcfirst($string)));
}
public static function slugify(string $string): string
{
$string = preg_replace('/[^a-zA-Z0-9\s]/', '', $string);
$string = preg_replace('/\s+/', '-', trim($string));
return strtolower($string);
}
public static function truncate(string $string, int $length, string $suffix = '...'): string
{
if (strlen($string) <= $length) {
return $string;
}
return substr($string, 0, $length - strlen($suffix)) . $suffix;
}
}
// src/Validator.php
namespace MyVendor\StringHelper;
class Validator
{
public static function email(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public static function url(string $url): bool
{
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
public static function ipAddress(string $ip): bool
{
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
public static function json(string $json): bool
{
json_decode($json);
return json_last_error() === JSON_ERROR_NONE;
}
public static function creditCard(string $number): bool
{
$number = preg_replace('/\D/', '', $number);
if (strlen($number) < 13 || strlen($number) > 19) {
return false;
}
return self::luhnCheck($number);
}
private static function luhnCheck(string $number): bool
{
$sum = 0;
$numDigits = strlen($number);
$parity = $numDigits % 2;
for ($i = 0; $i < $numDigits; $i++) {
$digit = (int) $number[$i];
if ($i % 2 === $parity) {
$digit *= 2;
if ($digit > 9) {
$digit -= 9;
}
}
$sum += $digit;
}
return $sum % 10 === 0;
}
}
?>
Package Configuration
{
"name": "myvendor/string-helper",
"description": "A helpful string manipulation and validation library",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "[email protected]"
}
],
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"MyVendor\\StringHelper\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyVendor\\StringHelper\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"test-coverage": "phpunit --coverage-html coverage"
}
}
Package Tests
<?php
// tests/StringHelperTest.php
namespace MyVendor\StringHelper\Tests;
use PHPUnit\Framework\TestCase;
use MyVendor\StringHelper\StringHelper;
class StringHelperTest extends TestCase
{
public function testCamelCase(): void
{
$this->assertEquals('camelCase', StringHelper::camelCase('camel_case'));
$this->assertEquals('camelCase', StringHelper::camelCase('camel-case'));
$this->assertEquals('camelCase', StringHelper::camelCase('Camel Case'));
}
public function testStudlyCase(): void
{
$this->assertEquals('StudlyCase', StringHelper::studlyCase('studly_case'));
$this->assertEquals('StudlyCase', StringHelper::studlyCase('studly-case'));
$this->assertEquals('StudlyCase', StringHelper::studlyCase('studly case'));
}
public function testKebabCase(): void
{
$this->assertEquals('kebab-case', StringHelper::kebabCase('KebabCase'));
$this->assertEquals('kebab-case', StringHelper::kebabCase('kebabCase'));
}
public function testSnakeCase(): void
{
$this->assertEquals('snake_case', StringHelper::snakeCase('SnakeCase'));
$this->assertEquals('snake_case', StringHelper::snakeCase('snakeCase'));
}
public function testSlugify(): void
{
$this->assertEquals('hello-world', StringHelper::slugify('Hello World!'));
$this->assertEquals('test-string', StringHelper::slugify('Test@String#'));
}
public function testTruncate(): void
{
$this->assertEquals('Hello...', StringHelper::truncate('Hello World', 8));
$this->assertEquals('Hello World', StringHelper::truncate('Hello World', 20));
$this->assertEquals('Hello--', StringHelper::truncate('Hello World', 7, '--'));
}
}
// tests/ValidatorTest.php
namespace MyVendor\StringHelper\Tests;
use PHPUnit\Framework\TestCase;
use MyVendor\StringHelper\Validator;
class ValidatorTest extends TestCase
{
public function testEmail(): void
{
$this->assertTrue(Validator::email('[email protected]'));
$this->assertFalse(Validator::email('invalid-email'));
}
public function testUrl(): void
{
$this->assertTrue(Validator::url('https://example.com'));
$this->assertFalse(Validator::url('not-a-url'));
}
public function testIpAddress(): void
{
$this->assertTrue(Validator::ipAddress('192.168.1.1'));
$this->assertTrue(Validator::ipAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334'));
$this->assertFalse(Validator::ipAddress('not-an-ip'));
}
public function testJson(): void
{
$this->assertTrue(Validator::json('{"key": "value"}'));
$this->assertFalse(Validator::json('invalid json'));
}
public function testCreditCard(): void
{
$this->assertTrue(Validator::creditCard('4532015112830366')); // Valid test card
$this->assertFalse(Validator::creditCard('1234567890123456')); // Invalid
}
}
?>
Publishing to Packagist
Preparing for Publication
# Ensure all files are ready
# README.md with documentation
# LICENSE file
# Proper composer.json
# Tests passing
# Tag a release
git tag -a v1.0.0 -m "First release"
git push origin v1.0.0
# Alternatively, use semantic versioning
git tag -a 1.0.0 -m "First stable release"
git push origin 1.0.0
Packagist Submission
- Create Packagist Account: Visit packagist.org and create an account
- Submit Package: Click "Submit" and enter your GitHub/GitLab repository URL
- Set Up Webhook: Configure automatic updates when you push new tags
- Badge Integration: Add Packagist badges to your README
# Your Package Name
[](https://packagist.org/packages/myvendor/string-helper)
[](https://packagist.org/packages/myvendor/string-helper)
[](https://packagist.org/packages/myvendor/string-helper)
## Installation
```bash
composer require myvendor/string-helper
Usage
use MyVendor\StringHelper\StringHelper;
echo StringHelper::camelCase('hello_world'); // helloWorld
echo StringHelper::slugify('Hello World!'); // hello-world
### Package Maintenance
```json
{
"scripts": {
"post-update-cmd": [
"@composer dump-autoload"
],
"post-install-cmd": [
"@composer dump-autoload"
],
"test": [
"phpunit"
],
"cs-check": [
"phpcs --standard=PSR12 src tests"
],
"cs-fix": [
"phpcbf --standard=PSR12 src tests"
],
"analyse": [
"phpstan analyse src --level=5"
],
"check": [
"@cs-check",
"@analyse",
"@test"
]
}
}
Advanced Composer Features
Custom Repositories
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/username/private-repo"
},
{
"type": "path",
"url": "../local-package",
"options": {
"symlink": true
}
},
{
"type": "composer",
"url": "https://private-packagist.example.com"
},
{
"type": "package",
"package": {
"name": "custom/package",
"version": "1.0.0",
"dist": {
"url": "https://example.com/package.zip",
"type": "zip"
}
}
}
]
}
Platform Packages
{
"require": {
"php": "^8.0",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"lib-curl": "*"
},
"config": {
"platform": {
"php": "8.0.0",
"ext-something": "1.0.0"
}
}
}
Composer Scripts
{
"scripts": {
"pre-install-cmd": [
"echo 'Before install'"
],
"post-install-cmd": [
"echo 'After install'",
"@php artisan migrate --force"
],
"serve": [
"Composer\\Config::disableProcessTimeout",
"php -S localhost:8000 -t public"
],
"build": [
"@composer install --no-dev --optimize-autoloader",
"npm run production"
]
},
"scripts-descriptions": {
"serve": "Start development server",
"build": "Build for production"
}
}
Security and Best Practices
Security Audit
# Check for known security vulnerabilities
composer audit
# Update only security fixes
composer update --with-dependencies --dry-run
# Use specific versions for critical packages
{
"require": {
"critical/package": "1.2.3" # Exact version
}
}
Lock Files and CI/CD
# Always commit composer.lock
git add composer.lock
git commit -m "Update dependencies"
# In CI/CD, use install instead of update
composer install --no-dev --optimize-autoloader
# For faster CI builds
composer install --no-dev --no-scripts --no-suggest
Best Practices
{
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"audit": {
"abandoned": "report"
}
},
"prefer-stable": true,
"minimum-stability": "stable"
}
Related Topics
For more PHP package development:
- PHP Object-Oriented Programming - OOP concepts for packages
- PHP Testing - Testing your packages
- PHP Security - Security considerations
- PHP Advanced Features - Advanced PHP features
- PHP Frameworks - Framework integration
Summary
Composer revolutionizes PHP development by providing:
- Dependency Management: Declarative dependency resolution and installation
- Autoloading: PSR-4 autoloading standards and automatic class loading
- Package Discovery: Easy discovery and installation of packages via Packagist
- Version Management: Semantic versioning and constraint resolution
- Development Workflow: Scripts, hooks, and development tools integration
Key benefits:
- Eliminates manual library management
- Ensures consistent development environments
- Enables modular application architecture
- Facilitates package sharing and reuse
- Integrates with modern development workflows
Composer is essential for modern PHP development, enabling developers to build on existing libraries, share code effectively, and maintain clean, modular applications with properly managed dependencies.