Guides:our thoughts

Add extensions to Laravel Herd without Homebrew

Checking read time...

We've recently started using Laravel Herd on our development machines for most of our projects.

Herd is a project that takes Laravel Valet, which we used for many years, and wraps it up into a nice UI. In addition, Herd will install precompiled versions of PHP for you, whereas Valet depends on Homebrew to compile PHP on your machine.

For all the benefits it brought the Mac ecosystem, we have experienced many instances where doing a Homebrew update would cause other dependencies to break on our machines, leading to hours or even days of lost productivity to get everything fixed and running again.

Laravel Sail solves this problem by building all of your dependencies inside a Docker container which won't be effected by Homebrew, however Sail can be quite heavy to get setup and can be overkill on simple projects.

Herd gives us a different approach where PHP is precompiled, so you don't need to worry about compiling it yourself, or dealing with Homebrew in general - it's largely just install Herd, and you're off to the races.

Default extensions

Because Herd's PHP is precompiled, you only get access to a set of common extensions that are used in most projects.

You can see these if you install Herd, and then run php -m:

1[PHP Modules]
2bcmath
3bz2
4calendar
5Core
6ctype
7curl
8date
9dba
10dom
11exif
12FFI
13fileinfo
14filter
15ftp
16gd
17gmp
18hash
19iconv
20imagick
21imap
22intl
23json
24ldap
25libxml
26mbstring
27mysqli
28mysqlnd
29openssl
30pcntl
31pcre
32PDO
33pdo_mysql
34pdo_pgsql
35pdo_sqlite
36pgsql
37Phar
38posix
39random
40readline
41redis
42Reflection
43session
44shmop
45SimpleXML
46soap
47sockets
48sodium
49SPL
50sqlite3
51standard
52sysvmsg
53sysvsem
54sysvshm
55tokenizer
56xml
57xmlreader
58xmlwriter
59xsl
60Zend OPcache
61zip
62zlib
63
64[Zend Modules]
65Zend OPcache

The official way to add PHP extensions to Laravel Herd

For a recent talk I was working on, I wanted to parse email files (.eml) in realtime using Laravel, and to do that I needed the Mailparse PHP extension.

The Herd documentation explains:

You may add additional PHP extensions that are not included out of the box with Herd, by installing them via Homebrew and pecl.

This immediately gave me flashbacks to the dark days of yore, and I knew this wasn't how I wanted to get started with my talk - dealing with not only installing Homebrew, then PHP, then running PECL, only to have it probably break right before my talk šŸ˜…

Looking for help with your Laravel project?

The better way to add PHP extensions to Laravel Herd

I did some research, crawling through GitHub issues, until I found this amazing comment by SagarNaliyapara on GitHub.

I downloaded extension manually from here - https://packages.macports.org/php81-xsl/ and then found .so file in it, added into php.ini and it fixed the issue for me

Thanks to Sagar's comment, I had now seen the light.

I had never heard of the MacPorts project before, but their mission is to create an easy-to-use system for compiling, installing, and upgrading open-source software on the Mac operating system - and boy have they done it!

I went searching in their huge list of packages (trust me there are a lot), and using Ctrl+F sure enough I found the magical php83-mailparse that I was looking for.

Clicking into that directory I could see they had lots of variants for different environments:

  • php83-mailparse-3.1.6_0.darwin_10.i386.tbz2
  • php83-mailparse-3.1.6_0.darwin_10.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_11.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_12.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_13.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_14.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_15.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_16.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_17.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_18.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_19.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_20.arm64.tbz2
  • php83-mailparse-3.1.6_0.darwin_20.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_21.arm64.tbz2
  • php83-mailparse-3.1.6_0.darwin_21.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_22.arm64.tbz2
  • php83-mailparse-3.1.6_0.darwin_22.x86_64.tbz2
  • php83-mailparse-3.1.6_0.darwin_23.arm64.tbz2
  • php83-mailparse-3.1.6_0.darwin_23.x86_64.tbz2

From this list, I could see that they only had the latest version of Mailparse, 3.1.6, which was perfect for what I needed.

Next I needed to determine my own environment, so that I knew which version to download. From the filenames, it looked like I needed to determine two things:

  • My "Darwin" version
  • My architecture (x86_64 or arm64)

Fortunately for us, Herd has done the hard work of getting PHP setup for our machine, so we can piggyback off it and get both details with a simple command:

1php -i | grep "Build System => "

Running that will give you an output that looks like this:

1Build System => Darwin oh-162-55-253-165 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:53:44 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T8103 arm64

From this, we can see that for the precompiled version of PHP that we are running, our Darwin version is 22.5.0, and we are on an arm64 architecture.

Be careful

Keep in mind you are downloading a library from the Internet and running it on your machine, so be sure that you're comfortable with the risk associated with that.

I then downloaded the corresponding file, php83-mailparse-3.1.6_0.darwin_22.arm64.tbz2, and just using Finder I could decompress the file to reveal the contents. I then just needed to locate the magical mailparse.so that I so desperately needed.

