diff --git a/php/.tool-versions b/php/.tool-versions new file mode 100644 index 0000000..13bc26d --- /dev/null +++ b/php/.tool-versions @@ -0,0 +1 @@ +php 8 diff --git a/php/README.md b/php/README.md index b467e11..b083d0d 100644 --- a/php/README.md +++ b/php/README.md @@ -1,23 +1,34 @@ # PHP -Skeleton project for PHP with PHPUnit for testing. +Skeleton project for PHP. -## Usage (on MacOS or Linux) -- `./script/setup` to install dependencies using brew - - If this doesn't work install PHP CLI and [Composer](https://getcomposer.org/) manually -- `./script/test` to run the tests -- `./script/start` to run the code +## Prerequisites + +The toolchain this project needs is declared in [`.tool-versions`](./.tool-versions): + +``` +php 8 +``` -*Note: You need [brew](https://brew.sh/) installed for the setup script to work. If you haven't installed brew before it will require at least 5gb of space. Downloading these packages will take a long time, so please ensure you do so well in advance of the interview.* +Install those however you prefer, as long as they end up on your `PATH`. The quickest route is +[mise](https://mise.jdx.dev/), which reads the file for you: -## Usage (on Windows) -- `PowerShell.exe -ExecutionPolicy UnRestricted -File .\script\setup.ps1` to download temp install of php and composer dependencies - - If this doesn't work install [PHP](https://www.php.net/manual/en/install.windows.php) and [Composer](https://getcomposer.org/) manually -- `php composer.phar test` to run the tests -- `php composer.phar start` to run the code +```sh +mise install +``` + +Note that mise compiles PHP from source, so it needs some system libraries to build against. +If `mise install` fails, run [`./script/system-packages`](./script/system-packages) to install +them and try again. You can skip this if you install PHP from a package manager instead +(`apt install php`, `brew install php`), as that comes pre-built. + +## Usage +- `./script/setup` to fetch dependencies +- `./script/test` to run the tests +- `./script/start` to run the app ## Structure - Code located in [`code.php`](./code.php) -- Tests located in [`tests.php`](./tests.php) +- Tests located in [`Tests.php`](./Tests.php) -However, you're free to organise your code as you like. +However, you're free to organise your code as you like. diff --git a/php/composer.json b/php/composer.json index 594c44d..1c8f665 100644 --- a/php/composer.json +++ b/php/composer.json @@ -4,6 +4,7 @@ }, "scripts": { "start": "php code.php", - "test": "phpunit Tests.php" + "test": "vendor/bin/phpunit Tests.php" } } + diff --git a/php/script/setup b/php/script/setup index 296efed..069d192 100755 --- a/php/script/setup +++ b/php/script/setup @@ -1,7 +1,21 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -brew install php -brew install phpunit -brew install composer +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# Bootstrap composer locally if not present +if [[ ! -f composer.phar ]]; then + EXPECTED_SIG="$(curl -fsSL https://composer.github.io/installer.sig)" + php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + ACTUAL_SIG="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" + if [[ "$EXPECTED_SIG" != "$ACTUAL_SIG" ]]; then + echo "Composer installer signature verification failed" >&2 + rm -f composer-setup.php + exit 1 + fi + php composer-setup.php + rm -f composer-setup.php +fi + +php composer.phar install diff --git a/php/script/setup.ps1 b/php/script/setup.ps1 deleted file mode 100644 index 4a2973e..0000000 --- a/php/script/setup.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -$phpZipUrl = "https://windows.php.net/downloads/releases/php-8.1.10-nts-Win32-vs16-x64.zip" -$phpZipFilename = "php-8.1.10-nts-Win32-vs16-x64.zip" -$localPhp = "./php" - -# Download PHP zip -Import-Module BitsTransfer -Start-BitsTransfer -Source $phpZipUrl -Destination $phpZipFilename - -# Extract PHP zip -Expand-Archive -Force -LiteralPath ./$phpZipFilename -DestinationPath $localPhp - -# Add downloaded php to path -$phpPath = (Convert-Path $localPhp) -$env:PATH = $env:PATH + ";" + $phpPath - -php --version - -# Copy php config to local install -cp ./php.ini $localPhp - -# Download Composer install php script -php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" -php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" -php composer-setup.php -php -r "unlink('composer-setup.php');" - -php composer.phar install \ No newline at end of file diff --git a/php/script/start b/php/script/start index 50ebaf2..ee7f767 100755 --- a/php/script/start +++ b/php/script/start @@ -1,5 +1,7 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -composer run-script start +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +php composer.phar run-script start diff --git a/php/script/system-packages b/php/script/system-packages new file mode 100755 index 0000000..ded5c34 --- /dev/null +++ b/php/script/system-packages @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# Installs the system libraries PHP is compiled against. +# +# You only need this if you are *building* PHP from source, which is what mise does. +# If you install PHP from a package manager (apt install php, brew install php) the +# packaging has already taken care of this and you can skip it. +# +# This is separate from `script/setup` because it has to run *before* the toolchain is +# built. By the time `setup` runs, PHP is already compiled and on your PATH. + +set -euo pipefail + +# Debian/Ubuntu package names, used by the CI runner and most devcontainers. +APT_PACKAGES=( + autoconf + bison + build-essential + libbz2-dev + libcurl4-openssl-dev + libfreetype6-dev + libgd-dev + libgmp-dev + libicu-dev + libjpeg-dev + libonig-dev + libpng-dev + libpq-dev + libreadline-dev + libsodium-dev + libsqlite3-dev + libssl-dev + libwebp-dev + libxml2-dev + libxslt1-dev + libzip-dev + pkg-config + re2c + zlib1g-dev +) + +# macOS equivalents. Xcode Command Line Tools provide the compiler toolchain itself. +BREW_PACKAGES=( + autoconf + bison + freetype + gd + gettext + gmp + icu4c + jpeg + libiconv + libpng + libsodium + libxml2 + libzip + oniguruma + pkg-config + re2c + webp +) + +if command -v apt-get >/dev/null 2>&1; then + # Containers often run as root, without sudo installed. + if [[ "$(id -u)" -eq 0 ]]; then + sudo=() + elif command -v sudo >/dev/null 2>&1; then + sudo=(sudo) + else + echo "This script needs root to install packages, but sudo is not available." >&2 + echo "Re-run it as root, or install these packages yourself:" >&2 + printf ' %s\n' "${APT_PACKAGES[*]}" >&2 + exit 1 + fi + + "${sudo[@]}" apt-get update --quiet + "${sudo[@]}" apt-get install --yes --no-install-recommends "${APT_PACKAGES[@]}" + +elif command -v brew >/dev/null 2>&1; then + brew install "${BREW_PACKAGES[@]}" + +else + echo "Could not find apt-get or brew, so this script cannot install packages for you." >&2 + echo "" >&2 + echo "PHP is compiled from source, and needs the development headers for roughly:" >&2 + echo " autoconf bison libcurl libgd libicu libjpeg libpng libsodium libsqlite3" >&2 + echo " libssl libxml2 libzip oniguruma pkg-config re2c zlib" >&2 + echo "" >&2 + echo "Install the equivalents for your system, or install PHP from your package" >&2 + echo "manager instead of building it - see .tool-versions for the version needed." >&2 + exit 1 +fi diff --git a/php/script/test b/php/script/test index c69c1ce..6ffdaec 100755 --- a/php/script/test +++ b/php/script/test @@ -1,5 +1,7 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -composer run-script test +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +php composer.phar run-script test