Re-learning Modern PHP
Tags: asdf, development, php • Categories: Learning
A while back, I ran into Monica (a sort of fancy address book). I started to use and self-host the project as part of tinkering with my raspberry pi and then wanted to make a couple of improvements to the project. This was a good excuse to explore the PHP world and see how things have evolved since I touched the language over a decade ago.
I’m surprised to say this, but working with PHP was not nearly as painful as I thought it would be. The language and tooling have evolved nicely over the years. If I was forced to use PHP as my daily driver, I wouldn’t be too disappointed!
Note: I originally started writing notes on this >2yr ago and I’m just getting around to posting them now after doing a recent WordPress theme upgrade; some of the information could be out of date.
Language
I haven’t used PHP professionally for a long time at this point (10 years?). I’m impressed with how much the language has evolved. It copied a lot of functionality from other languages but still retains a lot of legacy garbage (the stdlib is a mess). I don’t think they have a choice though—with such a huge percentage of the web running off wordpress & drupal sites which are a pile of poorly-written plugins, you can’t break the web and continue to mitigate security issues. It feels like they’ve made the right tradeoffs.
Here’s some new things I learned about the language:
- Laravel is a really broad framework. It includes a bunch of stuff that Rails does (queuing, ORM, etc) but also includes nice-to-haves like test factories and improved REPL functionality
- PHP8 has a lot of neat language improvements. It’s impressive how such an old language is continuing to improve: keyword arguments, function metadata, JIT, etc while maintaining great backward compatibility.
- PHP has added typing right into the language since PHP7. I’ve become a really big fan of built-in optional typing in languages. It’s one of my biggest gripes with Elixir + Ruby.
- The community has built a really nice static analysis tool and an awesome VS Code extension. The ecosystem-provided PHP tooling has really gotten great.
use
imports the namespace to be accessible in the current file, similar toimport
in python.use
inside a class acts like a mixin- The namespace of a class is defined at the top of the file, not in the class definition.
- Namespaces are separated by a backslash. Weird syntax.
- phpunit is the rspec or jest of PHP-land. Here’s how to run a specific test within a project:
vendor/bin/phpunit --filter='it_stores_a_contact$' tests/Unit/Services/Contact/Contact/CreateContactTest.php
Debugging PHP unit tests
$this->withoutExceptionHandling();
within the test function makes the whole stack trace visible in a Laravel test. Helpful for controller/feature tests.- If you run into
Exception: The Mix manifest does not exist.
build your frontend dependencies. dd()
is ‘dump and die’ and is helpful if you want to halt execution and output a specific variable.- You can use the VS Code php debugger plugin to watch for a debugger session, and then set
xdebug_break();
. - Breaking on unhandled exceptions works even on web requests, just like binding.pry. This is really great and creates a great debugging experience. You can enable it within the VS Code php-debug interface.
xdebug.mode = debug
xdebug.start_with_request = trigger
xdebug.client_port = 9000
Package Management
- Composer == bundler / npm
- Laravel == Ruby on Rails / django
- Artisan == rake
- https://packagist.org == https://rubygems.org
- Pecl == packages with a C extension. Pecl is a very important piece of the PHP ecosystem. PHP does not come installed with a lot of important features (like debugging support) by default, and you need pecl packages to add these in.
- Most of the PHP community seems to use containers for their dev environment. There’s php-build and a plugin for
asdf
but both don’t have the same maturity as rbenv (I ended up having to fix a couple of bugs to get it working!). - If you run into PHP build issues, make sure you don’t have brew packages with multiple versions installed:
brew cleanup
- You can install packages globally:
composer global require 'styleci/cli:^1.1'
Hacking & Debugging
- There’s still no decent built in REPL. There’s
php -a
but it is incredibly basic. Laravel providestinker
, which is built onpsysh
, which solves most of the problem though:composer require --dev laravel/tinker
- There’s no built-in
binding.pry
equivalent. You have to install xdebugpecl install debug
, install a debugger (like VS Code’s php-debug), and then drop axdebug_break();
in your code. - Psysh is really robust.
help
to dig into the various commands available.ls -al $var
is a great way to inspect what’s going on in an object
Installing PHP
The project I was tinkering with recommended a vagrant installation. I’m not a fan of containerized local development. If you are running an ubuntu container, in my experience, it’s a huge RAM suck and slowns down your machine (and feedback loop).
I assumed it would be easy nowadays to install PHP. This is not the case, PHP installation is like a 20yr time warp. When installing elixir with asdf
pre-compiled binaries are installed and you are done. Takes less than 20s. Sames goes with node. Python + ruby installations are always a little more complicated but work just fine. This is not the case with PHP.
There are pre-compiled php binaries you can install via homebrew. If you go that route, you don’t get the automatic version switching available with asdf
and you have to pick just the right pre-compiled php version with the flags required for your project (which is very challenging).
I tried to go the asdf
route but ran into a bunch of problems:
- The PHP plugin did not include all of the options I needed. I had to hack this into the build scripts.
- I had to install a bunch of homebrew packages to include the required PHP options. This took some figuring out to determine which packages were required.
- Some required extensions were included via
pecl
which is some additional silly PHP extension installation system. The crazy thing is, even after you install the extension, you need to modify a php.ini file to load the extension. Huh?
I had some trouble finding exactly where to put the php.ini
file. You can see where PHP will look via php --ini
. I ended up discovering you can add the php.ini
file to $(asdf where php)/conf.d/
.
Here’s the script that ended up working for me:
pecl install redis
pecl install imagick
echo "extension=redis.so
extension=imagick.so" > $(asdf where php)/conf.d/php.ini
Open Questions
- What does
use function
do? - What is the docstring syntax that seems to be used in some places?
- I was curious to look into additional debuggers and understand how to break on exception