Redownloaded the Tetris app last night. Opened it this morning, clicked past the login prompt, hit the giant play button, got a few triples, and apparently beat the level?? Which did some bonus animation and then played an ad???

I have uninstalled the Tetris app.


Finally checked my PO Box after way too long. Thanks for the sticker, @jean!

A Micro-dot-blog sticker on top of a laptop next to a Smolblog sticker

“Lisdexamfetamine” scans to “God Bless America.” I can do no more with this cursed knowledge.


Something I haven’t seen offered for the Apple “Scary Fast” event: the new whole-console CarPlay they showed off last year.

Another possibility: F1 on Apple TV. 🏎️


Said goodbye to our dog Toby yesterday. We’ve had him since he was 3 months old, and he’s literally grown alongside us for the last 13 years. The weirdest, strangest, goodest boy. May he rest in peace. More pics on Instagram.

Portrait photo of TobyPicture of me eating a gyro, an empty plate on my lap, while Toby looks on, shocked that I am not giving it to him.Toby leaping up in the snow


Nothing like a Sunday afternoon at the emergency vet, but when your normally lethargic dog can’t lie still because of the pain, you go.


Nothing like getting an alert and seeing that whoever set it up set the threshold level to 9000…


Anyone know of an end-user friendly cross-platform/cross-browser guide to installing a web app?

Google has plenty of guides… for Chrome. And MDN doesn’t get into the details (and isn’t updated for macOS Sonoma yet).


My old coworker Lax has been on his own journey of self-discovery and self-improvement, and he’s written a post summarizing what he’s learned so far. Having seen his growth is inspiring.


I’ve got a poll running on my Mastodon asking how people are getting long form content these days. Would love your input.


So I thought the Toy Story Funday Football was gonna be a fun gimmick, but…

It’s a tech demo. And not a good one at that. There’s some fun ideas here, but the real-time animation is just Not There Yet. Unknown players, bad ball spotting, wonky camera; like watching a Let’s Play of Sonic 2006.


That’s How We’re Gonna Win: Not Fighting What We Hate, Saving What We Love.

With that in mind, what do we love about social media?


Follow-up to a previous question: the WGA tightened their rules for talk shows after 2008:

…“writing” could mean anything from scripting a monologue to developing and researching questions for guests or putting those words on cue cards.

(via @KevvyC062@mastodon.social)


Trying to avoid having a “quick” manual process instead of building another setting panel or extra admin screen.

Another couple of hours now saves me:

  • The 5 min process
  • The 30 min context switch
  • The 10 min of support communication
  • Et Cetera

Every. Time.


TiPhone. To go with the TiBook I never had.


Genuine question: how is Drew Barrymore starting her show back without writers different from Conan, Colbert, and Stewart starting theirs back in 2008? I don’t remember them getting in trouble for strike breaking.


Hot take: “Don’t Fear the Reaper” actually does need more cowbell.


Bookmarking for later: Creating a card game in Tabletop Simulator.


Been a fan of Cory Godbey for years and SUPER excited to see his work in the TCG world!

Picture of the “Magic Golden Flower” card from Disney’s Lorcana trading card game

Based on Stratechery’s summary of the Disney-Spectrum dispute du jour, I’m actually pulling for Spectrum? To wit:

D: Pay us more.
S: Give us Disney+ where the good stuff is.
D: lol no
S: 😎
D: But we’ll turn off ESPN!
S: We’ll send them to YouTube ourselves.
D: But…
S: 😎


A Stupidly Simple PHP Dependency Injection Container

I’ve already written at length about dependency injection. And in the months since it’s only proven to be more helpful. But just because I got over some of my hangups about SOLID doesn’t mean I got rid of all of my bad habits.

Particularly the bad habit of deciding that even though there’s a perfectly servicable library I’m already using, I can’t ignore the persistent thought that I can do better.

So I did. I got irked by something and ended up writing my own dependency injection container.

The lay of the land

The way I’m building Smolblog’s core library at the moment separates objects into two categories:

  • Value objects contain strongly-typed information. They are (mostly) read-only, as mutation should only happen in certain places. Any methods in a Value object should be self-contained; they do not call out to other objects, Services, or dependencies. Value objects are state, not code.
  • Service objects perform actions. They can have dependencies on other services and objects and should be given those dependencies at construction. They can act on information in Value objects; those should be given to the Service when the particular method is called. Services should typically not contain data; they should be able to function as singletons (regardless of whether they actually are). Service objects are code, not state.

Having this separation has actually really helped me focus the architecture in Smolblog, and it’s kept me from making any one class too “big.”

