How to develop a library and a project in PHP concurrently

I keep forgetting the best way to do this, so thought it best to write it down.

Suppose you are building a library and an example project that implements it. You can do this in a single repository with an example folder, for small examples.

But for larger examples you might want to use a separate repository, with it’s own issues and documentation.

This can make it a hassle to work with the library and keep the project in sync.

Let’s say these are mylibrary and myproject which is using the library. Let’s assume you’re using PHP and Composer. mylibrary is at version 0.1 but there are newer commits in the develop branch.

Require a Git repository

$ cd myproject
$ composer config vcs
$ composer require me/mylibrary:0.1 --prefer-source

--prefer-source means Composer will install from source if there is one, the result is a git repository.

Switch to the develop branch

Once installed it’s then easy to switch to any other branch you want to work on:

$ cd vendor/me/mylibrary; git switch develop

You don’t have to register the repository with packagist if you follow this workflow! As a bonus when after you run composer update in myproject the mylibrary directory sticks with the branch.

Daily Clean Trash

I use trash-cli on every linux install, and today I realised it’s not actually removing files from the trash, so I’ve set up this little scheduled task to delete items that have been trashed at least 30 days ago:

$ crontab -e
# daily trash clean
@daily /usr/bin/trash-empty 30

OpenSUSE Tumbleweed Installation Notes

For future reference here are some issues I’ve come across installing and setting up Tumbleweed on my workstation.

The reason for switching from macOS is that a commercial operating system focused on services builts in too many distractions for me. Also long term the Apple hardware is priced above what I’m prepared to pay, so it makes sense for me to look for an alternative.

Tumbleweed is a rolling distribution which makes for a long term maintained setup, and packages are tested thoroughly but are quickly updated.

So far these are just little niggles.

Day one issues

  • The old Grub menu from my previous Linux installation wasn’t wiped, I simply reinstalled making sure all existing partitions were deleted before partitioning.
  • avahi is not allowed to start itself due to security policies. This was because dbus needed to restart after avahi installed configuration into it. I could have rebooted.
  • My Apple Magic Keyboard must be selected in KDE after the installation as it’s not an option during the installation routine.
  • Opening ports for OpenSSH means that they’re open on the public zone. I didn’t realise this as I want to use the home zone as default.Yast Firewall sorted this out.
  • After installing Lutris, Steam and Proton Experimental, certain games would not start, due to missing vulkan drivers. This Lutris Wiki link describes the issue, I had to search Yast Software for vulkan showed I had to install libvulkan_intel and libvulkan_intel-32bit.

Interesting links

  • There’s a nice tiling window manager script for KDE called Krohnkite. Meta-D conflicted with show desktop, after reassigning the latter I still think that shortcut doesn’t do anything, though.

So far so good. The interface is more snappy than macOS and more powerful, and the system is working nicely.

Bye Twitter

I’ve deactivated my Twitter account.

Twitter didn’t like it, you get unceremoniously dumped out of the service with an error message. All your tweets disappear. Like you weren’t welcome anyway, social media with a nasty streak.

The problem with Twitter is that there’s only space for black/white arguments and replies. It harms compassion and collaboration. It’s algorithms are optimised for making money, not your wellbeing.

Let’s go make something beautiful instead with all the time saved!

elementaryOS 6 refresh

default elementaryOS desktop

I refreshed the olde Thinkpad to elementaryOS 6, and thought it would be useful to me and others to document the process.

As they don’t support an upgrade from elementaryOS 5, I backed up my repositories using repoman and paved the rest, as I use this laptop for side-projects and have a sync solution in place.

The first install resulted in a black screen (perhaps it went into standby and did not respond to mouse or keyboard presses), but a restart continued the installation successfully. Further OS setup was straightforward and simple. I thought about creating a /home partition but as I was happy with my migration process I decided to go with the convenient default.

The first app to install I had to get from FlatHub, which is nicely integrated. This first flatpakref loaded on the second try and installed using the integration included in the OS.

Syncthing’s introducer feature allows a device to introduce shares to connected devices and this helps setting up the shared folder containing my new-device scripts. It’s important to untick “Receive only” so that it’s a two way sync.

This post will be updated as more thoughts become available.

The trick to relative symlinks

I learned this week that relative symbolic links are relative to the link not the current working directory. What does this mean?

To remind ourselves, the format of ln is:

ln -s {{/path/to/file_or_directory}} {{path/to/symlink}}


Say we have the following structure:


Say we want to create a symlink to app/config.yaml. If we are familar with the cp command we would incorrectly do this:

$ ln -s config/config.yaml app/config.yaml  #this is incorrect

Here, we’re asking ln to create a symlink in app/config.yaml that points to app/config/config.yaml because the ‘source’ parameter is relative to the link being created, not the current working directory. As that file does not exist the command fails!

Correct example:

$ ln -s ../config/config.yaml app/config.yaml #this is correct
$ cd app; ln -s ../config/config.yaml config.yaml; cd - #this is clearer

