PHP & Databases

Debian Development Machine Setup

Chapter 7 ยท PHP, Composer & Databases

Your web projects run on PHP and MySQL, so devserver needs both. The plan follows the pattern from the last two chapters: check what is there, install the right thing, and keep a clear line between the development machine and the deployment target. There is one trap in the PHP package names that this chapter steers you around, and one surprise about “MySQL” on Debian.

Check First

command -v php composer mariadb mysql sqlite3 dpkg -l 'php*' | grep ^ii apt policy php-cli composer mariadb-server

On a fresh desktop install none of these should be present. apt policy shows what Debian 13 offers: PHP 8.4 (the current build is 8.4.26), Composer 2.8.8, and MariaDB 11.8.6.

Installing PHP Without a Web Server

Here is the trap. The obvious command, sudo apt install php, installs a metapackage that depends on php8.4, and php8.4 in turn requires one of a server-side interpreter: the Apache module, php8.4-fpm or php8.4-cgi. On a typical install, that means apt pulls in Apache and starts a web server you did not ask for. On a development machine where you will use PHP from the command line and its built-in test server, you do not want that.

Do not install the php metapackage here
Installing php is designed for someone setting up a web server. Install php-cli and the extensions you need instead. Read apt's list of packages to be installed before you answer y: if it includes apache2 or libapache2-mod-php8.4, you have installed the wrong thing. Chapter 3's apt policy and apt's own summary are how you catch this.
sudo apt install php-cli php-mbstring php-xml php-curl php-zip php-intl php-mysql php-sqlite3
PackageProvides
php-cliThe php command, without any web server.
php-mbstringMulti-byte string handling, needed for UTF-8 text including Japanese.
php-xmlXML and DOM support. Many frameworks and tools require it.
php-curlHTTP requests to other services.
php-zipReading and writing zip archives. Composer uses it.
php-intlInternationalisation support, needed by some libraries.
php-mysqlThe mysqli and PDO MySQL drivers, for talking to MySQL and MariaDB.
php-sqlite3The SQLite driver, for lightweight local databases.

Verify with the same checks you have used since Chapter 3:

php -v php -m | grep -Ei 'mbstring|curl|mysqli|pdo_mysql|pdo_sqlite|zip|intl' type -a php

php -m lists the loaded modules; the ones you asked for should all appear. type -a php should show a single /usr/bin/php.

Trying it without a web server

PHP has a small development web server built in. Create a folder with an index.php and serve it:

mkdir -p ~/projects/php-demo && cd ~/projects/php-demo echo '<?php echo "Hello from PHP " . PHP_VERSION . " on " . gethostname();' > index.php php -S localhost:8000

Open http://localhost:8000 in a browser. Stop the server with Ctrl+C. The built-in server is only for development: it is single-purpose and not meant for real traffic, which is what the web server on debserver is for. Because it listens on localhost, nothing else on your network can reach it.

Matching the server

Your web server still runs an older Debian, so its PHP will differ from devserver's 8.4. On debserver, run php -v and note the version. When you reinstall it with Trixie, the two will match. Until then, write code that runs on both, and test on the server before you trust it. If you ever genuinely need a different PHP version alongside 8.4, Debian's own repositories cannot supply it, and the usual options are a third-party repository (Linux Package Managers 3 explains how to add one safely and what you are trusting when you do) or a container, as in the Docker for Beginners course.

Composer

Composer is PHP's dependency manager, the equivalent of npm (Chapter 6) and pip (Chapter 5). Debian packages it, and installing it that way is simple and keeps it managed by apt:

sudo apt install composer git unzip composer --version

Composer recommends git and unzip, which is why they are in the command: Composer downloads many packages as archives and works better with both present. You have already seen the pattern for project dependencies:

File / folderWhat it isCommit it?
composer.jsonThe project's list of wanted packages and version ranges.Yes
composer.lockThe exact version of every package installed.Yes
vendor/The installed packages, plus an autoloader.No (add to .gitignore)
cd ~/projects/php-demo composer init --no-interaction --name="me/php-demo" composer require monolog/monolog composer show # Rebuild exactly what composer.lock describes rm -rf vendor composer install

To use an installed package in your code, include Composer's autoloader once at the top of your script: require __DIR__ . '/vendor/autoload.php';. As with npm ci, the important distinction is between composer install and composer update. Install reads composer.lock and installs exactly those versions, so it gives the same result everywhere. Update ignores the lock, fetches the newest versions that composer.json allows, and rewrites the lock. Use install to rebuild, and update only when you mean to upgrade.

Composer needs PHP, and PHP needs the right extensions
If composer require complains that a package needs a PHP extension you have not installed, read the message: it names the extension. Install the matching php-NAME package with apt, then run the command again.

Databases: What “MySQL” Means on Debian

Here is the surprise. Oracle's MySQL is not in Debian's repositories. On Trixie there is no package called mysql-server. Instead there are metapackages called default-mysql-server and default-mysql-client, which install MariaDB (version 11.8.6), a fork of MySQL that is compatible for nearly everything a PHP project does. If you have been running a database on Debian and calling it MySQL, it is very likely MariaDB already. Check on debserver with mysql --version or mariadb --version: MariaDB announces itself in the output.

Same on both machines
Because both devserver and debserver are Debian, they will both use MariaDB from Debian's repositories, so using it on devserver keeps your development database as close as possible to your real one. Differences in the version number will shrink once the server is reinstalled with Trixie.

