macOS Setup
On macOS, PHP is commonly installed through Homebrew, a local development app such as Herd, MAMP, or Laravel Valet, or a project container. The key skill is not memorising one installer. It is proving which PHP your shell and project are actually using.
macOS setup bugs usually come from PATH order, multiple PHP versions, missing extensions, or a mismatch between CLI PHP and the PHP used by a local web server.
Homebrew Paths
Homebrew installs into different default locations depending on the Mac:
Apple Silicon: /opt/homebrew
Intel: /usr/local
That affects where php may live:
/opt/homebrew/bin/php
/usr/local/bin/php
Check the active binary:
which php
php -v
If which php points somewhere unexpected, your shell PATH is choosing a different PHP installation.
PATH Order
Your shell searches PATH from left to right. The first matching php wins.
echo $PATH
which -a php
which -a php is useful because it shows all matching PHP binaries your shell can see, not only the first one.
Common shell config files on macOS include:
~/.zshrc
~/.zprofile
~/.bash_profile
After changing PATH, open a new terminal or reload the shell config before checking again.
Configuration And Extensions
Once the active binary is right, inspect its configuration:
php --ini
php -m
php -i | grep "Loaded Configuration File"
Important project extensions often include:
mbstring
intl
pdo
pdo_mysql
curl
zip
gd
Check one extension quickly:
php -r "var_dump(extension_loaded('mbstring'));"
From a PHP script:
<?php
declare(strict_types=1);
foreach (['mbstring', 'intl', 'pdo'] as $extension) {
echo $extension . ': ';
echo extension_loaded($extension) ? 'loaded' : 'missing';
echo PHP_EOL;
}
// Prints:
// mbstring: loaded
// intl: loaded
// pdo: loaded
Composer Checks
After PHP is installed and visible, check the project requirements:
composer check-platform-reqs
If Composer reports the wrong PHP version or a missing extension, compare:
which php
php -v
php -m
composer --version
Composer is only as useful as the PHP runtime it is using. If your editor, terminal, and local web server use different PHP binaries, setup problems will feel random.
Local Web Options
For a quick local request, the built-in server is enough:
php -S 127.0.0.1:8000 -t public
For project-style local development, teams often use Valet, Herd, Docker, or another app that manages DNS, TLS, PHP versions, and services. Those tools are useful, but they do not remove the need to check the active PHP version and extensions.
Services
If PHP-FPM is managed by Homebrew, it may run as a service. The exact service name depends on the installed formula and version, but the professional check is the same: confirm what service is running and which PHP binary/configuration it uses.
Useful commands include:
brew services list
php --ini
php-fpm -v
Do not assume restarting a terminal restarts PHP-FPM. CLI PHP and FPM are different processes.
Recent macOS Does Not Supply PHP
PHP was bundled with older macOS releases, but PHP's official installation documentation states that macOS 12 Monterey and newer do not include it. A new Mac therefore needs a third-party package, a development application, a container image, or a source build.
Do not rely on a tutorial that assumes /usr/bin/php exists. Run the command-resolution checks on the actual machine.
Install Homebrew And PHP As Separate Steps
Homebrew is a package manager; PHP is one formula it can install. Homebrew's supported installer uses /opt/homebrew on Apple Silicon and /usr/local on Intel. It prints post-install instructions that add brew to the shell environment. Follow the generated command rather than pasting a prefix from a different Mac.
Inspect the installation:
uname -m
brew --prefix
brew doctor
uname -m commonly reports arm64 on Apple Silicon and x86_64 on Intel. brew --prefix reports the active Homebrew installation directly, which is safer than inferring it from processor architecture alone.
Before installing PHP, inspect the formula that Homebrew currently provides:
brew info php
Install the current unversioned PHP formula with:
brew install php
The exact stable patch release changes over time. Verify it after installation instead of copying a version from this lesson:
command -v php
php -v
brew list --versions php
If the brew command itself is unavailable after installation, apply the brew shellenv command printed by the installer to the appropriate shell startup file, then open a new terminal.
Understand Versioned Formulae
Homebrew can provide versioned PHP formulae such as php@8.4 while they remain available. Versioned formulae are commonly keg-only, which means Homebrew installs them without automatically replacing the unversioned command links.
Inspect before changing links:
brew info php@8.4
brew list --versions | grep '^php'
A project may prepend a versioned formula's bin and sbin directories to PATH, use a development app's version switcher, or run PHP in a container. Choose one documented activation method. Repeatedly running brew link --overwrite without understanding the existing owner can break another project's assumptions.
After switching versions, open a new terminal and verify all three facts:
command -v php
php -v
php --ini
The later multiple-version lesson covers switching strategy in depth. This lesson's rule is simpler: installation and activation are separate, and the selected path must be visible.
Discover Formula Paths Instead Of Hard-Coding Them
Homebrew formula paths include the Homebrew prefix and the installed PHP branch. Use commands to discover them:
brew --prefix php
brew info php
php --ini
The formula information identifies configuration locations and service notes for the installed release. php --ini remains the authority for the configuration loaded by the CLI process.
Do not copy an Intel path beginning with /usr/local into an Apple Silicon shell profile, or copy an old branch directory into a current installation. Hard-coded paths become stale when architecture, formula, or branch changes.
Keep Shell Startup Files Understandable
The default interactive shell on current macOS is normally Zsh. Login and interactive shells can read different files, including ~/.zprofile and ~/.zshrc. Terminal applications and IDE terminals may start shells differently.
When adding Homebrew or a selected PHP version to PATH:
- use the command recommended by the owning tool;
- add it once, not in several startup files;
- preserve existing path entries;
- avoid duplicate version-manager and Homebrew activation;
- start a new shell and inspect the result.
Use:
printenv PATH
command -v php
which -a php
If one terminal works and another does not, compare the shell, startup mode, and environment rather than reinstalling PHP.
Homebrew Services And PHP-FPM
The Homebrew PHP formula can expose PHP-FPM as a managed service. Inspect service state with:
brew services list
Start or restart a service only when the local web setup actually uses that formula and version. A terminal restart does not restart FPM, and a Homebrew service restart does not change the already-running CLI process.
After a service change, verify the web runtime independently. It may use a branch-specific configuration file and listen on a configured socket or port. Local tools such as Valet can manage those details and should be operated through their documented commands rather than a competing manual service setup.
Do not expose PHP-FPM directly to an untrusted network. It is normally reached through a correctly configured web server or local development tool.
Development Apps And Containers
Herd, MAMP, Valet-based setups, and other macOS development tools can manage PHP versions, DNS names, TLS certificates, web-server configuration, and services. They are reasonable choices when the team standardises on them.
Record whether the tool also modifies shell PATH. Its graphical web runtime and the terminal's php command may be separate until configured. Verify both when a browser and CLI disagree.
A containerized project may not need host PHP for application execution. It can still be useful for editor tools, but mixing host Composer with container tests creates two platforms. Prefer the repository's wrapper commands and verify the runtime inside the container.
Extensions On Homebrew PHP
Many common capabilities are included in or supplied with the Homebrew formula's dependencies, but a project can still require an extension not loaded by default. First prove the absence with the active PHP:
php -m
php --ri mbstring
php --ri extension_name reports extension information and fails when that extension is unavailable. For a PECL or PIE-installed extension, follow current official extension guidance and ensure build tools and configuration target the same PHP formula selected by php.
Never copy a .so file from another PHP branch or Mac architecture. Apple Silicon and Intel binaries are not interchangeable, and PHP extension modules must match the runtime API and build.
macOS-Specific Failure Patterns
Use evidence to recognise common cases:
| Symptom | Likely boundary to inspect |
|---|---|
brew unavailable after installation |
Homebrew shellenv was not added to the active shell |
php still points to an old release |
PATH, a version-manager shim, or another development app wins first |
| CLI extension works but website fails | Web tool or PHP-FPM uses a different PHP/configuration |
brew services shows several PHP services |
Multiple branches are running without clear ownership |
| Intel path appears on Apple Silicon | Migrated shell configuration or Rosetta-based tooling may be active |
| Composer succeeds in one terminal only | Shell startup and selected interpreter differ |
Do not remove /usr/bin entries, disable macOS security controls, or recursively change ownership of /usr/local or /opt/homebrew as a generic fix. Follow the package manager's diagnostics and correct the smallest ownership or path problem supported by the evidence.
macOS Setup Checklist
For a macOS project setup, record:
- Which tool installs PHP.
- Which PHP version the project expects.
- What
which phpreturns. - What
php -vreturns. - What
php --inireturns. - Which extensions are required.
- How to run
composer check-platform-reqs. - How local HTTP requests are served.
This is the information a teammate needs when their machine behaves differently from yours.
What You Should Be Able To Do
After this lesson, you should be able to find the active PHP binary on macOS, understand why Apple Silicon and Intel paths differ, check PATH order, inspect loaded configuration, check extensions, and verify a project with Composer.
For junior PHP work, this matters because macOS setup is common in development teams. The useful skill is proving the environment, not just saying PHP is installed.
Practice
Practice: Produce A Reproducible macOS Setup Record
Create a setup record for a Mac used on a PHP project.
Requirements
Include commands that record:
- CPU architecture and macOS version;
- active Homebrew prefix and formula information;
- selected PHP command, every visible PHP command, and PHP version;
- loaded INI files and extensions;
- Homebrew service state;
- Composer platform requirements.
State whether PHP is owned by Homebrew, a development app, a version manager, or a container. If Homebrew is used, explain why /opt/homebrew and /usr/local are not interchangeable assumptions.
Finish with one CLI smoke test and one local HTTP smoke test, while identifying what each test does not prove.
Show solution
uname -m
sw_vers
brew --prefix
brew info php
brew list --versions | grep '^php'
command -v php
which -a php
php -v
php --ini
php -m
brew services list
composer check-platform-reqs
Record the observed values rather than example paths. Homebrew's supported default is /opt/homebrew on Apple Silicon and /usr/local on Intel, but brew --prefix proves the active installation directly.
State ownership explicitly, for example: PHP is installed by the Homebrew php formula; Homebrew owns updates, extensions supplied by the formula, services, and removal. If a development app or container owns PHP, replace Homebrew-specific operational steps with that tool's documented commands.
Run a CLI file with php hello.php and a local HTTP entry point with:
php -S 127.0.0.1:8000 -t public
The CLI smoke test proves the selected shell runtime can parse and execute the file. The built-in server proves a local HTTP request can reach the chosen document root. Neither proves that a separate Homebrew PHP-FPM, Valet, Herd, MAMP, container, or production runtime uses the same PHP or configuration.
Task: Diagnose A macOS PHP Path Conflict
An Apple Silicon Mac has Homebrew PHP installed, but a new terminal reports an older PHP from another development tool. An IDE terminal reports yet another path.
Create a diagnosis and correction plan.
Requirements
- Inspect architecture, Homebrew prefix, shell type, and
PATHorder. - List all visible
phpcommands and identify the selected one. - Inspect
~/.zprofile,~/.zshrc, and any version-manager or development-app setup that is actually relevant. - Use Homebrew's generated
shellenvapproach rather than hard-coding an Intel prefix. - Explain when to open a new terminal and why IDE terminals may differ.
- Preserve other PHP installations until their owners and dependent projects are known.
Show solution
uname -m
brew --prefix
printf '%s\n' "$SHELL"
printenv PATH
command -v php
which -a php
php -v
On Apple Silicon, the supported Homebrew prefix is normally /opt/homebrew, but use the observed brew --prefix. Apply the brew shellenv command recommended by the Homebrew installer in the startup file appropriate to that shell. Remove duplicate or contradictory activation lines only after identifying whether Homebrew, a version manager, or the development app owns them.
Inspect ~/.zprofile and ~/.zshrc for repeated PATH changes. If a version manager or app supplies PHP, inspect its documented shell integration too. Do not delete its executable merely because Homebrew should win for this project.
Open a new terminal after changing startup configuration, then rerun the evidence commands. Restart or reconfigure the IDE terminal if it launches a different shell mode or inherits the graphical application's older environment.
The correction is complete when the intended terminals report the same selected path, PHP version, and INI configuration required by the project. Other installations can remain when they have known owners and uses.
Task: Verify A macOS Extension In The Correct Runtime
A project requires mbstring, intl, pdo, and the SQLite PDO driver. CLI tests report that intl is missing, while a local development app claims it is enabled.
Requirements
- Record the active PHP path, version, and INI files before checking extensions.
- Use terminal commands and a PHP script to check the required extensions and PDO drivers.
- Compare the CLI runtime with the development app's web runtime rather than assuming they share configuration.
- State how Composer contributes evidence.
- Explain why copying a
.somodule from Intel PHP to Apple Silicon PHP, or from another PHP branch, is unsafe. - Define the evidence required before declaring the setup fixed.
Show solution
command -v php
php -v
php --ini
php -m
php --ri intl
composer check-platform-reqs
Use a repeatable script:
<?php
declare(strict_types=1);
foreach (['mbstring', 'intl', 'pdo'] as $extension) {
echo $extension, ': ', extension_loaded($extension) ? 'loaded' : 'missing', PHP_EOL;
}
$drivers = extension_loaded('pdo') ? PDO::getAvailableDrivers() : [];
echo 'PDO SQLite driver: ', in_array('sqlite', $drivers, true) ? 'available' : 'missing', PHP_EOL;
Run it with the selected CLI PHP. Then execute an equivalent focused report through the development app and compare PHP_BINARY, PHP_VERSION, PHP_SAPI, and php_ini_loaded_file(). The app can truthfully have intl while the shell's separate PHP does not.
Composer verifies declared platform requirements against the PHP environment running Composer; it does not enable extensions. Install or enable intl through the owner of the affected runtime.
A .so module is compiled for a CPU architecture, PHP module API, build mode, and linked libraries. An Intel or different-branch module may not load on the selected Apple Silicon PHP. The setup is fixed only when the intended runtime reports the correct binary and INI, loads every required extension and driver, passes Composer's platform check, and passes the project's relevant tests.