My book-writing workflow – Matthias Noback

By request: what’s my workflow for writing books? Steps, tools, etc.

Writing with the Leanpub platform

A long time ago I noticed that testing-advocate Chris Hartjes published his books on Leanpub. When I had the idea of writing a book about the Symfony framework, I tried this platform and it was a good match. You can write your book in Markdown, commit the manuscript to a GitHub repository, and push the button to publish a new version. Leanpub will generate EPUB and PDF versions for you. Readers can be updated about releases via email or a notification on the website.

While publishing multiple books like this, I kept running into these limitations:

  • Even though writing a book like this feels a lot like writing code (make a change, push, deploy), what’s missing is a continuous integration step where we can verify that the book is “correct”.
  • With the Leanpub Markdown setup you can include .md chapter files from your main Book.txt file, but you can’t include files from those .md files. This makes it hard to create a nested folder structure for chapters, sections, and code samples.
  • If you include source code in a book, it has to be formatted to be readable and to fit on a page, and parts have to be skipped or abbreviated. Still, you want the code to be correct. For this you want to run tests on the code, but you can’t run tests on incomplete or abbreviated code samples. I no longer wanted to copy/paste working code and format it manually for presentation purposes.
  • The code samples should be tested, but they should also be analyzed with PHPStan, and they should be upgrade-able to newer
    PHP versions. The go-to tool for this is Rector. I want to have these tools available in my book projects by default.
  • When I include the output of tools like PHPUnit in the book, I don’t want to copy/paste it from the terminal either. When I make a change to the code, the output should be regenerated automatically and included in the book.

A manuscript pre-processor

To overcome all these problems I created a pre-processor tool that allows me to do things that aren’t possible with Leanpub’s Markdown (or Markua, their variation on Markdown), but that eventually result in a manuscript directory that Leanpub can turn into an actual book. This means that nowadays I write in “Matthias-Flavored Markua” 😉 And that I have a continously-integrated book-writing process which uses PHPUnit for tests, PHPStan for static analysis, Rector for automated refactoring, and ECS for the coding standard. The pre-processor tool is hand-written in PHP. Given that it has to process Markua, it contains a Markua parser which leverages the awesome Parsica library.

Some of the things the tool can do:

  • You can add special comments to code samples that influence the final look when included in the manuscript, e.g. // skip-start and // skip-end to create an ellipsis (// ...) or // crop-start and // crop-end to remove lines from the beginning and end of an included file.
  • You can create a cover image with Gimp, and the tool will convert the .xcf file to a .png file that matches Leanpub’s expectations.
  • You can include .md files from any other .md file. In the end, everything will be compiled into a single book.md file.
  • When you regenerate this manuscript, it’s easy to see which parts have been changed, which allows you to spot parts that were changed by mistake, or that no longer work because of a different output from PHPUnit, etc.
  • You can collect all external links and generate a link registry for it, so the book will never have broken links.
  • You can include code from a package in vendor/ and reformat it according to the style used for the other code samples.
  • And so on! The project is yet another example of the amazing powers that will be unleashed if you have access to an Abstract Syntax Tree for a language, in this case Markua.

The directory structure of a book project

The path towards releasing a new book

For me, a new book always starts with an idea, which I immediately judge for its capability of becoming a book (as opposed to a blog post, or series of blog posts). In a sense, every idea can become a book, but it has to be interesting, I should be able to build on my own exp

Truncated by Planet PHP, read more at the original (another 8937 bytes)

When to use a trait? – Matthias Noback

When to use a trait? Never.

Well, a trait could be considered to have a few benefits:

Benefits

  1. If you want to reuse some code between multiple classes, using a trait is an alternative for extending the class. In that case the trait may be the better option because it doesn’t become part of the type hierarchy, i.e. a class that uses a trait isn’t “an instance of that trait”.
  2. A trait can save you some manual copy/paste-ing by offering compile-time copy/paste-ing instead.

Downsides

On the other hand, there are several problems with traits. For instance:

  • When a trait adds one or more public methods to a class, you’ll often experience the need to define those methods as an interface that the class will automatically implement by using the trait. That’s impossible. A trait can’t implement an interface. So you have to use the trait and implement the interface explicitly if you want to achieve this.
  • Traits have no “private” methods and properties of their own. I often like to hide some things from the class where the trait is used, but it’s impossible to make things “trait-private”. Anything defined as private in the trait will still be accessible by the class where the trait is used. Which makes it impossible for a trait to encapsulate anything.

Better alternatives

