Skip to content

Re-learning Modern PHP

Tags: asdf, development, php • Categories: Learning

Table of Contents

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 to import 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 provides tinker, which is built on psysh, which solves most of the problem though: composer require --dev laravel/tinker
  • There’s no built-in binding.pry equivalent. You have to install xdebug pecl install debug, install a debugger (like VS Code’s php-debug), and then drop a xdebug_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