Installing and securing a local database

sudo apt install mariadb-server systemctl status mariadb mariadb --version # Which addresses is it listening on? Expect 127.0.0.1 only ss -tlnp | grep 3306

The server should listen only on 127.0.0.1, meaning only programs on devserver can connect. Confirm that with ss, rather than assuming it. If a database is listening on 0.0.0.0, it is open to your whole network, and that is not what you want on a development box.

On Debian, MariaDB's administrator account is tied to your operating-system login through a Unix socket, so sudo mariadb gets you in as the administrator without a database password. Older tutorials use mysql as the command name; on current MariaDB, mariadb is the command to use (a mysql name may also work through Debian's compatibility packages, but do not rely on it). Use the administrator account to create a database and a separate, limited user for your projects:

sudo mariadb -- inside the MariaDB prompt: CREATE DATABASE devdb CHARACTER SET utf8mb4; CREATE USER 'dev'@'localhost' IDENTIFIED BY 'choose-a-password'; GRANT ALL PRIVILEGES ON devdb.* TO 'dev'@'localhost'; EXIT;

The dev user can do anything inside devdb and nothing anywhere else, which limits the damage a bug or a stray script can do. Test it from PHP using PDO, the database interface you will use in most projects. Save this as db-test.php and run php db-test.php:

<?php $pdo = new PDO( 'mysql:host=localhost;dbname=devdb;charset=utf8mb4', 'dev', 'choose-a-password', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); $pdo->exec('CREATE TABLE IF NOT EXISTS notes (id INT AUTO_INCREMENT PRIMARY KEY, body VARCHAR(200))'); $pdo->prepare('INSERT INTO notes (body) VALUES (?)')->execute(['hello from devserver']); foreach ($pdo->query('SELECT id, body FROM notes') as $row) { echo $row['id'], ': ', $row['body'], PHP_EOL; }
Keep passwords out of your code and out of Git
The password above is written in the script only so this example is easy to follow. In a real project, put credentials in a configuration file or environment variable that is listed in .gitignore, and never commit them. Notice the query uses a prepared statement (prepare and execute) with a placeholder instead of building SQL from text; that habit is what protects you from SQL injection. The Personal Catalogue: PHP & MySQL course builds a full application on this same pattern.

Starting and stopping it

A database that runs all day uses memory and starts on every boot. With 64 GB of RAM that is no burden, but you may prefer to run it only when needed:

# Stop it now, and stop it starting at boot sudo systemctl disable --now mariadb # Start it when you want it sudo systemctl start mariadb

SQLite: a database with no server

For small tools and quick experiments, you do not need a database server at all. SQLite stores a whole database in a single file:

sudo apt install sqlite3 sqlite3 ~/projects/php-demo/dev.db "CREATE TABLE IF NOT EXISTS t (x TEXT); INSERT INTO t VALUES ('hi'); SELECT * FROM t;"

PHP reaches it through the php-sqlite3 extension you already installed, using PDO with a connection string like sqlite:/path/to/dev.db. Choose SQLite for something small and self-contained, and MariaDB when you need to match the server's database.

Looking ahead
Chapter 9 installs Gitea, which can store its data in SQLite or in a database server. The databases you set up here are what that choice will be made from.

Which Tool for Which Job

You want to…Use
Run PHP scripts and try a site locallyphp-cli and php -S localhost:8000
Add PHP libraries to a projectcomposer require (recorded in composer.lock)
Rebuild a project's libraries exactlycomposer install
Match the server's databaseMariaDB (mariadb-server)
A small, serverless databaseSQLite (sqlite3, php-sqlite3)
A different PHP versionA third-party repository or a container

Hands-On Exercises

Exercise 1

Install PHP the correct way (without the php metapackage). Verify the version and the loaded extensions, confirm no web server was installed, and serve a page with the built-in server. Explain why apt install php would have been the wrong command here.

๐Ÿ“„ View solution
Exercise 2

Create a Composer project, require a package, write a script that uses it via the autoloader, then delete vendor/ and restore it with composer install. Explain the difference between composer install and composer update, and say which files you would commit.

๐Ÿ“„ View solution
Exercise 3

Install MariaDB, prove it listens only on localhost, create a database and a limited user, and write a PDO script that inserts and reads a row. Then record the PHP and database versions on debserver and say what differences you expect from devserver and how you would deal with them.

๐Ÿ“„ View solution

Chapter 7 Quick Reference

  • Debian 13: PHP 8.4 (8.4.26), Composer 2.8.8, MariaDB 11.8.6
  • Do not install the php metapackage on a dev machine; it requires a server interpreter (Apache module, FPM or CGI)
  • Install php-cli php-mbstring php-xml php-curl php-zip php-intl php-mysql php-sqlite3
  • php -v, php -m to check; php -S localhost:8000 is a development-only web server
  • Composer: commit composer.json and composer.lock, not vendor/; composer install uses the lock, composer update rewrites it
  • Oracle MySQL is not packaged in Debian; default-mysql-server installs MariaDB
  • sudo mariadb gives administrator access through your Unix login; use the mariadb command, not mysql
  • Check the database listens on 127.0.0.1 only: ss -tlnp | grep 3306
  • Give each project a database and a limited user; use prepared statements; keep passwords out of Git
  • SQLite (sqlite3, php-sqlite3) is a database in one file, with no server
  • A different PHP version needs a third-party repository or a container