1$ tree php83-mailparse-3.1.6_0.darwin_22.arm64
2php83-mailparse-3.1.6_0.darwin_22.arm64
3ā”œā”€ā”€ +COMMENT
4ā”œā”€ā”€ +CONTENTS
5ā”œā”€ā”€ +DESC
6ā”œā”€ā”€ +PORTFILE
7ā”œā”€ā”€ +STATE
8ā””ā”€ā”€ opt
9 ā””ā”€ā”€ local
10 ā”œā”€ā”€ lib
11 ā”‚Ā Ā  ā””ā”€ā”€ php83
12 ā”‚Ā Ā  ā””ā”€ā”€ extensions
13 ā”‚Ā Ā  ā””ā”€ā”€ no-debug-non-zts-20230831
14 ā”‚Ā Ā  ā””ā”€ā”€ mailparse.so
15 ā”œā”€ā”€ share
16 ā”‚Ā Ā  ā””ā”€ā”€ doc
17 ā”‚Ā Ā  ā””ā”€ā”€ php83-mailparse
18 ā”‚Ā Ā  ā”œā”€ā”€ CREDITS
19 ā”‚Ā Ā  ā””ā”€ā”€ README.md
20 ā””ā”€ā”€ var
21 ā””ā”€ā”€ db
22 ā””ā”€ā”€ php83
23 ā””ā”€ā”€ ~mailparse.ini
24
2513 directories, 9 files

You can see the mailparse.so extension is contained inside opt/local/lib/php83/extensions/no-debug-non-zts-20230831, so I just went to that directory in Finder, and copied the extension file.

I then realised I wasn't sure what to do next.

Where should we place it?

Typically, Homebrew will setup somewhere inside our /opt/homebrew/lib/php folder to store extensions, but since we aren't using Homebrew, we don't have a place already established on our machine to store extensions.

Fortunately, PHP can load extensions from anywhere, so it's really up to you for where you want to put them.

I opted to just put the extension directly in the same folder as the Herd php.ini for this version of PHP, so that as I switch between PHP versions, the extension is only enabled for the correctly compiled PHP version that it corresponds to.

You can find that directory by running this command:

1php --ini | grep "Additional .ini files parsed:"

That ended up being this path on my machine:

1/Users/mdavis/Library/Application Support/Herd/config/php/83/php.ini

I copied the mailparse.so from the compressed file we downloaded and uncompressed, and pasted it into the same folder as the php.ini that Herd is reading, which in this case was /Users/mdavis/Library/Application Support/Herd/config/php/83/.

If you're not able to navigate to that file in Finder, try using Go > Go to Folder... from the Finder menu bar, and then paste in the path. You can also use Herd's Open configuration files menu bar item, and then navigate to the right PHP version.

Enabling the extension

I then opened the php.ini in this directory, and with a single line added at the end of the file, I was able to enable the extension:

1extension=/Users/mdavis/Library/Application Support/Herd/config/php/83/mailparse.so

I was surprised to see that I didn't need to add double quotes around the path, despite the fact that it contained a space.

Make sure you save the file, and you're all set.

I then restarted PHP from the Herd menu bar using Stop all and then Start all.

Taking the extension out of quarantine

Immediately I was presented with a system popup telling me:

"mailparse.so" can't be opened because Apple cannot check it for malicious software.

This is a protection built into macOS to stop you running executables from unknown (unsigned) developers.

No really, be careful

It's worth reiterating that you are trusting software from the Internet, which could do anything on your machine.

I trust the process at MacPorts, so I proceeded with the following.

You can get around it by taking the file out of quarantine with this command, importantly noting that we do need the double quotes this time, and substituting in your own path to your extension:

1xattr -d com.apple.quarantine "/Users/mdavis/Library/Application Support/Herd/config/php/83/mailparse.so"

I then restarted PHP from the Herd menu bar, again using Stop all and then Start all.

Now, when I run php -m to list our installed modules, I now see mailparse šŸŽ‰

1[PHP Modules]
2bcmath ...
3bz2
4calendar
5Core
6ctype
7curl
8date
9dba
10dom
11exif
12FFI
13fileinfo
14filter
15ftp
16gd
17gmp
18hash
19iconv
20imagick
21imap
22intl
23json
24ldap
25libxml
26mailparse
27mbstring ...
28mysqli
29mysqlnd
30openssl
31pcntl
32pcre
33PDO
34pdo_mysql
35pdo_pgsql
36pdo_sqlite
37pgsql
38Phar
39posix
40random
41readline
42redis
43Reflection
44session
45shmop
46SimpleXML
47soap
48sockets
49sodium
50SPL
51sqlite3
52standard
53sysvmsg
54sysvsem
55sysvshm
56tokenizer
57xml
58xmlreader
59xmlwriter
60xsl
61Zend OPcache
62zip
63zlib
64
65[Zend Modules]
66Zend OPcache

This doesn't always work

If you still get issues with PHP being unable to load a dynamic library, it's possible that you are missing some dependencies of the extension, in which case your mileage may vary. I was fortunate with the mailparse extension, but in testing for this article I installed the uuid extension and found that it needed another library called libuuid.

MacPorts should have the missing library for you, but at this point it does start to get a bit tedious to find the right file, decompress, move, and unquarantine the file, so in this case you may actually want to consider using Homebrew. I wish you luck!

Finishing up

If you need to install PHP extensions beyond what Laravel Herd comes with out of the box, and don't want to deal with dependency hell, I can strongly recommend using this method as it just works.

We are enormously thankful to the folks behind MacPorts for making this possible.

Want to receive updates from us?

Sign up for our newsletter to stay up to date with Laravel, Inertia, and Expo development.