A modern PHP toolchain with Composer

Install a current PHP, serve it locally, and let Composer handle dependencies, PSR-4 autoloading and the tooling around the language.

PHP 8.4 or 8.5, served locally

php -v                                 # aim for 8.4 or 8.5
php -m                                 # extensions actually loaded
php -i | grep "Loaded Configuration"   # which php.ini is in use
php -S localhost:8000 -t public        # built-in server, docroot public/
  • Install through a package manager or a version manager so one machine can host projects on different PHP versions.
  • Pin the version in composer.json with "php": "^8.4"; CI then refuses to run on anything older.
  • Enable mbstring, curl and one PDO driver (pdo_sqlite is enough to practise). Missing extensions fail in ways that do not obviously point at the extension.
  • The built-in server is single-threaded and rewrites nothing beyond PHP files. Treat it as a development convenience only.

Composer and PSR-4 autoloading

composer init --name=acme/blog --require=php:^8.4
composer require monolog/monolog:^3         # production dependency
composer require --dev phpunit/phpunit:^11  # development only
composer install                            # installs exactly what composer.lock pins
composer update monolog/monolog             # deliberately move one package forward
<?php
// public/index.php - the single entry point the web server hands requests to
require __DIR__ . '/../vendor/autoload.php';

use Acme\Blog\Post;   // resolves to src/Post.php through the psr-4 rule

$post = new Post();
echo $post->title();
Key in composer.jsonWhat it does
requireRuntime dependencies; installed in production
require-devTests and analysis tools; skipped by --no-dev
autoload.psr-4Maps a namespace prefix to a directory; the prefix ends in a backslash
scriptsNamed commands such as composer test or composer stan
config.platformPretends to be a given PHP version while resolving packages
💡
Commit composer.lock. It makes every machine and every deploy install identical versions; without it two developers can run the same code against different libraries and see different bugs.

Editor, Xdebug and static analysis

composer require --dev phpstan/phpstan friendsofphp/php-cs-fixer
vendor/bin/phpstan analyse src --level=6
vendor/bin/php-cs-fixer fix --dry-run --diff

# Xdebug is an extension; enable it per command instead of globally
XDEBUG_MODE=coverage php vendor/bin/phpunit
  • Point the editor at vendor/autoload.php so autocomplete and go-to-definition work across packages.
  • Run PHPStan early: level 6 already catches wrong argument types and impossible branches before runtime does.
  • Keep Xdebug off by default. The step debugger slows every request; switch it on for one command when you actually need stepping.

FAQ

Should I install tools globally with composer global?
Only for machine-wide commands you invoke by name. Project libraries always belong in the project, so the lockfile records them and CI installs the same set.
What is the difference between install and update?
install reads composer.lock and changes nothing; update re-resolves constraints and rewrites the lock. Deploys always run install.

Composer, dependencies and coding standards Functions, control flow and type declarations

Last refreshed 2026-09-18.