06: Package Management - npm vs Composer
Package Management: npm vs Composer
Section titled “Package Management: npm vs Composer”Overview
Section titled “Overview”If you’re comfortable with npm, you’ll feel right at home with Composer. Both are package managers that handle dependencies, version constraints, and scripts. This chapter maps your npm knowledge directly to Composer.
Learning Objectives
Section titled “Learning Objectives”By the end of this chapter, you’ll be able to:
- ✅ Understand Composer’s role (PHP’s equivalent to npm)
- ✅ Navigate composer.json vs package.json
- ✅ Install and manage dependencies
- ✅ Use semantic versioning constraints
- ✅ Run scripts via Composer
- ✅ Understand autoloading (PHP’s module system)
- ✅ Publish packages to Packagist
- ✅ Use popular PHP packages
Code Examples
Section titled “Code Examples”📁 View Code Examples on GitHub
This chapter includes package management examples:
- Sample composer.json configurations
- PSR-4 autoloading setup
- Package creation examples
- CLI tool development
Explore the examples:
cd code/php-typescript-developers/chapter-06cat composer.jsoncomposer installQuick Comparison
Section titled “Quick Comparison”| Feature | npm | Composer |
|---|---|---|
| Config File | package.json | composer.json |
| Lock File | package-lock.json | composer.lock |
| Install Command | npm install | composer install |
| Add Package | npm install lodash | composer require vendor/package |
| Dev Dependencies | npm install -D | composer require --dev |
| Registry | npmjs.com | packagist.org |
| Scripts | npm run script | composer run script |
| Global Install | npm install -g | composer global require |
| Dependencies Dir | node_modules/ | vendor/ |
| Autoloading | import/require() | PSR-4 autoloading |
Installation
Section titled “Installation”npm Installation
Section titled “npm Installation”# Install Node.js (includes npm)# macOSbrew install node
# Verifynode --versionnpm --versionComposer Installation
Section titled “Composer Installation”# macOSbrew install composer
# Linux/macOS (alternative)curl -sS https://getcomposer.org/installer | phpsudo mv composer.phar /usr/local/bin/composer
# Windows (Chocolatey)choco install composer
# Verifycomposer --versionConfiguration Files
Section titled “Configuration Files”package.json (npm)
Section titled “package.json (npm)”{ "name": "my-app", "version": "1.0.0", "description": "My TypeScript app", "main": "dist/index.js", "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "test": "jest", "lint": "eslint src/" }, "dependencies": { "express": "^4.18.0", "lodash": "^4.17.21" }, "devDependencies": { "typescript": "^5.0.0", "@types/node": "^20.0.0", "jest": "^29.0.0", "eslint": "^8.0.0" }, "engines": { "node": ">=18.0.0" }}composer.json (Composer)
Section titled “composer.json (Composer)”{ "name": "vendor/my-app", "description": "My PHP app", "type": "project", "require": { "php": "^8.1", "guzzlehttp/guzzle": "^7.5", "monolog/monolog": "^3.0" }, "require-dev": { "phpunit/phpunit": "^10.0", "phpstan/phpstan": "^1.10", "squizlabs/php_codesniffer": "^3.7" }, "autoload": { "psr-4": { "App\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "scripts": { "test": "phpunit", "lint": "phpcs src/", "analyse": "phpstan analyse" }, "config": { "optimize-autoloader": true, "sort-packages": true }}Key Differences:
- Package naming: npm uses simple names (
lodash), Composer requires vendor prefix (vendor/package) - PHP version: Specified as a dependency in
require - Autoloading: Explicit autoload configuration (PSR-4)
- Main entry: No
mainfield (PHP doesn’t bundle like Node.js)
Installing Dependencies
Section titled “Installing Dependencies”npm Commands
Section titled “npm Commands”# Install all dependenciesnpm installnpm i
# Install specific packagenpm install expressnpm install lodash@4.17.21
# Install as dev dependencynpm install -D typescriptnpm install --save-dev jest
# Install globallynpm install -g typescript
# Remove packagenpm uninstall express
# Update packagesnpm updatenpm update express
# Audit securitynpm auditnpm audit fixComposer Commands
Section titled “Composer Commands”# Install all dependenciescomposer install
# Install specific packagecomposer require guzzlehttp/guzzlecomposer require monolog/monolog:^3.0
# Install as dev dependencycomposer require --dev phpunit/phpunitcomposer require --dev phpstan/phpstan
# Install globallycomposer global require laravel/installer
# Remove packagecomposer remove guzzlehttp/guzzle
# Update packagescomposer updatecomposer update monolog/monolog
# Audit securitycomposer audit
# Show installed packagescomposer showcomposer show --treeCheat Sheet:
| npm | Composer |
|---|---|
npm install | composer install |
npm install pkg | composer require pkg |
npm install -D pkg | composer require --dev pkg |
npm uninstall pkg | composer remove pkg |
npm update | composer update |
npm list | composer show |
Version Constraints
Section titled “Version Constraints”Both npm and Composer use semantic versioning (semver).
npm Versioning
Section titled “npm Versioning”{ "dependencies": { "express": "4.18.0", // Exact version "lodash": "^4.17.21", // Compatible: 4.17.21 to <5.0.0 "axios": "~1.4.0", // Approximately: 1.4.0 to <1.5.0 "moment": "*", // Any version (not recommended) "react": ">=18.0.0 <19.0.0" // Range }}Composer Versioning
Section titled “Composer Versioning”{ "require": { "monolog/monolog": "3.0.0", // Exact version "guzzlehttp/guzzle": "^7.5", // Compatible: 7.5.0 to <8.0.0 "symfony/console": "~6.2.0", // Approximately: 6.2.0 to <6.3.0 "psr/log": "*", // Any version (not recommended) "doctrine/orm": ">=2.14 <3.0" // Range }}Identical Behavior! ^ and ~ work the same way.
Semver Quick Reference
Section titled “Semver Quick Reference”| Constraint | npm | Composer | Meaning |
|---|---|---|---|
| Exact | 1.2.3 | 1.2.3 | Exactly 1.2.3 |
| Caret | ^1.2.3 | ^1.2.3 | 1.2.3 to <2.0.0 |
| Tilde | ~1.2.3 | ~1.2.3 | 1.2.3 to <1.3.0 |
| Wildcard | 1.2.* | 1.2.* | Any patch version |
| Range | >=1.2.3 <2.0 | >=1.2.3 <2.0 | Between versions |
Lock Files
Section titled “Lock Files”package-lock.json (npm)
Section titled “package-lock.json (npm)”{ "name": "my-app", "version": "1.0.0", "lockfileVersion": 3, "packages": { "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-...", "dependencies": { "body-parser": "1.20.1" } } }}composer.lock (Composer)
Section titled “composer.lock (Composer)”{ "packages": [ { "name": "guzzlehttp/guzzle", "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", "reference": "abc123..." }, "require": { "php": "^7.2.5 || ^8.0" } } ]}Purpose (Identical):
- ✅ Lock exact versions of all dependencies
- ✅ Ensure consistent installs across environments
- ✅ Committed to version control
- ✅ Generated automatically
Commands:
| npm | Composer |
|---|---|
npm ci (clean install from lock) | composer install (uses lock if present) |
npm install (updates lock) | composer update (updates lock) |
Scripts
Section titled “Scripts”npm Scripts
Section titled “npm Scripts”{ "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "test": "jest", "test:watch": "jest --watch", "lint": "eslint src/", "format": "prettier --write src/", "start": "node dist/index.js", "pretest": "echo 'Running before test'", "posttest": "echo 'Running after test'" }}Run with: npm run dev, npm test
Composer Scripts
Section titled “Composer Scripts”{ "scripts": { "dev": "php -S localhost:8000 -t public/", "test": "phpunit", "test:coverage": "phpunit --coverage-html coverage/", "lint": "phpcs src/", "fix": "phpcbf src/", "analyse": "phpstan analyse", "pre-test": "echo 'Running before test'", "post-test": "echo 'Running after test'" }}Run with: composer run dev, composer test
Script Hooks:
| npm | Composer |
|---|---|
pretest | pre-test |
posttest | post-test |
prepublish | pre-install-cmd |
Autoloading (Module System)
Section titled “Autoloading (Module System)”TypeScript/JavaScript Modules
Section titled “TypeScript/JavaScript Modules”export function greet(name: string): string { return `Hello, ${name}!`;}
export class User { constructor(public name: string) {}}
// src/index.tsimport { greet, User } from './utils/helper';
const user = new User("Alice");console.log(greet(user.name));PHP PSR-4 Autoloading
Section titled “PHP PSR-4 Autoloading”composer.json:
{ "autoload": { "psr-4": { "App\\": "src/" } }}Run composer dump-autoload to generate autoloader.
PHP Files:
<?phpnamespace App\Utils;
function greet(string $name): string { return "Hello, {$name}!";}
class User { public function __construct( public string $name ) {}}<?phprequire __DIR__ . '/vendor/autoload.php';
use App\Utils\User;use function App\Utils\greet;
$user = new User("Alice");echo greet($user->name);PSR-4 Mapping:
| Namespace | Directory |
|---|---|
App\ | src/ |
App\Utils\User | src/Utils/User.php |
App\Http\Controllers\UserController | src/Http/Controllers/UserController.php |
Key Points:
- Namespace must match directory structure
- Class name must match filename
- One class per file
- Always
require __DIR__ . '/vendor/autoload.php';at entry point
Popular Packages
Section titled “Popular Packages”npm Ecosystem
Section titled “npm Ecosystem”npm install express # Web frameworknpm install lodash # Utility librarynpm install axios # HTTP clientnpm install dotenv # Environment variablesnpm install winston # Loggingnpm install joi # Validationnpm install jest # TestingComposer Ecosystem
Section titled “Composer Ecosystem”composer require guzzlehttp/guzzle # HTTP client (like axios)composer require monolog/monolog # Logging (like winston)composer require vlucas/phpdotenv # Environment variables (like dotenv)composer require symfony/console # CLI frameworkcomposer require doctrine/orm # ORMcomposer require twig/twig # Templatingcomposer require phpunit/phpunit --dev # Testing (like jest)Framework Ecosystems:
| TypeScript | PHP |
|---|---|
| Express.js | Laravel, Symfony |
| Nest.js | Laravel (similar architecture) |
| Next.js | Laravel + Inertia.js |
| Prisma | Eloquent (Laravel), Doctrine |
Package Discovery
Section titled “Package Discovery”npm Registry
Section titled “npm Registry”Search: https://www.npmjs.com/
npm search expressnpm view expressnpm view express versionsPackagist (Composer Registry)
Section titled “Packagist (Composer Registry)”Search: https://packagist.org/
composer search guzzlecomposer show guzzlehttp/guzzlecomposer show guzzlehttp/guzzle --allPopular Packages Sites:
- npm: https://www.npmjs.com/
- Packagist: https://packagist.org/
- PHP packages: https://packagist.org/explore/popular
Creating a Package
Section titled “Creating a Package”npm Package
Section titled “npm Package”package.json:
{ "name": "@username/my-package", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": ["dist"], "scripts": { "build": "tsc", "prepublishOnly": "npm run build" }}Publish:
npm loginnpm publish --access publicComposer Package
Section titled “Composer Package”composer.json:
{ "name": "username/my-package", "description": "My awesome PHP package", "type": "library", "license": "MIT", "authors": [ { "name": "Your Name", "email": "you@example.com" } ], "require": { "php": "^8.1" }, "autoload": { "psr-4": { "Username\\MyPackage\\": "src/" } }}Publish:
- Push to GitHub
- Submit to Packagist: https://packagist.org/packages/submit
- Packagist auto-updates from your repository
No manual publish command needed! Packagist pulls from your repo.
Private Packages
Section titled “Private Packages”npm Private Packages
Section titled “npm Private Packages”{ "name": "@myorg/private-package", "private": true}Registry options:
- npm private packages (paid)
- GitHub Packages
- Verdaccio (self-hosted)
Composer Private Packages
Section titled “Composer Private Packages”composer.json:
{ "repositories": [ { "type": "vcs", "url": "https://github.com/myorg/private-package.git" } ], "require": { "myorg/private-package": "^1.0" }}Options:
- Private GitHub/GitLab repos
- Satis (self-hosted Packagist)
- Private Packagist (paid)
Workspace/Monorepo
Section titled “Workspace/Monorepo”npm Workspaces
Section titled “npm Workspaces”package.json:
{ "workspaces": [ "packages/*" ]}Composer (Monorepo)
Section titled “Composer (Monorepo)”composer.json:
{ "repositories": [ { "type": "path", "url": "./packages/*" } ], "require": { "myorg/package-a": "@dev", "myorg/package-b": "@dev" }}Practical Example: Building a CLI Tool
Section titled “Practical Example: Building a CLI Tool”npm/TypeScript CLI
Section titled “npm/TypeScript CLI”package.json:
{ "name": "my-cli", "version": "1.0.0", "bin": { "my-cli": "./dist/cli.js" }, "dependencies": { "commander": "^11.0.0", "chalk": "^5.0.0" }}src/cli.ts:
#!/usr/bin/env nodeimport { Command } from 'commander';import chalk from 'chalk';
const program = new Command();
program .name('my-cli') .description('My awesome CLI tool') .version('1.0.0');
program .command('greet <name>') .description('Greet someone') .action((name: string) => { console.log(chalk.green(`Hello, ${name}!`)); });
program.parse();Install:
npm install -g .my-cli greet AliceComposer/PHP CLI
Section titled “Composer/PHP CLI”composer.json:
{ "name": "vendor/my-cli", "bin": ["bin/my-cli"], "require": { "php": "^8.1", "symfony/console": "^6.3" }, "autoload": { "psr-4": { "App\\": "src/" } }}bin/my-cli:
#!/usr/bin/env php<?phprequire __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;use App\Commands\GreetCommand;
$app = new Application('my-cli', '1.0.0');$app->add(new GreetCommand());$app->run();src/Commands/GreetCommand.php:
<?phpnamespace App\Commands;
use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Input\InputArgument;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends Command { protected function configure(): void { $this->setName('greet') ->setDescription('Greet someone') ->addArgument('name', InputArgument::REQUIRED, 'Name to greet'); }
protected function execute(InputInterface $input, OutputInterface $output): int { $name = $input->getArgument('name'); $output->writeln("<info>Hello, {$name}!</info>"); return Command::SUCCESS; }}Install:
composer global require vendor/my-climy-cli greet AliceHands-On Exercise
Section titled “Hands-On Exercise”Task 1: Initialize a PHP Project
Section titled “Task 1: Initialize a PHP Project”Create a new PHP project with Composer and add dependencies:
# Create project directorymkdir my-php-app && cd my-php-app
# Initialize composer.json interactivelycomposer init
# Add dependenciescomposer require guzzlehttp/guzzlecomposer require --dev phpunit/phpunit
# Configure autoloading# Edit composer.json to add autoload section
# Generate autoloadercomposer dump-autoloadTask 2: Create a Simple Package
Section titled “Task 2: Create a Simple Package”Create a simple math utilities package:
Solution
composer.json:
{ "name": "username/math-utils", "description": "Simple math utilities", "type": "library", "license": "MIT", "autoload": { "psr-4": { "MathUtils\\": "src/" } }, "require": { "php": "^8.1" }, "require-dev": { "phpunit/phpunit": "^10.0" }}src/Calculator.php:
<?phpnamespace MathUtils;
class Calculator { public function add(float $a, float $b): float { return $a + $b; }
public function multiply(float $a, float $b): float { return $a * $b; }}example.php:
<?phprequire 'vendor/autoload.php';
use MathUtils\Calculator;
$calc = new Calculator();echo $calc->add(5, 3) . PHP_EOL; // 8echo $calc->multiply(4, 7) . PHP_EOL; // 28Key Takeaways
Section titled “Key Takeaways”- Composer is PHP’s npm - Nearly identical workflow and concepts
- composer.json = package.json with minor syntax differences (uses
requirenotdependencies) - PSR-4 autoloading replaces import/require statements - maps namespaces to directories
- Packagist.org is PHP’s npm registry (default, no configuration needed)
- composer.lock = package-lock.json for reproducible builds - commit this file!
- Semantic versioning works identically in both (^, ~, *, exact versions)
- Scripts work similarly with minor naming differences (use
composer run script-name) - No build step required (PHP is interpreted, not compiled)
vendor/=node_modules/- add to.gitignore- Global packages installed with
composer global require(likenpm install -g) - Platform requirements ensure PHP version/extensions via
require.platform composer dump-autoloadregenerates autoloader (like rebuilding imports)
Command Cheat Sheet
Section titled “Command Cheat Sheet”| Task | npm | Composer |
|---|---|---|
| Initialize | npm init | composer init |
| Install all | npm install | composer install |
| Add package | npm install pkg | composer require pkg |
| Add dev package | npm install -D pkg | composer require --dev pkg |
| Remove package | npm uninstall pkg | composer remove pkg |
| Update all | npm update | composer update |
| Update one | npm update pkg | composer update pkg |
| List packages | npm list | composer show |
| Run script | npm run script | composer run script |
| Audit security | npm audit | composer audit |
| Clean install | npm ci | composer install |
Next Steps
Section titled “Next Steps”Now that you understand package management, let’s explore testing with PHPUnit.
Next Chapter: 07: Testing: Jest Patterns in PHPUnit
Resources
Section titled “Resources”- Composer Documentation
- Packagist
- PSR-4 Autoloading Standard
- Semantic Versioning
- Composer vs npm Comparison
Questions or feedback? Open an issue on GitHub