Dependency injection containers are classes that store dependencies for other classes and can provide instances of them. Essentially, instead of creating new instances, you get new instances from the container.

Where normal code might create a service like this:

$service = new Service(db: new Database(), fetch: new HttpClient());

Using a dependency injection container, it would look like this:

$service = $container->get(Service::class);

This takes all the responsibility for knowing how to instantiate a service away from the classes throughout the application and centralizes it into one place.

Containers are a common pattern, such that there is a common interface for containers to use: PSR-11. This way, different frameworks and libraries can define containers, and other libraries can use them without having to depend on the specific behavior of specific containers. For a while, I was using Container from The League of Extraordinary Packages as the container for Smolblog.

Until I wasn’t.

Preoccupied with whether I could

Let me state two things first:

  1. I could not have built my own container at the outset. I needed to fully grasp the concept first, and that could only happen by writing my own code against another library.
  2. Nothing in this article is a dig against the League’s Container. I want to be absolutely clear on this. I’m not interested in starting drama or picking fights.

But as I got more and more into using dependency injection, especially in the very specific ways I was using it for Smolblog, I realized how… simple the concept was.

In PHP, every class has a static constant class that is simply a string of the fully-qualified class name:

namespace oddEvan\Example\Simple;

class StupidSimple {}

echo StupidSimple::class;
// oddEvan\Example\Simple\StupidSimple

Passing that string into a container’s get method will typically return an instance of that class.

Now let’s consider the constraints I have for Smolblog:

  1. The only classes with dependencies are Services.
  2. Services should be given all dependencies at construction.
  3. Services should function as singletons.

This makes our container’s logic… actually pretty simple:

  1. Have a configuration of classes and dependencies.
  2. Given a class name, check for an existing instance and skip to step 6 if there is one.
  3. If no instances, retrieve that class' dependencies.
  4. For each class in those dependencies, call step 2 with the dependency’s class.
  5. Use the dependencies to create an instance of the class and store it.
  6. Return the instance to the caller.

…I think we can do this.

Considered whether I should

That’s cool and all, but replacing an established library with my own implementation is not something to be done lightly. A well-built library, like the ones from the League, are well-tested and well-maintained by a group of people. I’m just me.

By rolling my own solution, I’m eschewing the time and effort put into the existing library. Sometimes it can look like “bloat” or “unnecessary” code, but often that code covers edge cases that aren’t immediately obvious. Some of those potential bugs can even be security concerns.

In this specific case, a lot of the code in the League’s container involves different ways to load classes into the container. Because it is a general-purpose library, it has to handle several different scenarios:

  • Singleton classes (return the same instance every time)
  • Multiple classes (return a new instance every time)
  • Set dependencies in the constructor
  • Set dependencies by method calls after construction
  • Store classes by name
  • Store classes by aliases
  • Receive an initial configuration
  • Accept changes at any time
  • Determine if a dependency is another class or a value

With Smolblog’s constraints, this list is a lot shorter:

  • Singleton classes
  • Set dependencies in the constructor
  • Store classes by name
  • Receive an initial configuration
  • Determine if a dependency is another class or a value
  • Uses named arguments

That last point is what tipped me over to writing my own container. In PHP 8, you can now use named arguments. This is a language construct I first saw in Objective-C that Apple carried over into Swift, and understandably so. It makes method calls much more readable, especially if they have many optional parameters. Let’s start with an obtuse function:

make_superhero('Larry', 'Larry-Boy', 'Archibald', 3, false);

With named arguments, not only is it clearer what argument is what, but the order is no longer significant:

make_superhero(
  super_name: 'Larry-Boy',
  num_episodes: 3,
	citizen_name: 'Larry',
  assistant: 'Archibald',
  can_super_size: false,
);

I’ve been using named arguments extensively in Smolblog, and I wanted that flexibility in my container. And wanting that feature is ultimately what let me give myself permission to write my own container. It wasn’t—and isn’t!—enough just to want “less code”; there has to be a reason for me to write my code.

So let’s get to it.

Level 1: it begins

We’ll start with a naive implementation just to get an idea of where we are, a simple configuration and handler.

Let’s set up some pretend services first:

class DatabaseService {
  public function __construct() {
    $this->connection = new DatabaseConnection('db://user:pass@server/db');
  }
  //...
}

class UserService {
  public function __construct(private DatabaseService $db) {}
  //...
}

class UserApiService {
  public function __construct(private UserService $users) {}
  //...
}