The first correct example can be difficult to comprehend unless we’re familiar with this concept: From the perspective of the app/config.yaml file, we need to first go to the parent directory then into the config directory.

This is clarified by the third example: by changing into the destination directory, we align the current working directory with the path to the source.


  1. If we create absolute symbolic links by providing the full path to the source file, this is avoided, however these links are more fragile. For example when syncing links the absolute path to the source is likely different on another device.

nvm ind

So shell login can be slow if you installed node versions using nvm and used nvm to set a default interpreter. The solution that helped me was to unset the default node but that means that there’s no node executable unless you manually first run nvm use node or setup .nvmrc in projects… If limited to the former solution, that means PHPStorm might complain node is missing.

I had success installing node from nodesource as the “system node” fallback. weird flex but ok. Some node is better than the right node.

Sharing Smaller Screen Recordings

Often I share a screen recording so that others can follow along with tips, processes and generally shared knowledge. However the screen recordings produced by macOS are huge! Here’s how to share smaller recordings, using the command-line version of HandBrake and Hazel:

Rule details

By default new recordings are dropped on the Desktop. Create a new Hazel rule monitoring new files in the Desktop directory:

If all of the following conditions are met:

  • Name starts with “Screen Recording”
  • Extension is “mov”
  • Size is less than “500 MB” (this excludes large recordings)

Do the following to the matched file or folder:

  • Add tags “Red” (to indicate the file is being processed)
  • Run shell script “embedded script” (see below)
  • Move to folder “Trash” (delete the original)

Embedded Script

handbrakecli --preset="Web/Gmail Large 3 Minutes 720p30" -i "$1" -o "$1.mp4"

My last 48 second recording clocked in at 408KB, not bad.

25 years of making the case for accessibility

WordPress Tavern makes a case for why accessibility matters in WordPress. In the meantime some of us have been discussing this for 25 years, and so have something to say about it.

The main problem with accessibility is the term itself. What we are actually talking about here is a universal user experience.

The industry needs to stop selling accessibility as a checkbox and we need to stop viewing it as something extra to do. Inherently every feature and decision we make is already more or less accessible — it is not an on/off switch.

By applying the inversion principle, we should look at what barriers are preventing people from using our websites to the fullest extend, and code all future solutions with these lessons learned.

Aiming for anything less than universal access also makes little economic sense. Let’s ask ourselves: why make a theme and then make it hard to use on purpose? Why make a theme and only allow a subset of people to use it? Do you only allow people with a first name in the first half of the alphabet to log in to your site? Let’s not artificially limit the versatility of the web.

A universal experience should be expected now, and when someone cannot use your site it is a bug. Your site is not inclusive when people cannot use it.

How to transfer a ringtone from macOS to iOS

With Sync Library (previously iCloud Music Library) enabled, simply move the file into the iCloud Drive > Music folder.

This setting can be enabled in Settings > Music > Library > Sync Library.

Ringtones are m4a files renamed to m4r, with a length under 30 seconds.

The ringtone can then be selected at the Settings > Sounds > Sounds and Vibration Settings > Ringtone list.

Time to wake up!

Enjoy the WordPress fun while it lasts folks, I’ve seen the light, and I am worried.

Automattic and the community are spending millions and millions on building a more useful but buggy JS editing interface for the web to make publishing rich documents passable. It can’t get a close button aligned on a modal, or scroll correctly in a mobile device. Basically, Gutenberg is a lesser version of Word 97. It’s not their fault, the browser stack is a brittle way to build apps. We’ve collectively stretched web technologies past what they’re best used for – there are no JavaScript native widgets that can compete with what the OS offers itself. It turns out the web excels to read documents, not write them. I know how it sounds, I’m writing this in the same editor! But we’re on the wrong path:

Because, while everyone is looking to the left at SquareSpace and Wix, trying to figure out how to improve the publishing workflow, on the right Alejandro Crosa builds an iOS app that publishes notes online by simply saving them. It abstracts the whole publishing process. Similarly to how Dropbox abstracted the filesharing site, a native app removes the web editor. In. Three. Weeks. Using Rails and Swift!

In 3 weeks he did a (subjectively) better job for this use case than the WP app and the WB mobile experience. Just look at it and it’s blog — the latter which is written using the service itself!). Yes it does less, but it’s about the job to be done. Gutenberg does less than Word 97 and Dreamweaver. The point is, that’s fine.

WP is currently deciding whether the REST API should be open to third party editors. It’s mission is to democratise publishing, but somehow this is up for discussion. However it turns out the best experience is a native one. Should WP forego the admin interface, change Gutenberg’s full site editing into the theme editor, and restructure itself for what’s to come? WP has a lot of moving parts, and consensus isn’t easily reached. It seems to me that the web will experience will refocus on reading documents, with content creation in apps.

If Apple ever adds a backend service to Pages to sync with a static site generator, or someone goes 10% further than iA Writer’s web-publishing feature in a ‘writing app’ than the whole WP ecosystem is sherlocked.

It’s time to wake up.