Posted: 2025-01-26 12:31:00 by Alasdair Keyes
I've recently heard some good things about the PHP tool Rector (https://getrector.com/). It provides automatic refactoring for your PHP code to increase code quality.
This website (https://www.akeyes.co.uk) is running on PHP 8.x, previously 7.x and with a quick check of my composer.json
in Git, I can see that it was even running on PHP 5.6 when the site was built using the Slim Framework (https://www.slimframework.com/).
The earliest version of Laravel that I used was Laravel 5.5 on PHP 7.x, given that it's now on Laravel 11 on PHP 8.2, the code has progressed through many changes to the core language.
During this time, I've tried to keep the code updated. Implementing strict type checking on function parameters and return values. Keeping up with modern standards, but there will always bee a few bits I've missed. I thought this would be an ideal time to try Rector and see how it performs.
First, I checked out the latest version of the codebase on my dev machine and created a new branch.
I've tried to keep my test suite up-to-date which should give me some confidence that any changes made by Rector have not broken anything. We'll see how this confidence holds....
$ ./artisan test --compact
.............................................
Tests: 46 passed (140 assertions)
Duration: 1.50s
$
Before any changes, everything is passing.
$ composer require rector/rector --dev
./composer.json has been updated
Running composer update rector/rector
...
...
...
Using version ^2.0 for rector/rector
$
Rector is configured with a file called rector.php
. If you don't have this, running Rector for the first time will detect this and offer to put in a basic config.
$ app vendor/bin/rector
No "rector.php" config found. Should we generate it for you? [yes]:
> yes
[OK] The config is added now. Re-run command to make Rector do the work!
Taking a look at the file, it looks to have scanned for the common directories in my repo.
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/bootstrap',
__DIR__ . '/config',
__DIR__ . '/lang',
__DIR__ . '/public',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/tests',
])
// uncomment to reach your current PHP version
// ->withPhpSets()
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0);
I'm going to keep it at the defaults for the time being, the only change I'll make is to uncomment ->withPhpSets()
, this will cause Rector to check the PHP version in my composer.json
file and test against that (Currently PHP 8.2).
Running rector again outputs it's suggested changes and has updated the PHP files.
$ vendor/bin/rector
136/136 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
12 files with changes
=====================
...
... (Remove for brevity)
...
[OK] 12 files have been changed by Rector
$
Right off the bat, let's see if anything's broken...
$ ./artisan test --compact
.............................................
Tests: 46 passed (140 assertions)
Duration: 1.99s
$
So far, so good. Let's look at some of the changes it's suggested.
Firstly the RemoveExtraParametersRector
option has detected that the optional parameters for PHPUnit's assertStatus()
can be removed.
- $response->assertStatus(200, "Assert $uri returns a 200 response");
+ $response->assertStatus(200);
It looks like I added this second parameter to add some debug when I refactored the tests after upgrading to PHPUnit 10.x and hit some problems. It looks like I got confused with Perl's test suite which allows extra parameters. Checking the docs, the assertStatus()
function does only accept a single parameter. +1 for Rector
bootstrap/cache
.As the path suggests, the files bootstrap/cache/services.php
and bootstrap/cache/packages.php
are cache files and will be deleted and recreated regularly by Laravel. There is no need to fix these. This is user error and can be resolved by adding the following to my rector.php
config file.
->withSkip([
__DIR__ . '/bootstrap/cache',
])
This is a good catch and can be removed. I probably left it there during debug and didn't remove it. +2 for Rector.
try {
$record = $reader->city($ipAddress);
- } catch (AddressNotFoundException $e) {
+ } catch (AddressNotFoundException) {
//
}
The AddClosureVoidReturnTypeWhereNoReturnRector
option found a few instances in the boilerplate Laravel code within routes/console.php
where no return value was specified on closures. +3 for Rector.
-Artisan::command('inspire', function () {
+Artisan::command('inspire', function (): void {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
The ClosureToArrowFunctionRector
wishes me to convert anonymous functions to the newer format arrow =>
functions.
-Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
- return (int) $user->id === (int) $id;
-});
+Broadcast::channel('App.Models.User.{id}', fn($user, $id) => (int) $user->id === (int) $id);
I actually prefer the old style anonymous function. Retaining the function () { ... }
format helps my brain easily parse what the function is up to. The arrow format feels like a run-on sentence in my mind.
Although not an issue in this instance, arrow functions include variables from the parent scope and don't require the use
keyword to pass variables e.g. function() use ($i) { ... }
. Requiring use
to bring variables into scope is a good thing in my mind, so I think I'll keep these functions as they are.
To stop this I will just add the following to my rector.php
config file.
->withSkip([
\Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector::class,
]);
Note: The documentation shows that you can use the non-qualified class name ClosureToArrowFunctionRector::class
, however this give an error that the Rector rule does not exist, it may work for some rules, but for this one and possible others, you must give the fully qualified class name to the withSkip()
function.
I like this. The ClassPropertyAssignToConstructorPromotionRector
realises that as I'm now running PHP 8, the old style boiler plate class code can be dispensed with and the cleaner constructor property promotion format can be used. This was the only class in my codebase that was affected, but on a large codebase with some big classes this could really slim down the code. +4 for Rector.
@@ @@
class Repository
{
- /** @var string $folderPath */
- private $folderPath;
-
- /** @var string $gitBinary */
- private $gitBinary;
-
private const GIT_BINARY = 'git';
private const DEFAULT_TRUNCATED_COMMIT_ID_LENGTH = 10;
@@ @@
*
* @param string $folderPath
*/
- public function __construct(string $folderPath = '.', string $gitBinary = self::GIT_BINARY)
+ public function __construct(private string $folderPath = '.', private string $gitBinary = self::GIT_BINARY)
{
- $this->folderPath = $folderPath;
- $this->gitBinary = $gitBinary;
}
/**
Overall, I'm very impressed with Rector. It did nothing wrong, it broke no code and the only changes I had to make were due to my tastes rather than to fix a problem. If anything I wish my codebase was in a worse condition so that I could get more benefit from it.
It appears there's even a PHPStorm/Jetbrains plugin to automatically run Rector on your code base https://plugins.jetbrains.com/plugin/19718-rector-support.
If I took on an old codebase as part of a new role, I would definitely use Rector to incrementally modernise the code.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2025-01-03 10:00:35 by Alasdair Keyes
My most enjoyed albums of 2024...
Special mention to Squarepusher - Dostrotime.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-11-20 15:13:09 by Alasdair Keyes
After my blog post earlier this year about installing LineageOS onto an old OnePlus2 phone (https://www.akeyes.co.uk/blog/installing_lineageos_on_ancient_device); I've had some interest from people asking me to install LineageOS onto their older phones.
Installation of LineageOS is quite a technical endeavour and can be quite difficult for some devices. It seems that regular phone stores on the high street may offer hardware repairs but not Operating System installs.
As such I have expanded some of the services that I offer through my company Cloudee LTD (https://www.cloudee.co.uk/), to include installation of LineageOS onto old mobile devices.
So if you do not feel comfortable doing it yourself or have perhaps already tried and been unsuccessful, feel free to contact Cloudee and we'll see if we can help.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-07-26 10:50:42 by Alasdair Keyes
Once again, it's Sysadmin day.
Give thanks to your sysadmin. If you are a sysadmin, have a beer.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-06-18 16:56:26 by Alasdair Keyes
This week I finally upgraded my phone from my ancient OnePlus 2 I bought in 2015 to a more modern Samsung.
For years, I've wanted to install LineageOS, an open Android variant. I always held off from installing it on my only phone in case I bricked it and was left up the creek without a paddle.
Now that I have the OnePlus 2 spare, I decided to finally go for it. Unfortunately, the device is now so old that LineageOS no longer hosts the installation images for it.
Thankfully, with https://archive.org/ I was able to gather all this data and thought it might be useful for others in the same situation. I will use my OnePlus 2 as an example, but obviously substitute this for your device.
Visit https://wiki.lineageos.org/devices/ - Using the Filter Options
unselect the Hide discontinued devices
check box then find your device. If it does not appear LineageOS never supported your device.
Navigate to the page for your device (For me https://wiki.lineageos.org/devices/oneplus2/). From here it's worth noting the latest LineageOS version that was available for your phone under Previously supported versions
(17.1) and you will have access to the installation instructions via the Installation
link. (The installation instructions will tell you to build the image from source, but ignore that, we will get the pre-built image).
As the build images are no longer kept we will have to go to https://archive.org/ to hunt for it. When at archive.org enter the URL https://wiki.lineageos.org/devices/oneplus2/ into the Wayback machine search box. You will need to browse around all the snapshots to find the last snapshot that has the heading Get LineageOS for the OnePlus 2
and a Get the builds here
link.
(Using a manual binary search on the snapshots is probably the quickest way to find this).
For my device, this URL was https://web.archive.org/web/20210518101444/https://wiki.lineageos.org/devices/oneplus2/
From this archived page click on the Get the builds here
link. This page doesn't link to the image files any more, but it does show the image names, which we can use.
Go back to https://archive.org/ and in the search make sure Search Metadata
is selected and enter the image name lineage-17.1-20210605-nightly-oneplus2-signed
If archived, it will show the files available. If it doesn't you're probably out of luck. There are other sites that provide builds, but I would only trust archive.org to not have tampered with the images.
You may wish to play around with the search function and enter just nightly-oneplus2-signed
and scan the results for an even later version that may have been archived. The numbers in the filename 20210605
are the concatenated year/month/day that the image was generated.
For the Last LineageOS 17.1 build on the OnePlus 2 this page is https://archive.org/details/lineage-17.1-20210605-nightly-oneplus2-signed_202108 - You can download the image via HTTPS but be a friend to archive.org and use the Torrent instead to help save them data costs.
Follow the installation instructions we found in Step 2.
The TWRP recovery image and Google Apps image are still available at LineageOS through the links provided in the installation instructions. From here you should have everything you need to install LineageOS on your ancient device.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-04-12 09:52:59 by Alasdair Keyes
TL;DR if you are just looking for the fix scroll down to see the The Fix
section.
As I run a UK company, I have to supply various bits of information to HMRC (the UK tax office) for payroll.
HMRC offer a free tool called Basic PAYE Tools
for small businesses to make this possible (https://www.gov.uk/basic-paye-tools). It's a great tool, without it small companies would have to shell out money for proprietary solutions just to fulfil basic payroll/tax obligations. The tool is available for Windows, Mac and Linux. This is specifically for the Linux version, but a similar bug/fix may work for Mac.
The tool is a binary rti.linux
that starts a web-server which listens on a http://127.0.0.1:46729/
. A browser is then opened to automatically connect to that URL to provide the user with a GUI. The server stores data in a local sqlite database. When any actions are performed that require HMRC to be notified (such as paying employees), these transactions are stored locally and then sent via a batch process to HMRC.
As we have just passed into the 24-25 tax year, I have to 'close' the old year so that I can start the new one. Part of this process is to generate a P60 form for all employees. However, when doing this, the page just appeared to refresh and no form was displayed. This action is not one that requires any transfer of information to HMRC as the P60 is just a summary of an employees payments and tax contributions for that tax year.
A Google didn't show any similar issues and there was no easy 'File bug report' option. The only reporting is to call up HMRC on the phone. I didn't really want to try explaining the issue over the phone so I thought I'd investigate myself.
I could find no information about log files for the tool so I found the process of the rti.linux
and looks in /proc/
for any possible logs.
$ ls -al /proc/24876/fd/
total 0
dr-x------ 2 user user 0 Apr 12 09:56 .
dr-xr-xr-x 9 user user 0 Apr 12 09:52 ..
lr-x------ 1 user user 64 Apr 12 09:56 0 -> /dev/null
l-wx------ 1 user user 64 Apr 12 09:56 1 -> /home/user/.xsession-errors
lrwx------ 1 user user 64 Apr 12 09:56 10 -> 'socket:[147745]'
lrwx------ 1 user user 64 Apr 12 09:56 11 -> 'socket:[148555]'
lrwx------ 1 user user 64 Apr 12 09:56 12 -> 'anon_inode:[eventfd]'
lrwx------ 1 user user 64 Apr 12 09:56 13 -> 'socket:[148556]'
lrwx------ 1 user user 64 Apr 12 09:56 14 -> 'socket:[147746]'
lrwx------ 1 user user 64 Apr 12 09:56 15 -> 'socket:[147747]'
lrwx------ 1 user user 64 Apr 12 09:56 18 -> 'socket:[147819]'
l-wx------ 1 user user 64 Apr 12 09:56 2 -> /home/user/.xsession-errors
lrwx------ 1 user user 64 Apr 12 09:56 21 -> 'anon_inode:[eventfd]'
lr-x------ 1 user user 64 Apr 12 09:56 3 -> /dev/urandom
l-wx------ 1 user user 64 Apr 12 09:56 4 -> /tmp/rti.log
lrwx------ 1 user user 64 Apr 12 09:56 5 -> 'anon_inode:[eventfd]'
lr-x------ 1 user user 64 Apr 12 09:56 6 -> 'pipe:[144859]'
l-wx------ 1 user user 64 Apr 12 09:56 7 -> 'pipe:[144859]'
lrwx------ 1 user user 64 Apr 12 09:56 8 -> 'socket:[144860]'
lrwx------ 1 user user 64 Apr 12 09:56 9 -> 'socket:[144024]'
The /tmp/rti.log
shows one entry.
24876 Server has asked us to open_file
And /home/user/.xsession-errors
shows something a little more helpful.
evince: error while loading shared libraries: libjpeg.so.62: failed to map segment from shared object
So it looks like an error in libjpeg.so.62
, but nothing specific.
To get more information, I ran strace
to find more about what was being called. (The read()
call data is truncated to save space).
$ strace -Ff -v -s 1000 -p 24876
[pid 25497] openat(AT_FDCWD, "/home/user/HMRC/payetools-rti/libjpeg.so.62", O_RDONLY|O_CLOEXEC) = 3
[pid 25497] read(3, "\177ELF\2\1\----TRUNCATED----\0)\0\0\0", 832) = 832
[pid 25497] newfstatat(3, "", {st_dev=makedev(0xfd, 0x1), st_ino=23609456, st_mode=S_IFREG|0644, st_nlink=1, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=848, st_size=432128, st_atime=1712911963 /* 2024-04-12T09:52:43.275396143+0100 */, st_atime_nsec=275396143, st_mtime=1602102343 /* 2020-10-07T21:25:43+0100 */, st_mtime_nsec=0, st_ctime=1712611938 /* 2024-04-08T22:32:18.204486045+0100 */, st_ctime_nsec=204486045}, AT_EMPTY_PATH) = 0
[pid 25497] mmap(NULL, 434200, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f318954d000
[pid 25497] mprotect(0x7f3189551000, 413696, PROT_NONE) = 0
[pid 25497] mmap(0x7f3189551000, 241664, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = -1 EACCES (Permission denied)
[pid 25497] close(3) = 0
[pid 25497] writev(2, [{iov_base="evince", iov_len=6}, {iov_base=": ", iov_len=2}, {iov_base="error while loading shared libraries", iov_len=36}, {iov_base=": ", iov_len=2}, {iov_base="libjpeg.so.62", iov_len=13}, {iov_base=": ", iov_len=2}, {iov_base="failed to map segment from shared object", iov_len=40}, {iov_base="", iov_len=0}, {iov_base="", iov_len=0}, {iov_base="\n", iov_len=1}], 10) = 102
[pid 25497] exit_group(127) = ?
[pid 25497] +++ exited with 127 +++
Here we see /home/user/HMRC/payetools-rti/libjpeg.so.62
is opened for reading, the data is read and then mmap()
and mprotect()
are run, the file is closed and then we see the log error above is written to file handle #2 (/home/user/.xsession-errors) before the process exits with a non-zero code.
This indeed looks to be the issue, I don't know enough about the codebase to determine if it is corrupt or perhaps it wasn't updated when the rest of the code? It's very likely a FOSS library. So I checked if Debian supplied a version.
$ apt-file search libjpeg.so.62
libjpeg62-turbo: /usr/lib/x86_64-linux-gnu/libjpeg.so.62
libjpeg62-turbo: /usr/lib/x86_64-linux-gnu/libjpeg.so.62.3.0
Great, maybe I can use that?
Make sure paye-tools is closed.
Install the libjpeg62-turbo
package
It turns out I already had the libjpeg62-turbo
package installed, but if you don't you can use.
apt install libjpeg62-turbo
I checked if the files were the same.... it turns out they're different versions.
$ sha256sum /home/user/HMRC/payetools-rti/libjpeg.so.62.paye-tools-version /usr/lib/x86_64-linux-gnu/libjpeg.so.62
4f3446bc4c2a2d3c75b7c62062305ff8c5fcdaa447d5a2461d5995d40f728d00 /home/user/HMRC/payetools-rti/libjpeg.so.62
dad87949ccad2be7e40a02986306087fdcfb35ccaadd59aea923a3f96d290eec /usr/lib/x86_64-linux-gnu/libjpeg.so.62
$ cd /home/user/HMRC/payetools-rti
$ mv libjpeg.so.62 libjpeg.so.62.orig
$ ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so.62 libjpeg.so.62
I started the tool back up and I was able to get the P60.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-03-13 16:21:36 by Alasdair Keyes
I've been playing about with local LLM (Large Language Model) AIs.
I knocked together this docker-compose.yml
file to help people get started with Ollama with a nice Open-WebUI front-end to have the "joy" of AI, but locally.
It's available through a Gitlab snippet, or you can copy and paste from below. https://gitlab.com/-/snippets/3687211
---
# Created by Alasdair Keyes (https://www.akeyes.co.uk)
# * `docker-compose up`
# * Visit http://127.0.0.1:3000 to create account and login
# * Click 'Select a model'
# * Enter the model name to use. Click the link on the page to see all. `llama2` or `llama2-uncensored` are suitable first options.
# * Chat
version: '3'
services:
ollama:
image: "ollama/ollama"
volumes:
- ollama-data:/root/.ollama
# Uncomment ports to allow access to ollama API from the host
# ports:
# - "127.0.0.1:11434:11434"
open-webui:
image: "ghcr.io/open-webui/open-webui:main"
depends_on:
- ollama
ports:
- "127.0.0.1:3000:8080"
environment:
- "OLLAMA_BASE_URL=http://ollama:11434"
volumes:
- open-webui-data:/app/backend/data
volumes:
ollama-data:
open-webui-data:
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2024-01-01 11:17:12 by Alasdair Keyes
My most enjoyed albums of 2023...
Special mentions Jungle - Volcano, which just missed out a place in the top five. Additional mentions to Metallica - 72 Seasons, Aphex Twin - Blackbox Life Recorder 21f / In a Room7 F760 as it's been a while since each had a release but neither quite had the spark I was hoping for.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2023-12-23 14:53:03 by Alasdair Keyes
I recently had a Dell laptop that required a BIOS update, but the BIOS installers were only for Windows and my machine was running Debian.
This posed a problem as to how to apply the update. I didn't want to go swapping out hard-drives to install Windows, or even wipe the Linux installation to install Windows and then re-install Linux after.
In the end I found that I could start a Windows 10 installation process, drop to a shell and run the BIOS update.
The steps are as follows...
.exe
files and put them onto a second USB stick.SHIFT+F10
, a Windows Command prompt appears.E:
.e: <ENTER>
.After the update, you can remove the USB sticks and reboot back into Linux.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
Posted: 2023-07-28 07:59:10 by Alasdair Keyes
It's that time of year again - Give thanks to your sysadmin.
If you found this useful, please feel free to donate via bitcoin to 1NT2ErDzLDBPB8CDLk6j1qUdT6FmxkMmNz
© Alasdair Keyes
I'm now available for IT consultancy and software development services - Cloudee LTD.
Happy user of Digital Ocean (Affiliate link)