For configuration, we’ll create an array of arrays. Each array will contain a class' dependencies, and we’ll key that array to the class' name:

$config = [
  UserApiService::class => [
    'users' => UserService::class,
  ],
  UserService::class => [
    'db' => DatabaseService::class,
  ],
  DatabaseService::class => [],
];

And now, our container:

class Container implements Psr\Container\ContainerInterface {
  private array $instances = [];
  
  public function __construct(private array $config) {}

  public function has(string $id): bool {
    return array_key_exists($id, $this->config);
  }
  
  public function get(string $id) {
    // Check if $id is in the configuration.
    if (!$this->has($id)) { throw new ServiceNotFoundException($id); }
    
    // If we don't already have an instance, create one.
    $this->instances[$id] ??= $this->instantiateService($id);
    
    // Return the instance.
    return $this->instances[$id];
  }
  
  private function instantiateService(string $id) {
    // Get the listed dependencies from the container.
		$args = array_map(
			fn($dependency) => $this->get($dependency),
			$this->config[$id]
		);

		return new $service(...$args);
  }
}

Simple! But these are hardly real-world conditions.

Level 2: Other Parameters

Now let’s say we want to make DatabaseService more resilient. Instead of having a hard-coded database connection string, we’ll pass one into the constructor:

class DatabaseService {
  public function __construct(string $connectionString) {
    $this->connection = new DatabaseConnection($connectionString);
  }
  //...
}

Now we just add that string to our configuration… wait…

$config = [
  //...
  DatabaseService::class => [
    'connectionString' => 'db://user:pass@server/db', // This is ambiguous
  ]
];

Remember that the class constants are just strings. How is our container going to tell the difference between a class string like oddEvan\Thing\DatabaseService and db://user:pass@server/db?

  • We could check class_exists or $this->has() to see if the given string represents a class or a value.
  • We could have some way of tagging an entry as a value.

Right now, I prefer explicit signals over trying to “figure out” a programmer’s intent. So to explicitly tag this as a value, we’ll use a callable (such as an arrow function) that will return the value we want. Let’s revisit our configuration with this:

$config = [
  //...
  DatabaseService::class => [
    'connectionString' => fn() => 'db://user:pass@server/db', // This is clearer.
  ]
];

Then we’ll look for callables in the container:

class Container implements Psr\Container\ContainerInterface {
  //...
  private function instantiateService(string $id) {
    // Get the listed dependencies from the container.
		$args = array_map(
			fn($dependency) =>
      	is_callable($dependency) ?
      		call_user_func($dependency) :
      		$this->get($dependency),
			$this->config[$id]
		);

		return new $service(...$args);
  }
}

Level 3: Interfaces

What about when a class takes an interface as a dependency (which it should)? Let’s add a PSR-18 HTTP client to one of our services:

class UserService {
  public function __construct(
    private DatabaseService $db,
    private \Psr\Http\Client\ClientInterface $http,
  ) {}
  //...
}

Updating the UserService configuration is easy enough since an interface also has a class constant:

$config = [
  //...
  UserService::class => [
    'db' => DatabaseService::class,
    'http' => \Psr\Http\Client\ClientInterface::class,
  ],
];

But now we need to add ClientInterface to our container somehow. We need to have some way to give an implementation in the configuration; otherwise our container will (unsuccessfully) try to instantiate an interface!

Going back to the idea of explicit signals, we actually can use strings here:

$config = [
  //...
  \Psr\Http\Client\ClientInterface::class => MyHttpClient::class,
];

Now we check the type of the class' configuration: if it’s a string, we get that class.

class Container implements Psr\Container\ContainerInterface {
  //...
  private function instantiateService(string $id) {
    $config = $this->config[$id];
    
    if (is_string($config)) {
			// This is an alias.
			return $this->get($config);
		}
    
    //...
  }
}

Note that we are very specifically not checking if $id is an interface. We want to be able to alias any class in here in case we want to replace a particular dependency with a subclass.

We kind of handwaved an implementation of that class. What if we wanted to use something specific?

Level 4: Factories

Let’s say instead of rolling our own HTTP client, we used an off-the-shelf library like Guzzle?

$config = [
  //...
	\Psr\Http\Client\ClientInterface::class => \GuzzleHttp\Client::class,
];

According to the Guzzle docs, a Client only needs a configuration array. We could do this with our existing config structure:

$config = [
  //...
	\GuzzleHttp\Client::class => [
    'config' => fn() => ['connect_timeout' => 30],
  ],
];

And this would work! But there’s a small assumption here that could turn into technical debt.