Anyway, in practice, I always find better alternatives for using a trait. As an example, here are some alternatives:

  • If the trait has service responsibilities, it’s better to turn it into an actual service instead, which can and should be injected as a constructor argument into the service that requires this service. This is known as composing behavior, instead of inheriting behavior. This approach can also be used when you want to get rid of parent classes.
  • If the trait adds some behavior to an entity that is the same for another entity, it often makes sense to introduce a value object and use it instead.
  • Another option when you have the same behavior in different parts of the model, is to just copy the code. This will keep the design of each object flexible enough, so each can evolve in its own direction. When we want to change the logic, we won’t have to worry about other places that reuse the same logic.

A counter-example

Even though most situation don’t really need a trait, and there are better alternatives, there is one situation that I keep using a trait for. This is the code for that trait, and if you ever attended one of my workshops, you’ll know it already:

trait EventRecording
{ /** * @var list<object> */ private array $events = []; private function recordThat(object $event): void { $this->events[] = $event; } /** * @return list<object> */ public function releaseEvents(): array { $events = $this->events; $this->events = []; return $events; }
}

I use this trait in entities, because there I always want to do the same thing: record any number of events, then after saving the modified state of the entity, release those events in order to dispatch them.

One concern might be that releaseEvents() is a public method. But it doesn’t have to be on any interface. We don’t need an Entity interface, or a RecordsEvents interface, unless we want to create some reusable code that can save an entity and dispatch its recorded events.

Another concern is that this trait suffers from the lack of “trait-private”. As an example, instead of using $this->recordThat(...), an entity that uses this trait could also just do $this->events[] = ....

We could fix this issue by extracting the code into an object, (adding even more evidence to the statement that there’s always an alternative for using a trait):

