Introduction to Composer
Composer is a dependency manager for PHP that has revolutionized how PHP developers work with libraries and packages. Before Composer, managing external code in PHP projects was a manual, error-prone process that involved downloading libraries, including them in your project, and figuring out complex dependency chains yourself.
Composer solves these problems by automating dependency management. It allows you to declare the libraries your project depends on, and it will handle installing and updating them for you.
Packagist] C --> E[Install Dependencies] E --> F[vendor/ Directory] F --> G[Autoloader] G --> A style A fill:#f9f7ff,stroke:#333,stroke-width:2px style B fill:#cce5ff,stroke:#004085,stroke-width:2px style C fill:#B91C1C,stroke:#7F1D1D,stroke-width:2px,color:#fff style D fill:#d4edda,stroke:#155724,stroke-width:2px style E fill:#fff3cd,stroke:#856404,stroke-width:2px style F fill:#d1ecf1,stroke:#0c5460,stroke-width:2px style G fill:#f8d7da,stroke:#721c24,stroke-width:2px
The Problems Composer Solves
- Dependency Hell: Managing complex dependency chains where Library A depends on Library B, which depends on specific versions of Libraries C and D.
- Version Compatibility: Ensuring that all libraries in your project can work together without conflicts.
- Manual Downloads: Eliminating the need to manually download and extract library files.
- Code Organization: Providing a standardized structure for third-party code.
- Autoloading: Automatically loading classes without requiring manual include/require statements.
- Project Portability: Making it easy to share and deploy projects across different environments.
Historical Context
To fully appreciate Composer, it helps to understand how PHP developers managed dependencies before its introduction:
| Era | Dependency Management Approach | Challenges |
|---|---|---|
| Pre-2000s | Copy-paste code snippets | Code duplication, difficult updates, no versioning |
| Early 2000s | Include libraries in project, manual require statements | Difficult to update, path issues, dependency conflicts |
| Mid-2000s | PEAR (PHP Extension and Application Repository) | System-wide installation, permissions issues, limited packages |
| 2012 onwards | Composer | Project-specific dependencies, vast ecosystem, modern standards |
Installing and Setting Up Composer
Installing Composer
Windows Installation
- Download the Installer: Visit getcomposer.org and download the Composer-Setup.exe installer.
-
Run the Installer: Follow the installation wizard, which will:
- Check for PHP installation and configure it
- Set up the PATH environment variable
- Install Composer globally
-
Verify Installation: Open a new command prompt and run
composer --version
macOS/Linux Installation
# Download the installer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
# Verify the installer (optional but recommended)
php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
# Run the installer
php composer-setup.php
# Remove the installer
php -r "unlink('composer-setup.php');"
# Make Composer globally accessible (recommended)
sudo mv composer.phar /usr/local/bin/composer
# Verify installation
composer --version
Note: On some systems, you might need to set read/write permissions for Composer:
sudo chmod +x /usr/local/bin/composer
Global vs. Local Installation
Composer can be installed in two ways:
- Global Installation: Installs Composer as a system-wide command, available from any directory. This is the most common approach and what we've covered above.
-
Local Installation: Installs Composer in the current directory only, useful for environments
where you don't have global installation privileges.
# Local installation - creates composer.phar in current directory php composer-setup.php --install-dir=./ # Using local Composer php composer.phar --version
Updating Composer
It's important to keep Composer updated to benefit from bug fixes and new features:
# Update to the latest version
composer self-update
# Update to a specific version
composer self-update 2.5.8
# Revert to previous version
composer self-update --rollback
Understanding composer.json
The composer.json file is the heart of a Composer-managed project. It defines the project's dependencies,
metadata, and configuration options.
Creating a composer.json File
Using the Initialization Wizard
The easiest way to create a composer.json file is with the initialization wizard:
composer init
This interactive command will ask you questions about your project and create the composer.json file based on your answers:
- Package name (vendor/project format)
- Description
- Author information
- Minimum stability
- Package type
- License
- Dependencies (you can add these later too)
Manual Creation
Alternatively, you can create the file manually:
{
"name": "vendor/project",
"description": "Project description",
"type": "project",
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "your.email@example.com"
}
],
"require": {
"php": "^8.1",
"monolog/monolog": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"Vendor\\Project\\": "src/"
}
}
}
Key Elements of composer.json
Basic Metadata
-
name: The package name in vendor/project format
- vendor: Your GitHub username, company name, or project namespace
- project: The specific project name
- description: A brief description of the project
- type: The package type (project, library, metapackage, composer-plugin)
- license: The license under which the package is distributed
- authors: Information about the package's authors
Dependency Management
-
require: Production dependencies that your project needs to function
- Format:
"package/name": "version constraint"
- Format:
-
require-dev: Development dependencies (testing, debugging, documentation)
- These are not installed in production with
--no-devflag
- These are not installed in production with
- conflict: Packages that conflict with this package
- replace: Packages that this package replaces
- provide: Packages/capabilities this package provides
- suggest: Suggested packages that complement this one
Autoloading Configuration
-
autoload: Autoloading rules for production code
psr-4: PSR-4 autoloading (namespace-based, modern approach)psr-0: PSR-0 autoloading (legacy)classmap: Maps classes to specific filesfiles: Files to always include
- autoload-dev: Autoloading rules for development code
Additional Configuration
- scripts: Custom commands that can be run via Composer
- config: Composer-specific configuration options
- repositories: Additional package repositories
- minimum-stability: Minimum stability requirement for packages
- prefer-stable: Prefer stable packages when available
- extra: Additional metadata for scripts or plugins
Version Constraints
Composer uses Semantic Versioning (SemVer) for managing package versions. Version constraints allow you to specify which versions of a package your project can use.
| Constraint | Meaning | Example |
|---|---|---|
^2.0 |
Compatible with 2.0 (>=2.0 <3.0) | Allows 2.0, 2.1, 2.9, but not 3.0 |
~2.0 |
Approximately 2.0 (>=2.0 <2.1) | Allows 2.0.0, 2.0.9, but not 2.1 |
~2.0.0 |
Approximately 2.0.0 (>=2.0.0 <2.0.1) | Only allows 2.0.0 |
2.0.* |
Any version starting with 2.0 | Allows 2.0.0, 2.0.9, but not 2.1 |
>=2.0 |
Greater than or equal to 2.0 | Allows 2.0, 2.1, 3.0, etc. |
>=2.0,<3.0 |
Between 2.0 and 3.0 | Allows 2.0, 2.1, 2.9, but not 3.0 |
2.0 |
Exactly 2.0 | Only allows 2.0 |
The caret (^) constraint is typically the most useful, as it follows SemVer principles:
- It allows updates to minor and patch versions, which should maintain backward compatibility
- It prevents updates to major versions, which might introduce breaking changes
Managing Dependencies with Composer
Installing Dependencies
Basic Installation
To install the dependencies defined in your composer.json file:
composer install
This command:
- Reads composer.json to determine dependencies
- Resolves the dependency graph
- Downloads the required packages
- Places them in the vendor/ directory
- Creates or updates composer.lock to lock the exact versions used
- Generates the autoloader
Development vs. Production Installation
For production environments, you typically want to:
- Skip development dependencies
- Optimize the autoloader
- Avoid updating the lock file
# Production installation
composer install --no-dev --optimize-autoloader --no-interaction
Installing from composer.lock
If a composer.lock file exists, composer install will use the exact versions specified in that file,
ensuring consistency across development and production environments.
Adding New Dependencies
Adding a Package
# Add a production dependency
composer require vendor/package
# Add a development dependency
composer require --dev vendor/package
# Add with specific version constraint
composer require vendor/package:^2.0
# Add multiple packages
composer require vendor/package1 vendor/package2
These commands will:
- Add the package to composer.json
- Install the package and its dependencies
- Update composer.lock
- Update the autoloader
Finding Packages
You can search for packages on Packagist, the main Composer repository. Packages are identified by their vendor/package name.
Popular PHP packages include:
symfony/console: Command-line interface toolsguzzlehttp/guzzle: HTTP client for making requestsmonolog/monolog: Logging libraryphpunit/phpunit: Testing frameworkdoctrine/orm: Object-relational mappertwig/twig: Template engine
Updating Dependencies
Updating All Packages
# Update all packages to their latest versions within constraints
composer update
# Update all packages including development dependencies
composer update --with-dependencies
# Update while preferring stable versions
composer update --prefer-stable
Updating Specific Packages
# Update a single package
composer update vendor/package
# Update multiple specific packages
composer update vendor/package1 vendor/package2
Checking for Outdated Packages
# List outdated packages
composer outdated
# Get more detailed information
composer outdated --direct
# Include dev dependencies in the check
composer outdated --direct --with-dependencies
Removing Dependencies
# Remove a package
composer remove vendor/package
# Remove a development package
composer remove --dev vendor/package
Understanding composer.lock
The composer.lock file is crucial for dependency management:
- It locks in the exact version of each package used in your project
- It includes a content hash to verify the integrity of the package
- It should be committed to version control to ensure consistent installations
When composer.lock exists:
composer installwill use the exact versions from the lock filecomposer updatewill update to newer versions and update the lock file
This provides a perfect balance between consistency and updates:
- Team members and production servers get identical dependencies via
install - Developers can deliberately update dependencies when needed via
update
Autoloading with Composer
One of Composer's most powerful features is its autoloading capability. Autoloading eliminates the need for numerous include/require statements by automatically loading PHP classes when they're needed.
How Autoloading Works
When Composer installs dependencies, it generates an autoloader in vendor/autoload.php. This autoloader:
- Maps class names to file paths
- Registers the autoloader with PHP's spl_autoload_register()
- Loads classes on demand when they're first referenced
Using the Autoloader
To use the Composer autoloader, simply include it at the top of your entry point file:
require_once __DIR__ . '/vendor/autoload.php';
// Now you can use any class from your dependencies without requiring them manually
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('name');
Autoloading Approaches
Composer supports several autoloading approaches, which you can configure in the composer.json file:
PSR-4 Autoloading (Recommended)
PSR-4 is the modern standard for PHP autoloading. It maps namespaces to directory structures:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
With this configuration:
- The namespace
App\maps to thesrc/directory - A class
App\Controllers\UserControllerwould be insrc/Controllers/UserController.php
Classmap Autoloading
Classmap autoloading scans directories for PHP classes and creates a map of class names to file paths:
{
"autoload": {
"classmap": ["src/", "lib/"]
}
}
This approach:
- Works well with legacy code that doesn't follow PSR-4 standards
- Is less efficient for development as it requires dumping the autoloader when adding new classes
Files Autoloading
Files autoloading simply includes the specified files in every request:
{
"autoload": {
"files": ["src/helpers.php", "src/functions.php"]
}
}
This is useful for:
- Global functions
- Constants
- Procedural code that doesn't use classes
Optimizing the Autoloader
For production environments, you should optimize the autoloader for performance:
# Optimize the autoloader
composer dump-autoload --optimize
# Or during installation
composer install --optimize-autoloader
Optimization creates a classmap for all PSR-4 and PSR-0 mappings, which is faster than dynamically resolving file paths.
Regenerating the Autoloader
If you add new classes or change your autoload configuration, you need to regenerate the autoloader:
composer dump-autoload
Composer Scripts
Composer scripts allow you to define custom commands that can be executed through Composer. They're a powerful way to automate common tasks.
Defining Scripts
Scripts are defined in the "scripts" section of composer.json:
{
"scripts": {
"test": "phpunit",
"cs-check": "phpcs --standard=PSR12 src/",
"cs-fix": "phpcbf --standard=PSR12 src/",
"start": "php -S localhost:8080 -t public/",
"post-install-cmd": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate"
]
}
}
Running Scripts
# Run a script
composer test
# Pass arguments to a script
composer test -- --filter UserTest
# Run multiple scripts
composer cs-check && composer test
Script Events
Composer provides various events that can trigger scripts automatically:
| Event | Description |
|---|---|
| pre-install-cmd | Before dependencies are installed |
| post-install-cmd | After dependencies are installed |
| pre-update-cmd | Before dependencies are updated |
| post-update-cmd | After dependencies are updated |
| post-create-project-cmd | After the create-project command is executed |
| pre-autoload-dump | Before the autoloader is dumped |
| post-autoload-dump | After the autoloader is dumped |
Script Types
Scripts can be defined in several ways:
-
Shell Commands: Simple shell commands
"test": "phpunit" -
PHP Callback: A PHP callable
"test": "@php -r \"echo 'Hello, World!';\"" -
Class Method: A method in a class
"test": "MyVendor\\MyClass::myMethod" -
Script Alias: Reference to another script
"test-all": ["@test", "@cs-check"]
Practical Script Examples
{
"scripts": {
"start": "php -S localhost:8080 -t public/",
"test": "phpunit --colors=always",
"lint": "phpcs",
"lint-fix": "phpcbf",
"db-migrate": "phinx migrate",
"clear-cache": "@php bin/console cache:clear",
"analyze": "phpstan analyse src/ --level=7",
"check": [
"@lint",
"@analyze",
"@test"
],
"post-install-cmd": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php bin/console cache:clear",
"@php bin/console assets:install"
]
}
}
These scripts can dramatically simplify common development tasks and enforce consistency across the team.
Advanced Composer Features
Private Repositories
You can configure Composer to use private packages from repositories other than Packagist:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/company/private-package"
}
],
"require": {
"company/private-package": "dev-main"
}
}
Repository types include:
- vcs: Version control systems (GitHub, GitLab, Bitbucket)
- composer: Other Composer repositories
- package: Single package repositories
- path: Local path repositories
Authentication
For private repositories that require authentication:
# Configure authentication globally
composer config --global github-oauth.github.com your-token
# Or use environment variables
COMPOSER_AUTH='{"github-oauth": {"github.com": "your-token"}}'
Custom Installer Paths
You can customize where certain packages are installed:
{
"require": {
"company/package": "^1.0",
"wordpress-plugin/akismet": "dev-trunk"
},
"extra": {
"installer-paths": {
"wp-content/plugins/{$name}/": ["type:wordpress-plugin"]
}
}
}
Platform Configuration
You can specify which PHP version and extensions to consider for dependency resolution:
{
"config": {
"platform": {
"php": "8.1.0",
"ext-gd": "1"
}
}
}
This is useful for ensuring compatibility with your production environment.
Project Creation from Templates
Composer can create new projects from package templates:
# Create a new Laravel project
composer create-project laravel/laravel my-project
# Create a new Symfony project
composer create-project symfony/skeleton my-project
# Create from a specific version
composer create-project laravel/laravel my-project "9.*"
Composer Best Practices
Version Control Integration
Follow these guidelines for using Composer with version control systems like Git:
-
Include in version control:
- composer.json
- composer.lock
-
Exclude from version control (add to .gitignore):
- vendor/ directory
- composer.phar
# Example .gitignore entries for PHP projects
/vendor/
composer.phar
Dependency Management Practices
- Be specific with version constraints: Use caret (^) for most dependencies to allow compatible updates but prevent breaking changes
-
Regularly update dependencies: Run
composer updateperiodically to get security fixes and improvements -
Audit dependencies: Use
composer auditto check for security vulnerabilities - Minimize dependencies: Only require packages you actually need to reduce complexity and potential vulnerabilities
- Use require-dev appropriately: Keep development tools separate from production dependencies
Performance Optimization
-
Use the optimized autoloader in production:
composer install --optimize-autoloader --no-dev -
Consider using Composer's classmap authoritative mode:
composer install --classmap-authoritative -
Enable the APCu cache if available:
composer install --apcu-autoloader -
Merge autoloaders if you're using multiple Composer-based projects:
composer dump-autoload --optimize
CI/CD Integration
For continuous integration and deployment:
# Example GitHub Actions workflow for PHP with Composer
name: PHP Composer
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run test suite
run: composer run-script test
Troubleshooting Common Composer Issues
Memory Limit Issues
If you encounter memory limit errors during Composer operations:
# Increase memory limit temporarily
php -d memory_limit=-1 /path/to/composer.phar install
# Or configure it in composer.json
{
"config": {
"process-timeout": 1800,
"memory-limit": "2G"
}
}
Dependency Conflicts
When you encounter dependency conflicts:
-
Check what's causing the conflict:
composer why vendor/package -
See what versions are available:
composer show vendor/package -
Try updating only that package:
composer update vendor/package -
As a last resort, you can use
--ignore-platform-reqsbut this can lead to other issues:composer install --ignore-platform-reqs
Timeout Issues
For slow downloads or timeouts:
# Increase process timeout
composer config --global process-timeout 2000
# Or in composer.json
{
"config": {
"process-timeout": 2000
}
}
Debugging Composer
For detailed information about what Composer is doing:
# Verbose output
composer -v install
# Very verbose output
composer -vv install
# Debug output
composer -vvv install
Practice Activity: Building a Project with Composer
In this activity, you'll create a simple PHP project using Composer for dependency management and autoloading.
Step 1: Create the Project Structure
# Create a project directory
mkdir weather-app
cd weather-app
Step 2: Initialize Composer
# Initialize a new Composer project
composer init --name=yourusername/weather-app --description="A simple weather app" --author="Your Name " --type=project --require=guzzlehttp/guzzle:^7.0
This will create a basic composer.json file with GuzzleHTTP as a dependency. GuzzleHTTP is an HTTP client that we'll use to fetch weather data from an API.
Step 3: Add More Dependencies
# Add more dependencies
composer require twig/twig
composer require vlucas/phpdotenv
# Add development dependencies
composer require --dev phpunit/phpunit
Step 4: Configure Autoloading
Edit your composer.json file to add PSR-4 autoloading:
{
"name": "yourusername/weather-app",
"description": "A simple weather app",
"type": "project",
"authors": [
{
"name": "Your Name",
"email": "your.email@example.com"
}
],
"require": {
"guzzlehttp/guzzle": "^7.0",
"twig/twig": "^3.0",
"vlucas/phpdotenv": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
Update the autoloader:
composer dump-autoload
Step 5: Create the Project Files
Create the necessary directories first:
mkdir -p src/Services
mkdir -p src/Controllers
mkdir -p public
mkdir -p templates
mkdir -p tests
Create a .env file for your API key:
# .env
WEATHER_API_KEY=your_api_key_here
# You can get a free API key from https://openweathermap.org/api
Create a weather service:
// src/Services/WeatherService.php
<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class WeatherService
{
private $client;
private $apiKey;
public function __construct()
{
$this->client = new Client([
'base_uri' => 'https://api.openweathermap.org/data/2.5/',
'timeout' => 5.0,
]);
$this->apiKey = $_ENV['WEATHER_API_KEY'];
}
public function getCurrentWeather(string $city): array
{
try {
$response = $this->client->request('GET', 'weather', [
'query' => [
'q' => $city,
'appid' => $this->apiKey,
'units' => 'metric',
],
]);
return json_decode($response->getBody()->getContents(), true);
} catch (GuzzleException $e) {
return ['error' => $e->getMessage()];
}
}
}
Create a weather controller:
// src/Controllers/WeatherController.php
<?php
namespace App\Controllers;
use App\Services\WeatherService;
use Twig\Environment;
class WeatherController
{
private $weatherService;
private $twig;
public function __construct(WeatherService $weatherService, Environment $twig)
{
$this->weatherService = $weatherService;
$this->twig = $twig;
}
public function index(): string
{
return $this->twig->render('index.html.twig', [
'title' => 'Weather App',
]);
}
public function getWeather(string $city): string
{
$weatherData = $this->weatherService->getCurrentWeather($city);
return $this->twig->render('weather.html.twig', [
'title' => "Weather for $city",
'weather' => $weatherData,
]);
}
}
Create template files:
// templates/index.html.twig
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1>{{ title }}</h1>
<form action="weather.php" method="get" class="mt-4">
<div class="mb-3">
<label for="city" class="form-label">Enter City Name</label>
<input type="text" class="form-control" id="city" name="city" required>
</div>
<button type="submit" class="btn btn-primary">Get Weather</button>
</form>
</div>
</body>
</html>
// templates/weather.html.twig
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1>{{ title }}</h1>
{% if weather.error is defined %}
<div class="alert alert-danger">{{ weather.error }}</div>
{% else %}
<div class="card mt-4">
<div class="card-body">
<h2 class="card-title">{{ weather.name }}, {{ weather.sys.country }}</h2>
<p class="card-text">Temperature: {{ weather.main.temp }}°C</p>
<p class="card-text">Feels like: {{ weather.main.feels_like }}°C</p>
<p class="card-text">Weather: {{ weather.weather[0].description }}</p>
<p class="card-text">Humidity: {{ weather.main.humidity }}%</p>
<p class="card-text">Wind: {{ weather.wind.speed }} m/s</p>
</div>
</div>
{% endif %}
<a href="index.php" class="btn btn-primary mt-3">Back to Search</a>
</div>
</body>
</html>
Create the front controller files:
// public/index.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Controllers\WeatherController;
use App\Services\WeatherService;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
// Set up Twig
$loader = new FilesystemLoader(__DIR__ . '/../templates');
$twig = new Environment($loader);
// Set up services and controller
$weatherService = new WeatherService();
$weatherController = new WeatherController($weatherService, $twig);
// Render the index page
echo $weatherController->index();
// public/weather.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Controllers\WeatherController;
use App\Services\WeatherService;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
// Set up Twig
$loader = new FilesystemLoader(__DIR__ . '/../templates');
$twig = new Environment($loader);
// Set up services and controller
$weatherService = new WeatherService();
$weatherController = new WeatherController($weatherService, $twig);
// Get the city from the query string
$city = $_GET['city'] ?? 'London';
// Render the weather page
echo $weatherController->getWeather($city);
Step 6: Create a Test
// tests/Services/WeatherServiceTest.php
<?php
namespace App\Tests\Services;
use App\Services\WeatherService;
use PHPUnit\Framework\TestCase;
class WeatherServiceTest extends TestCase
{
public function testGetCurrentWeatherReturnsArray()
{
// Skip this test if no API key is provided
if (empty($_ENV['WEATHER_API_KEY'])) {
$this->markTestSkipped('No API key provided');
}
$weatherService = new WeatherService();
$result = $weatherService->getCurrentWeather('London');
$this->assertIsArray($result);
$this->assertArrayHasKey('name', $result);
}
}
Step 7: Configure PHPUnit
// phpunit.xml.dist
tests
Step 8: Set Up Composer Scripts
Edit composer.json to add useful scripts:
{
"name": "yourusername/weather-app",
"description": "A simple weather app",
"type": "project",
"authors": [
{
"name": "Your Name",
"email": "your.email@example.com"
}
],
"require": {
"guzzlehttp/guzzle": "^7.0",
"twig/twig": "^3.0",
"vlucas/phpdotenv": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"scripts": {
"start": "php -S localhost:8080 -t public/",
"test": "phpunit",
"post-install-cmd": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]
}
}
Create a .env.example file:
# .env.example
WEATHER_API_KEY=your_api_key_here
Step 9: Run the Application
# Start the built-in PHP server
composer start
# In a separate terminal, run the tests
composer test
Now you can open your browser and navigate to http://localhost:8080 to see your weather app!
Step 10: Version Control
# Initialize Git repository
git init
# Create .gitignore
echo "/vendor/
.env
composer.phar
.phpunit.result.cache" > .gitignore
# Add files and commit
git add .
git commit -m "Initial commit of weather app"
Extension Activities
- Add a 5-day forecast feature using the OpenWeatherMap API
- Implement caching with a library like symfony/cache
- Add unit tests for the WeatherController
- Implement a more advanced routing system using a library like nikic/fast-route
Key Takeaways
- Composer solves the dependency management problem in PHP, making it easy to use and update third-party libraries
- The composer.json file defines your project's dependencies and configuration
- Version constraints follow semantic versioning principles, with the caret (^) constraint being most common
- The composer.lock file ensures consistent installations across environments
- Autoloading eliminates the need for manual require statements, with PSR-4 being the recommended standard
- Composer scripts provide a powerful way to automate common tasks
- For version control, include composer.json and composer.lock but exclude the vendor directory
- Best practices include optimizing the autoloader for production, regularly updating dependencies, and auditing for security