There many ways to setup a PHP development environment for Windows, WSL, Docker, Virtual Machine, WAMP and similar pre-packaged installers, etc, all of them works fine. I prefer somewhat old school approach to setup everything manually. Here is how
I will setup this environment for 64 bit Windows. If you are on 32 bit you have to download the appropriate packages for that. I also setup everything for a local User, if you prefer to make it global for everyone you can do that as well (e.g install everything in C:\Wamp
).
Create a directory somewhere called Wamp (or whatever). I will put mine at %OneDrive%\Program\Wamp
. That way I can share my development environment between machines. (My %OneDrive%\Program
directory consist of various Windows programs and tools I share between my machines, thus it makes sense to create a subdirectory there).
Inside the Wamp
directory we create another directory called cmd
, here we will place batch scripts to launch the different services and programs. Now add the cmd
directory, e.g. %OneDrive%\Program\Wamp\cmd
, to your User's (or globally) PATH
environment variable (run rundll32 sysdm.cpl,EditEnvironmentVariables
)
Go to https://windows.php.net/download/ to download PHP for Windows. Easiest setup for PHP+Apache is to load PHP as a Apache module, thus we need to download the thread safe PHP package. This is the easiest way to run PHP+Apache on Windows to avoid running a fastcgi service, the drawback is that you need a separate Apache install per PHP version, but is relatively easy to just copy an existing install.
At the time of this writing I'm downloading PHP 8.3.12 VS16 x64 Thread Safe zip package (php-8.3.12-Win32-vs16-x64.zip
). In order to run the PHP executable we also need Visual C++ Redistributable for Visual Studio 2015-2022 that can be downloaded here https://aka.ms/vs/17/release/vc_redist.x64.exe
Unzip the zip file into the Wamp directory as directory named after your php version, e.g php-8.3.12
(If you are planning to also run non-thread safe PHP executables on your machines you need to introduce a naming convention to separate the two)
Go into the new directory and copy the file php.ini-development
to php.ini
.
Edit php.ini
and update the following
extension_dir = "${OneDrive}\\Program\\Wamp\\php-8.3.12\\ext"
We need to set this path so when PHP is running inside Apache it can still find all of the extensions. When we are here uncomment the pdo driver
extension=pdo_mysql
Now go into your cmd
directory and create a file named similar as the php directory, e.g php-8.3.12.cmd
. Inside the file we add (or wherever you put your Wamp
directory)
@"%OneDrive%\Program\Wamp\php-8.3.12\php.exe" %*
We also add another batch script called php-8.3.cmd
Inside that file we add
@php-8.3.12 %*
Now we have an easy way to run PHP from the command line. If you need to run many different minior versions of the same PHP major version, setup up them separately like above with both the major and the minor release name in the directory name, just change the php-8.3.cmd
to the new version.
We download Apache from Apache Lounge https://www.apachelounge.com/download/ . At the time of this writing I pick Apache 2.4.62-240904 Win64 (httpd-2.4.62-240904-win64-VS17.zip
). What is important is that the Visual Studio version for PHP and Apache match. VS17 is backward compatible thus it will work with php-8.3.12-Win32-vs16-x64
.
Unzip the zip package directly inside our Wamp
directory. Apache Lounge adds an extra directory inside the zip called Apache24
. The unzipped Wamp\ReadMe.txt
describes the Apache Lounge build, rename that to ReadMe-AL.txt and move that into the Apache24
directory (Apache has it's own README.txt, thus the rename). There is also an leftover file called something like -- Win64 VS17 --
, delete that, not needed. Rename the Apache24
directory to httpd-2.4.62-8.3
. We include both the major and the minor release of Apache of in the directory name but also add the PHP major version we configure for it, thus the suffix -8.3
We add batch script to the cmd
directory called httpd-2.4-8.3
and add this (or with your install path)
@"%OneDrive%\Program\Wamp\httpd-2.4.62-8.3\bin\httpd.exe" %*
Now we need to configure Apache to work with our PHP.
Open %OneDrive%\Program\Wamp\httpd-2.4.62-8.3\conf\httpd.conf
Change SVROOT
Define SRVROOT "${OneDrive}/Program/Wamp/httpd-2.4.62-8.3"
Set ServerName
to
ServerName 127.0.0.1:80
(Optional) Set ErrorLog
to
ErrorLog "${TEMP}/httpd_error.log"
(Optional) Set CustomLog
to
CustomLog "${TEMP}/httpd_access.log" common
I don't want global error and access logs to spam my OneDrive to I just put it into the %TEMP%
directory. It can be good to have it but it is rare you need to look into it. We will setup per site specific log later.
Uncomment the line that includes Virtual hosts
Include conf/extra/httpd-vhosts.conf
At the end of the httpd.conf
put
AddType application/x-httpd-php .php
AddHandler application/x-httpd-php .php
LoadModule php_module "${OneDrive}/Program/Wamp/php-8.3.12/php8apache2_4.dll"
PHPiniDir "${OneDrive}/Program/php-8.3.12"
Open %OneDrive%\Program\Wamp\httpd-2.4.62-8.3\conf\extra\httpd-vhosts.conf
Delete the existing <VirtualHost>
declarations, we don't need them. Add instead
<VirtualHost *:80>
Define ROOT "${OneDrive}/Work/myproject"
DocumentRoot "${ROOT}/www"
ServerName myproject.local
DirectoryIndex index.html index.php
Options +Indexes
ErrorLog "${ROOT}/logs/error.log"
CustomLog "${ROOT}/logs/access.log" common
<Directory />
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
On port 80 (the same that Apache Listen
(see httpd.conf
) we define variable ROOT
that points to our project directory "${OneDrive}/Work/myproject" or wherever. Create that directory %OneDrive%\Work\myproject
and inside that create the directories logs
and www
.
Now start our apache by writing in a terminal
httpd-2.4-8.3
You should get Firewall popup from apache asking for allowing to access internet, allow it but you only need to allow for private networks (uncheck public)
Now we need to add our site myproject.local to our Windows hosts file, as administrator do notepad C:\Windows\System32\drivers\etc\hosts
Add to the end of the file and save.
127.0.0.1 myproject.local
Go to https://dev.mysql.com/downloads/mysql/ and download the "Windows (x86, 64-bit), ZIP Archive", lets pick MySQL 9 (mysql-9.0.1-winx64.zip
)
Unzip it into our Wamp
directory, it should exist a containing directory inside zip, I rename that to mysql-9.0.1
.
Create a batch file in our cmd
directory called mysqld-9.0.cmd
(I don't change minor version that often)
@"%OneDrive%\Program\Wamp\mysql-9.0.1\bin\mysqld.exe" %*
Also create a mysql-9.0.cmd
@"%OneDrive%\Program\Wamp\mysql-9.0.1\bin\mysql.exe" %*
Either you accept the default location inside the installation directory, this can become a problem if you are working with large databases and you place the installation on OneDrive, you can set another datadir with the the --datadir="<path>"
command line option or inside the my.ini
, see https://dev.mysql.com/doc/refman/9.0/en/data-directory-initialization.html
Create file my.ini
(or my.cnf
) in %OneDrive%\Program\Wamp\mysql-9.0.1\
and put the following
[client]
port=3390
[mysqld]
port=3390
I picked port 3390 as for MySQL 9.0, if you have other MySQL version setups you can follow the same pattern, e.g. port 3384 for MySQL 8.4. In [mysqld]
group you can add a datadir=...
directive if you want your databases stored elsewhere.
Then Open a terminal and go to %OneDrive%\Program\Wamp\mysql-9.0.1\bin
and run
mysqld --initialize --console
That will setup the datadir and default databases.
Important! to have --console
otherwise it will not print out the root password.
Take note of the root password printed, this password needs to be changed.
Start mysqld
mysqld-9.0
Firewall popup asking for permission, allow for private network only.
Now we will secure our installation and set a new root password , from %OneDrive%\Program\Wamp\mysql-9.0.1\bin
run
mysql_secure_installation.exe
Follow the instructions, because the root password has expired you need to give a new one, I put that to root (or whatever your want) You have also the option to remove test databases, removing anonymous users and limiting remove access for root. Recommended to do all of them.
Now run
mysql-9.0 -u root -p -e "create database myproject"
for creating our project database
Go to %OneDrive%\Work\myproject
, add inside www
directory the file .htaccess
FallbackResource /index.php
Also in www
create the file index.php
with
<?php
declare(strict_types=1);
require __DIR__ . '/../src/bootstrap.php';
Then create %OneDrive%\Work\myproject\src\bootstrap.php
And add
<?php
declare(strict_types=1);
$host = '127.0.0.1';
$port = 3390;
$db = 'myproject';
$user = 'root';
$pass = 'root';
$charset = 'utf8mb4';
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset={$charset}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
];
$pdo = new PDO($dsn, $user, $pass, $options);
$pdo->exec("CREATE TABLE IF NOT EXISTS test (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL
)");
$stmt = $pdo->prepare("INSERT INTO test (title) VALUES (?)");
$stmt->execute([date('c')]);
$stmt = $pdo->prepare("SELECT * FROM test");
$stmt->execute();
?>
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<ul>
<?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?>
<li><?= $row['title'] ?></li>
<?php endwhile; ?>
</ul>
</body>
</html>
<?php
If everything works as it should you can go to http://myproject.local and see a list of dates, one for each request.
Happy coding!
When you need another project
Then you only need to edit %OneDrive%\Program\Wamp\httpd-2.4.62-8.3\conf\extra\httpd-vhosts.conf
copy an existing <VirtualHost>
and modify it and then update the hosts file C:\Windows\System32\drivers\etc\hosts
and add the new project.
Then go thru the setup as before, follow the existing naming conventions and you can handle multiple Apache+PHP configurations at the same time.
How to get sqlite working with Apache.
Open php.ini
and uncomment the sqlite PDO extension
extension=pdo_sqlite
Copy this dll from %OneDrive%\Program\Wamp\php-8.3.12\libsqlite3.dll
to %OneDrive%\Program\Wamp\httpd-2.4.62-8.3\bin
Restart apache and test with the following in your bootstrap.php
<?php
declare(strict_types=1);
$db = __DIR__ . '/../myproject.sqlite';
if (!is_file($db)) {
$fp = fopen($db, 'w');
if ($fp === false) {
throw new RuntimeException("Failed creating file {$db}");
}
fclose($fp);
}
$dsn = "sqlite:{$db}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
];
$pdo = new PDO($dsn, null, null, $options);
$pdo->exec("CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(255) NOT NULL
)");
$stmt = $pdo->prepare("INSERT INTO test (title) VALUES (?)");
$stmt->execute([date('c')]);
$stmt = $pdo->prepare("SELECT * FROM test");
$stmt->execute();
?>
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<ul>
<?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?>
<li><?= $row['title'] ?></li>
<?php endwhile; ?>
</ul>
</body>
</html>
<?php
If everything works as it should you can go to http://myproject.local and see a list of dates, one for each request.
How to get curl working with Apache.
Download cacert.pem from here https://curl.se/docs/caextract.html Save it at %OneDrive%\Program\Wamp\extra\cacert.pem"
Open php.ini
and uncomment the curl extension
extension=curl
Find curl.cainfo
and set to the pem file
curl.cainfo = "${OneDrive}\\Program\\Wamp\\extra\\cacert.pem"
Copy this dll from %OneDrive%\Program\Wamp\php-8.3.12\libssh2.dll
to %OneDrive%\Program\Wamp\httpd-2.4.62-8.3\bin
Restart apache and test with the following in your bootstrap.php
<?php
declare(strict_types=1);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://news.ycombinator.com/',
CURLOPT_RETURNTRANSFER => true,
]);
$output = curl_exec($ch);
if (curl_errno($ch) !== CURLE_OK) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);
echo $output;
If everything works you should see Hacker News at http://myproject.local