final class EventRecording
{ /** * @var list<object> */ private array $events = []; public function recordThat(object $event): void { // ... } /** * @return list<object> */ public function releaseEvents(): array { // ... }
}

We then need to assign an instance of this new class to a private property of the entity:

final class SomeEntity
{ // Can we do this already? I forgot private EventRecording $eventRecording = new EventRecording(); /** * @return list<object> */ public function releaseEvents(): array { return $this->eventRecording->releaseEvents(); } // ...
}

Truncated by Planet PHP, read more at the original (another 1227 bytes)

Installing Xdebug on PHP 8.1 installed with Homebrew – Rob Allen

I have recently set up a new M2 MacBook Air and as usual, installed Homebrew and then installed PHP. Homebrew is always up to date, so it installed PHP 8.1 for me.

Again, as usual, I installed Xdebug using pecl install xdebug.

This whirrs and clicks for a while downloading and compiling the xdebug.so and then fails with:

rob@ardent ~ $ pecl install xdebug
downloading xdebug-3.1.5.tgz ...
Starting to download xdebug-3.1.5.tgz (232,070 bytes) ... Build process completed successfully
Installing '/opt/homebrew/Cellar/php/8.1.8/pecl/20210902/xdebug.so' PHP Warning: mkdir(): File exists in /opt/homebrew/Cellar/php/8.1.8/share/php/pear/System.php on line 294
ERROR: failed to mkdir /opt/homebrew/Cellar/php/8.1.8/pecl/20210902

Investigating, I found that /opt/homebrew/Cellar/php/8.1.8/pecl is a symlink to /opt/homebrew/lib/php/pecl, however, this directory doesn’t exist. So to fix, I created the directory and installed Xdebug again:

mkdir /opt/homebrew/lib/php/pecl
pecl install xdebug

This time it worked:

rob@ardent ~ $ pecl install xdebug
downloading xdebug-3.1.5.tgz ...
Starting to download xdebug-3.1.5.tgz (232,070 bytes) ... Build process completed successfully
Installing '/opt/homebrew/Cellar/php/8.1.8/pecl/20210902/xdebug.so'
install ok: channel://pecl.php.net/xdebug-3.1.5
Extension xdebug enabled in php.ini

A quick test shows that all is now as it should be:

rob@ardent ~ $ php -v
PHP 8.1.8 (cli) (built: Jul 8 2022 10:46:35) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.8, Copyright (c) Zend Technologies with Xdebug v3.1.5, Copyright (c) 2002-2022, by Derick Rethans with Zend OPcache v8.1.8, Copyright (c), by Zend Technologies

A nomenclature of hate – Jordi Boggiano

(What feels like) a lifetime of maintaining open source projects has left me with some things to say about the haters. I’m not sure why.

Trying to do a light take of it instead of getting mad, here is a post categorizing them.

Raging hater

Your average online troll, the raging hater just needs to empty their bile on you. Usually not worth engaging tho I’ve defused a few and gotten valuable feedback over the years, but it takes great amounts of patience.

Verbose hater

I will try to keep this one short, unlike our first hater. The verbose hater writes a book of a rant and mostly exhausts your nerves by the length of it all. There is usually a point in there somewhere but it’s so tedious to get to it and once you figure it out you just don’t want to help them anymore.

Schrödinger’s hater

This tweet by Marco Pivetta (named and shamed here with his permission because I don’t hold it against him) is a prime example of this, even though a fairly harmless one. Schrödinger’s hater is angry but you’re not sure about what. Right now they could both be hating themselves and just venting, or hate something you did. Until you engage them to know which it is it’s likely to leave you feeling bad just in case.

If you can get past the initial urge to yell back at them, I find it is usually worth asking for more info.

Illiterhater

The illiterhater isn’t really hating you, but hates to read warnings and errors. They would like you to do it for them. For some reason they can read better when they’re wasting someone else’s time and they will probably write a long issue and make you ask three times until they post the output that contains a warning giving the solution to their problem.

The mirror hater

I can tell they hate mirrors not because of their shoddy haircut but because I am pretty sure they’ve complained before about a customer or user of theirs not giving them any helpful info at all and yet here they are opening an issue which contains no more text than “it did not work”.

PHP 8.2.0 Beta 1 available for testing – PHP: Hypertext Preprocessor

The PHP team is pleased to announce the first beta release of PHP 8.2.0, Beta 1. This continues the PHP 8.2 release cycle, the rough outline of which is specified in the PHP Wiki.For source downloads of PHP 8.2.0 Beta 1 please visit the download page.Please carefully test this version and report any issues found in the bug reporting system.Please DO NOT use this version in production, it is an early test version.Because of a bug found in early testing of this release, this version is NOT usable with ZTS builds.For more information on the new features and other changes, you can read the NEWS file, or the UPGRADING file for a complete list of upgrading notes. These files can also be found in the release archive.The next release will be PHP 8.2.0 Beta 2, planned for Aug 4 2022.The signatures for the release can be found in the manifest or on the QA site.Thank you for helping us make PHP better.

“Hive token as expired” – Ken Guest

( The Hive “climate platform” is a set of products from https://www.hivehome.com/ that allow for remote control of your heating system, including thermostats. )

The HACS Hive integration in Home Assistant threw this into the logs today, which meant that I had to do things via the Hive app this morning instead of using the integration in Home Assistant.

“Hive token as expired when calling https://beekeeper.hivehome.com/1.0/cognito/refresh-token – HTTP status is – 401”

I had been using a release of the Hive integration several releases behind the current release – version 2021.2.6, where I didn’t have 2FA enabled in the app because that was breaking authentication for this integration. Then I upgraded to 2022.7.3 and found that the “Hive token as expired” message was being reported.

The fix was simple, somewhere along the updates the maintainers of the Hive Integration must have added support for Hive’s 2FA and I just needed to :

  1. Delete the hive section from Home Assistant’s configuration.yaml file
  2. Enable 2FA in the Hive app
  3. Reinstall the latest version of the Hive integration (I uninstalled it after encountering the problem)
  4. Re-enter my credentials, including the new Hive verification code when prompted by Home Assistant.

Voila.

Or something.

My road to fibers with ReactPHP – Cees-Jan Kiewiet

The road to fibers didn’t start for me in 2021 when the fibers RFC went into vote.
Nor did it in 2019 when I started working on what would become React Parallel,
a set of packages to me working with threads in ReactPHP quick and easy. (A goal which I managed to hit IMHO.)
It started when I joined the ReactPHP team to create the filesystem component and all we had where promises.

Waved fibers into a near pattern

Photo by Pixabay from Pexels

PHP 8.2.0 Alpha 3 available for testing – PHP: Hypertext Preprocessor

The PHP team is pleased to announce the third testing release of PHP 8.2.0, Alpha 3. This continues the PHP 8.2 release cycle, the rough outline of which is specified in the PHP Wiki. For source downloads of PHP 8.2.0 Alpha 3 please visit the download page.Please carefully test this version and report any issues found in the bug reporting system.Please DO NOT use this version in production, it is an early test version. For more information on the new features and other changes, you can read the NEWS file, or the UPGRADING file for a complete list of upgrading notes. These files can also be found in the release archive. The next release will be Beta 1, planned for Jul 21 2022.The signatures for the release can be found in the manifest or on the QA site.Thank you for helping us make PHP better.