Remember that our container splats the configuration into the parameters of the class' constructor. If the maintainers of Guzzle ever change the name of the parameter from $config to something else, our container would break. One way to avoid this would be to remove the key from the dependency array, but that still feels fragile to me. What we need is a way to create an instance of Client without assuming it will have the same constraints our services have.

We can do something similar to aliases: provide a callable function that returns the entire object.

$config = [
  //...
	\GuzzleHttp\Client::class =>
  	fn() => new \GuzzleHttp\Client(['connect_timeout' => 30]),
];

Then we check for those in the container:

class Container implements Psr\Container\ContainerInterface {
  //...
  private function instantiateService(string $id) {
    $config = $this->config[$id];
    
    if (is_callable($config)) {
			// The config is a factory function.
			return call_user_func($config);
		}
    
    //...
  }
}

Finishing up

At this point, we’ve hit all the use cases I have for a dependency injection container:

  • Lazy instantiation
  • One instance per class
  • Aliases (replacing one class/interface with another)
  • Dependencies can be other classes or functions returning a value
  • Factory methods to manually create instances

There’s a few places we could go from here. We could use the Reflection API to automatically determine configuration for some simple cases. We could (should!) add more error handling for when the configuration doesn’t match the code. And if you need those features, you can build them! Or just use something off-the-shelf that already does it.

Anyway, here’s our completed configuration and container:

// Service Classes //

class DatabaseService {
  public function __construct(string $connectionString) {
    $this->connection = new DatabaseConnection($connectionString);
  }
  //...
}

class UserService {
  public function __construct(
    private DatabaseService $db,
    private \Psr\Http\Client\ClientInterface $http,
  ) {}
  //...
}

class UserApiService {
  public function __construct(private UserService $users) {}
  //...
}

// Configuration //

$config = [
  UserApiService::class => [
    'users' => UserService::class,
  ],
  UserService::class => [
    'db' => DatabaseService::class,
    'http' => \Psr\Http\Client\ClientInterface::class,
  ],
  DatabaseService::class => [
    'connectionString' => fn() => 'db://user:pass@server/db',
  ],
	\Psr\Http\Client\ClientInterface::class => \GuzzleHttp\Client::class,
	\GuzzleHttp\Client::class =>
  	fn() => new \GuzzleHttp\Client(['connect_timeout' => 30]),
];

// Dependency Injection Container //

class Container implements Psr\Container\ContainerInterface {
  private array $instances = [];
  
  public function __construct(private array $config) {}

  public function has(string $id): bool {
    return array_key_exists($id, $this->config);
  }
  
  public function get(string $id) {
    // Check if $id is in the configuration.
    if (!$this->has($id)) { throw new ServiceNotFoundException($id); }
    
    // If we don't already have an instance, create one.
    $this->instances[$id] ??= $this->instantiateService($id);
    
    // Return the instance.
    return $this->instances[$id];
  }
  
  private function instantiateService(string $id) {
    $config = $this->config[$id];
    
    if (is_callable($config)) {
			// The config is a factory function.
			return call_user_func($config);
		}
    
    if (is_string($config)) {
			// This is an alias.
			return $this->get($config);
		}

    // Get the listed dependencies from the container.
		$args = array_map(
			fn($dependency) =>
      	is_callable($dependency) ?
      		call_user_func($dependency) :
      		$this->get($dependency),
			$config
		);

		return new $service(...$args);
  }
}

I’ll leave you with this last comment. You’ll note that our simple container still adheres to the Psr\Container\ContainerInterface interface. When I’m building a service that needs a container, I’m depending on this interface, not my specific container. The only part of Smolblog that really cares about how the container works is this configuration. And because this configuration is itself so simple, I could adapt it to a different container if and when I need to.

Which is really the whole point of this exercise: loosely couple things together. Using standard interfaces and a dependency injection container means that many of the key libraries Smolblog depends on can be swapped out. And that includes the container itself.

Thanks for reading; I’ll see y’all next time.


TFW you type docker compose stop when offboarding a client.


I do not have all the time in the world, and my goals for Smolblog pretty much require it to be in PHP right now: it’s efficient, type-safe, well-supported, and a language I and many others know well.

But if I had time… I really wonder what it would look like as server-side Swift.


There’s a lot of chatter about measuring the productivity of software engineers, especially at work right now. This article gets to the heart of it: most metrics measure effort and output when we should be measuring outcome and impact.


As an exercise, I tried seeing if I could make a quick middleware for adding content provinance manifests to media uploaded to Smolblog. And I think I’ll call this concept proved.