What Is Good Code?

(Epistemic status: I am not entirely satisfied with the Merchant here, but it's the closest I've been able to get so far.)

When managing developers, and when watching conversations between developers online, I sometimes notice them arguing past each other about what constitutes "good code". One group will assert that good code must adhere to specific programming practices. The other will assert that good code is code that makes money for its author.

In the worst cases, each camp derides the other. The first camp will say the other is immature and inexperienced, begging for trouble; the second camp that the other are mindless slaves to old dogmatism, and want everyone else in their chains. The first will say the other is dangerously unprofessional; the second will reply "Don't care: shipping and billing!"

In the course of trying to work through the arguments on each side, so that I can explain them to myself and others, I have started using the names "Priest" and "Merchant" as categories to help give me a handle on the attitudes expressed.

While the following descriptions are necessarily caricatures, I think they capture some recognizable qualities of the Priest and Merchant: what they care about most, their goals and focus, their time preferences, and their blind spots. In each case we start with the question, "What is good code?"

The Priest

When you ask the Priest "what is good code?", his answers will have this flavor:

  • Good code is SOLID.

  • Good code is well-abstracted and well-encapsulated.

  • Good code is well-separated into appropriate layers and concerns.

  • Good code is cleanly organized and architected.

  • Good code has tests -- especially unit tests.

There are similar variations on these answers; they reveal that the Priest is program oriented. He cares first and foremost about the code for its own sake.

The overarching goal of the Priest is holiness -- sometimes known as "code quality," or "correctness," or "conformance to standards."

The Priest is focused on peer satisfaction -- that is, on the satisfaction of other Priests who review his code. It is other Priests who will determine how well he has achieved holiness in his code.

The Priest has a preference for the long-term time horizon. He is driven to make working with his code frustration-free for the many years he expects it to exist.

One problem with the Priest-like desire for holiness in code, is that it leads into holiness spirals. Priests engage in arguments about whether sufficient code holiness has been achieved -- and of course, the answer is always that the code is not holy enough.

Each Priest, to demonstrate his own holiness, is eager to point out how his code is holier than yours, and to explain how yours could become even holier than it is. There is always a way for your code to be more SOLID. There are ever more finely-tuned abstractions to implement. The testability, and the tests themselves, can always be improved. The spiral towards more-perfected holiness never ends.

The Priest recognizes no boundary or limiting principle to coding for holiness -- why would a Priest ever volunteer to be less holy than possible?

The Merchant

When you ask the Merchant "what is good code?", his answers will have a different flavor:

  • Good code is code that makes money -- the sooner, the better!

This answer, and its variations, reveal that the Merchant is product-oriented. He cares first and foremost about the product being marketed, not about the code behind the product.

The overarching goal of the Merchant is market success -- whether in financial terms, or in terms of beating out competitors.

The Merchant is focused on customer satisfaction -- that is, the satisfaction of people who use the product, and do not ncessarily care about how the code behind the product operates. It is the customers who will determine the market success of his product.

The Merchant has a preference for the near-term time horizon. He is concerned with shipping code sooner rather than later, so that the product can achieve market success more quickly, and so that product iterations through the market are more responsive. Every minute of coding effort that does not line up directly with market success through customer satisfaction is a minute of coding effort to be avoided.

One problem with the Merchant-like desire for quick market success is that it leads quickly to technical debt. Speed in getting product to market means that little attention is paid to longer-term program concerns, which makes the product more difficult to improve over time. The codebase becomes resistant to change, which means the product cannot respond quickly to changed conditions for market success. This means living with the product and the pain it produces, abandoning the product and moving on to another one, or rewriting it entirely -- again, as quickly as possible.

The Merchant recognizes no boundary or limiting principle to coding for market success -- why would a Merchant ever volunteer for less market success than possible?

Which Is Better?

This is the old program-vs-product dichotomy again. In some ways it represents an argument about primacy: which mode is to dominate the other, the Priest-like or the Merchant-like?

The program-oriented developer (the Priest) wants a good codebase on which the product is founded. The product-oriented developer (the Merchant) wants a good product to present to the market.

But which approach is the right one? Or at least, which one is more pragmatic than the other -- which is the more practical approach?

Both the Priest and the Merchant will defend their attitude as being "practical." However, to quote Rand: "What is 'practical' depends on what you want to practice."

Obviously, says the Priest, coding for the long-term is practical. It helps to keep the codebase lower-cost in the end, even if it takes longer to ship to production. The venture will be better for it over all.

And obviously, says the Merchant, coding for the near-term is practical. The longer it takes to get the product to market, the longer it takes for the venture to succeed. There won't be a long-term if we don't make it through the near-term.

The Priest is OK with shipping later, to avoid foreseeable technical debt in advance, since avoiding technical debt is clearly a practical approach.

The Merchant is eager to ship sooner, and deal only with actually-realized technical debt later, since achieving market success earlier is clearly a practical approach.

The Priest wants to "do things the right way" to satisfy other Priests. The Merchant wants to "do things the right way" to satisfy the market.

The Priest will try to argue for excellence in the program according to various quality measures. The Merchant will argue that mass appeal in the market is itself a form of excellence in the product.

For just about any variation of these kinds of arguments, both the Priest and the Merchant have strong but conflicting cases.

Resolving The Conflict

What occurs to me is that both of these attitudes or aspects are simultaneously present in developers. The difference is not that one attitude or the other is present or absent, but that one aspect or the other is emphasized or de-emphasized by each developer to a different extent.

Stated differently: it is not that a developer is a Priest or a Merchant. It is that each developer emphasizes the Priest-like and Merchant-like aspects of his approach to different levels.

One developer may give the vast majority of emphasis to his Priest-like desires. Conversely, another will much more strongly emphasize the Merchant-like aspects of his personality. Even so, every Priest exhibits some Merchant behaviors once in a while, and every Merchant acts like a Priest sometimes. This is because doing so 100% either way leads to trouble.

On the Merchant side, if it's all product all the time, you end up with a program that is a mess, and the product cannot continue -- at least, not without greatly increased frustration and pain while working with the code.

On the Priest side, if it's all program all the time, you never get a product to market -- or at least, it takes more time than you have available, and the codebase is discarded when the venture fails.

A Priest-only developer would never ship a product, because the program can never be holy enough. A Merchant-only program would be entirely unreadable by others, since the program behind a successful product need not conform to the expectations of other developers.

Yet the Priest ships code to market (though perhaps not as early as the Merchant), and Merchant code conforms to expectations (though perhaps not as well as that from the Priest).

So it must be that every developer possesses at least some amount of each aspect. This means the two aspects are each in tension with each other. And whereas neither of the two aspects has a limiting principle of its own, each aspect can act as a limit on the other.

As an exercise, try to see which of those aspects you emphasize more strongly. Then, consciously practice consulting the other aspect -- in doing so, you may be able to more effectively evaluate tradeoffs when deciding when code is not yet holy enough, or for when it would be better to ship now and deal with technical debt later.

Then the Merchant aspect might be able to accept some more non-product efforts for the sake of the program. And the Priest aspect might be able to accept some more unholiness in the program for the sake of the product.


The "quality of product versus quality of program" topic looks like a running theme with me; here are some other posts on the subject.

Payload-Interop 1.0.0 Released

I am happy to announce that after one change from its public review period, the payload-interop specification has been released as 1.0.0 stable!

From the README:

The Domain Payload Object pattern was first described by Vaughn Vernon at https://vaughnvernon.co/?page_id=40

Whereas a Data Transfer Object provides properties found on domain objects, a Domain Payload Object provides entire domain objects. Its purpose is to transport those domain objects across the architectural boundary from the domain layer to the user interface layer.

The Domain Payload Object is not for intra-domain use. It is only for transporting a domain result across the domain layer boundary back to the calling code, typically the user interface layer.

Further, the Domain Payload Object is independent of any particular user interface. As part of the domain layer, it should have no knowledge of HTTP or CLI contexts.

This project defines only a reading interface, so that any user interface code can know how to get both the result and the status out of the Domain Payload Object.

This project does not define a writing or mutation interface. Creation and manipulation of Domain Payload Objects are core application concerns, not user interface concerns. The domain-specific nature places it outside the scope of an interoperability specification.

Try it out on your next project!

No, Jesus Was Not A Socialist

Christians are commanded in Scripture to love, to pray, to be kind, to serve, to forgive, to be truthful, to worship the one God, to learn and grow in both spirit and character. All of those things are very personal. They require no politicians, police, bureaucrats, political parties, or programs.

“The poor you will always have with you, and you can help them any time you want,” says Jesus in Matthew 26:11 and Mark 14:7. The key words there are you can help and want to help. He didn’t say, “We’re going to make you help whether you like it or not.”

In Luke 12:13-15, Jesus is approached with a redistribution request. “Master, speak to my brother that he divideth the inheritance with me,” a man asks. Jesus replied, “Man, who made me a judge or divider over you?” Then he rebuked the petitioner for his envy.

Christianity is not about passing the buck to the government when it comes to relieving the plight of the poor. Caring for them, which means helping them overcome it, not paying them to stay poor or making them dependent upon the state, has been an essential fact in the life of a true Christian for 2,000 years. Christian charity, being voluntary and heartfelt, is utterly distinct from the compulsory, impersonal mandates of the state.

Via https://www.washingtonexaminer.com/opinion/no-jesus-was-not-a-socialist.

Implicit Bias Is Pseudo-Scientific Nonsense

Via Ted Frank we have this:

The implicit association test ... is an excellent example [of the replication crisis in social "science"]. Banaji and Greenwald claim that the IAT, a brief exercise in which one sits down at a computer and responds to various stimuli, measures unconscious bias and therefore real-world behavior. If you score highly on a so-called black-white IAT, for example, that suggests you will act in a more biased manner toward a black person than a white person. Many social psychologists view the IAT, which you can take on Harvard University’s website, as a revolutionary achievement, and in the 20 years since its introduction it has become both the focal point of an entire subfield of research and a mainstay of diversity trainings all over the country. That’s partly because Banaji, Greenwald, and the test’s other proponents have made a series of outsize claims about its importance for fighting racism and inequality.

The problem, as I showed in a lengthy rundown of the many, many problems with the test published this past January, is that there’s very little evidence to support that claim that the IAT meaningfully predicts anything. In fact, the test is riddled with statistical problems — problems severe enough that it’s fair to ask whether it is effectively “misdiagnosing” the millions of people who have taken it, the vast majority of whom are likely unaware of its very serious shortcomings. There’s now solid research published in a top journal strongly suggesting the test cannot even meaningfully predict individual behavior. And if the test can’t predict individual behavior, it’s unclear exactly what it does do or why it should be the center of so many conversations and programs geared at fighting racism.

From https://nymag.com/intelligencer/2017/12/iat-behavior-problem.html.

Payload-Interop Public Review Period

The payload-interop project defines an interoperable interface for reading the domain result and domain status from a Domain Payload Object, whose purpose is to transport domain objects across the architectural boundary from the domain layer to the user interface layer.

After a long incubation period and much research, the project has finished internal deliberation and is now inviting wider public review. You can go straight to the code here.

Please send your comments, questions, and critiques as Github issues. Pull requests are also welcome.

Thanks, and happy New Year!

Atlas, PostgreSQL, and RETURNING

One of the powerful features of PostgreSQL is its RETURNING clause. For example, if you have a table like this ...

CREATE TABLE articles (
    article_id SERIAL PRIMARY KEY,
    title      VARCHAR(255) NOT NULL,
    -- ...

... and you insert a row using Atlas.Query with a RETURNING clause ...

/** @var $insert \Atlas\Query\Insert */
    ->columns(['title' => 'My Article'])

$pdoStatement = $insert->perform();

... then you can fetch the RETURNING column values from the PDOStatement result:

$returning = $pdoStatement->fetch(PDO::FETCH_ASSOC);
// ['created_at' => (the postgresql timestamp)]

If you have an Atlas.Table data gateway for that table, you can make use of RETURNING in your TableEvents classes to populate database-provided values into your Row objects automatically.

To do so, add a RETURNING clause in the modifyInsertRow() method, then retrieve the values into the Row in the afterInsertRow() method:

// ...

class ArticleTableEvents extends TableEvents
    public function modifyInsertRow(
        Table $table,
        Row $row,
        Insert $insert
    ) : void

    public function afterInsertRow(
        Table $table,
        Row $row,
        Insert $insert,
        PDOStatement $pdoStatement
    ) : void
        $returning = $pdoStatement->fetch(PDO::FETCH_ASSOC);
        $row->created_at = $returning['created_at'];

Now when a Row gets inserted through the Table data gateway, the created_at value will be populated automatically.

/** @var $tableLocator \Atlas\Table\TableLocator */
$articleTable = $tableLocator->get(ArticleTable::CLASS);
$article = $articlesTable->newRow();
$article->title = 'bar';
echo $article->created_at; // the postgresql timestamp

You can do the same for updates as well, using modifyUpdateRow() and afterUpdateRow().

Bonus: becuase it uses both Atlas.Table and Atlas.Query, this functionality is available "for free" in Atlas.Orm!

Controllers are Services

tl;dr: Contra my previous opinion, controllers are Service objects; my understanding of a "Service", regarding DI/SL Containers, was incorrect. However, I do continue to opine that it is better use a Factory to get new Controller instances, rather than getting them from the Container directly in a non-Factory object. All that and more in this followup post.


My previous post generated a great discussion on Reddit between myself and /u/ahundiak, which I suggest you read in its entirety. To summarize:

  • I thought of Services in a Container as objects that are retained for reuse throughout the application; e.g. a PDO instance, a logger, etc.

  • ahundiak disagreed, and pointed to the Symfony explanation of a Service as "objects that do something", even when not shared throughout the system.

  • I found that explanation unsatisfying and not well-defined.

  • However, ahundiak also said "Symfony only creates service definitions for objects which are actually injected somewhere." That piqued my interest, because it got closer to the heart what I was trying to express.

From there we ventured a bit further afield.


Based on that conversation, I did a couple hours' of research, using variations on "what is a service object" (and you get some unexpected search results in a couple of cases). In the end, though, I found myself at the Misko Hevery article To new or not to new.

I have taken Misko's advice before, particularly from his article on How to Think About the “new” Operator with Respect to Unit Testing; I believe I refer to it in MLAPHP. In that article, his final summary point is that your classes should either have application logic in them, or have new operators, but not both.

The idea is that object construction is a concern to be separated from object use. The end result is that you put all object construction into Factories, and anything that needs to create an object uses the Factory instead of the new operator.

In his later article on "To new or not", Misko lays out definitions around two distinct groups of objects within any application. Lightly edited for comprehension and emphasis, he says:

  • "Injectables" are objects which you will ask for in the constructors and expect the DI framework to supply. ... All external services are Injectables. ... Sometimes I refer to Injectables as Service Objects, but that term is overloaded.

  • There are a lot of objects out there which DI framework will never be able to supply. Let's call these “Newables” since you will be forced to call new on them manually. ... Newables are objects which are at the end of your application object graph. ... Sometimes I refer to Newables as Value Object, but again, the term is overloaded.

  • It is OK for Newable to know about Injectable. What is not OK is for the Newable to have a field reference to Injectable.

Now that is something I can wrap my head around. To a first approximation, Newables/Values are leafs on the object graph; everything else on the path to a Newable/Value is an Injectable/Service.

I think that very nicely backs up ahundiak's reference to Symfony's explanation about Service objects, with some additional and welcome rigor.

Misko goes on to define some rules-of-thumb around Injectables/Services and Newables/Values. Paraphrasing and summarizing:

  • An Injectable can ask for primitives (int, float, string, etc.) and other Injectables in its constructor, but never a Newable.

  • Conversely, a Newable can ask for primitives and other Newables in its constructor, but never an Injectable.


Based on Misko's definition (which is obviously better than my prior one) Controllers are Services that a DI container can provide. Mea culpa.

However, I continue to stand by a corollary opinion I expressed in the previous article: that you should not retain a Controller instance for reuse inside the Container. Instead, you should use a Factory to create the Controller instance. Further (and I did not say this explicitly) you should typehint on that Factory instead of the Container itself.

Now, it is well within the proper scope of a Container to be used as a Factory. A Container plays two roles: that of Factory (to create new object instances) and of Registry (to retain already-created instances for shared use). So in a Dispatcher, for example, you could do this to run a Controller based on Route information:

class Dispatcher
    protected $container;

    public function __construct(Container $container)
        $this->container = $container;

    public function dispatch(Route $route)
        $class = 'App\Http\Controller\\' . $route->getController();
        $object = $this->container->new($class);
        $method = $route->getAction();
        $params = $route->getParams();
        return $object->$method(...$params);

That's not bad -- by calling new() on the Container, you can tell the Container is being used as a Factory, not a Registry. (Some Containers will allow you to configure certain types always to be new instances, so that calling get() will always return a new instance, but I am not a fan of that idiom.)


However, if you you consider typehints as communication, then typehinting on Container communicates "the Dispatcher will need to create or locate anything the Container can provide." It's using the Container as a Service Locator, and in general we ought to prefer Dependency Injection.

As an alternative, we might typehint on a ControllerFactory instead. That kind of typehint communicates a narrower requirement: "the Dispatcher will need to create new Controller instances."

Putting together a ControllerFactory is easy enough ...

class ControllerFactory
    protected $container;

    public function __construct(Container $container)
        $this->container = $container;

    public function new(string $suffix) : Controller
        $class = 'App\Http\Controller\\' . $suffix;
        return $this->container->new($class);

... and then the Dispatcher can depend on that, instead of the Container:

class Dispatcher
    protected $controllerFactory;

    public function __construct(ControllerFactory $controllerFactory)
        $this->controllerFactory = $controllerFactory;

    public function dispatch(Route $route)
        $object = $this->controllerFactory->new($route->getController());
        $method = $route->getAction();
        $params = $route->getParams();
        return $object->$method(...$params);

Of course, the ControllerFactory itself now depends the Container -- the dependency on the Container just moved, it didn't disappear. Even so, this arrangement is less objectionable for a couple of reasons:

  • The "creation of objects" concern remains separated, per Hevery's guideline "to have either classes with logic OR classes with new operators."

  • The Controller instances being created really are likely to need a wide range of many different object injections, including shared objects from the Container. The typehint of Container is an accurate communication here.


As often happens, the answer leads to more questions. In this case, I begin to wonder about the kinds of objects that belong in a Container, per Misko's definitions. For example, is an HTTP Request object something that ought to be created and retained within a Container? I can think of no Request implementations that depend on Injectables; it seems more like a Newable, per Misko, in that it exists as a leaf on the object graph.

Well, I will leave that for another day. Many thanks to ahundiak for correcting me, or least leading me to correction, on this topic.

See the Reddit conversation on this post here.

Controllers are not Services

Controllers (in webbish MVC) and Actions (in ADR) are not Services. That is, their instances are not shared throughout the system. They are not shared objects used in many different places. Each is created and used only once, in one place, usually as the result of routing logic.

As such, Controllers and Actions should not be defined as "Services" in Dependency Injection containers. They should not be in a Service Locator, either.

Instead, try to use a Factory to create the one Controller or Action that you need for the interaction.

UPDATE (7 Dec 2019): Based on the Reddit discussion below, I have reason to revise my opinion. Look for a followup post in the next couple of days.

See the Reddit conversation on this post here.

Teams do not accomplish anything of genuine intellectual value

[T]eams are for normies, for neurotypicals, for trash people who can’t retain multiple levels of variable dereferencing in their heads while coding.

Teams do not accomplish, and have never accomplished, anything of genuine intellectual value. The history of scientific progress is a history of individuals.

Yes, you need a "team" to actually assemble the atomic bomb or the Intel Itanium or a commercial software product. You don’t need a team to conceive it and do the mental heavy lifting.

The effective IQ of a team is the same as the lowest IQ in the team; the productivity of the team is a minor percentage of the productivity you could get from its smartest member working alone.

Every once in a while you will see one brilliant person be inspired by another brilliant person in the near vicinity. This happens once for every hundred million times a "team" crushes the abilities of its members.

Science, even computer science, is not football.

I find myself sympathetic to this outlook. Via Weekly Roundup: The Passion Of Saint iGNUcius Edition.

Speaking at Bulgaria PHP 2019

I mentioned a couple of days ago that, as favors to friends, I am temporarily relaxing my self-imposed restriction on speaking at conferences I have to fly to. The first was EEConf 2019; the second is Bulgaria PHP.

As the opening talk of day 2, I'll be giving a brand-new presentation on "Rethinking What You Think You Know." It's nominally about the road to Action Domain Responder, but I expect to include more than just that. See you there!

Don’t Believe All Women, Because Some Are Terrible

"Maybe we should treat people like people:"

[I]if we are required to believe every woman who makes an accusation, then every allegation, simply by the act of making it, becomes “credible.” This leaves little room for raising questions about honesty and due process, and plenty of space for ideological distortion.

And since ideology abhors nuance, simple narratives come to dominate the discussion when an allegation is made (in Kavanaugh’s case: beer-drinking private school boy = bad; beer-drinking private school girl = good). And anything that contradicts the narrative is explained away or ignored (such as, being drunk is damning evidence if you’re the accused man, but exculpatory if you’re the woman who made the allegation).

Via https://www.commentarymagazine.com/politics-ideas/dont-believe-all-women-because-some-are-terrible/.

Speaking at EEConf 2019

I have been lax in pointing out that I will be speaking at EEConf 2019 in October. Here's a preview of the topic, and some other background:

I have for some years now sworn off speaking at conferences that I have to fly to. I hate air travel, not only because of the experience itself but because I often get sick, and once brought illness home to my family. But in this case (and in one other to be mentioned later!) I am making an allowance as a favor for a friend.

See you at EEConf!

Knitting Has Always Been Political

This is a couple of months old, but it echoes the totalitarian SJW attitudes in software as well:

Ravelry, a popular website for knitters and crocheters, took a political stand when it announced that it was banning content that supports President Trump, in what it said was a resolution against white supremacy.

“We cannot provide a space that is inclusive of all and also allow support for open white supremacy,” the site said in a statement explaining the decision. “Support of the Trump administration is undeniably support for white supremacy.”

The policy applies to content on the site, including knitting patterns and forum posts, but not to people, according to Ravelry, which said it still welcomed Republicans and those with conservative political views. “You can still participate if you do in fact support the administration, you just can’t talk about it here,” the statement said, adding that “hate groups and intolerance are different from other types of political positions.”

To the SJW, the totality of everything is political. There is no non-political space. All your hobbies and interests must be held hostage to social justice.

Via NYTimes with Twitter commentary here.

Farewell, Wendy

Wendy Lou Jones, aged around 17, passed away this morning. She was good when she was not being naughty. Clever, resilient, thiefy little dog. :-)




Moving Away From Google

In an effort to extract Google's tentacles from my life, and to give them as little feedstock as possible, I am no longer using my Gmail account as a primary contact address. Contact me at my pmjones.io address instead. Likewise, my paul-m-jones.com address is no longer being hosted by Google.

It was surprisingly time-consuming to find a reasonable alternative host that would support some other functionality (more on that later). After some preliminary attempts, I settled on Namecheap. Believe it or not, I am using a shared hosting plan, which supports another effort I am trying out (more on that in another post).

Immutable Objects for PHP

The new immutablephp/immutable package provides truly immutable value objects and an immutable value bag, along with base Immutable and ValueObject classes for your own objects. It helps to prevent against the oversights described by my earlier article Avoiding Quasi-Immutable Objects in PHP. (Of course, it cannot prevent you from deliberately adding your own mutable behaviors, or from using reflection to mutate an object from the outside. If you do those things, you get what you deserve.)

Its base Immutable class protects against common oversights in PHP regarding immutables:

  • It defines final public function __set() and final public function __unset() to prevent adding and mutating undefined properties.

  • It defines final public function offsetSet() and final public function offsetUnset() to prevent adding and mutating values via ArrayAccess.

  • It prevents multiple calls to __construct() to re-initialize the object properties.

Further, the base ValueObject class with() method checks the types of all incoming values to make sure they are themselves immutable. It does so via static methods on the Type class.

The Type class recognizes scalars and nulls as immutable. All other non-object values (such are resources and arrays) are rejected as mutable.

When it comes to objects, the Type class recognizes anything descended from Immutable as immutable, as well as DateTimeImmutable. To allow Type to recognize other immutable classes, call Type::register() with a variadic list of fully-qualified class names that you want to treat as immutable.

After installing immutablephp/immutable via Composer, you can use it to create your own immutable Value Objects, or consume one of the provided Value Objects:

There is also an immutable value Bag for an arbitrary collection of immutable values; it can be useful for immutable representations of JSON data.

If you need truly immutable objects for your PHP project, give immutablephp/immutable a try!

Read the Reddit commentary on this post here.

Clarifications to a review of Action Domain Responder

Herberto Graça, as part of his Software Architecture Chronicles, saw fit to review the Action-Domain-Responder pattern that I have written so much about. It’s a good writeup, and you should read the whole thing, but it is off in a couple of places. I sent Herberto a followup email with some minor corrections and clarifications; I repeat it below (with light editing) for posterity.

The ADR pattern was created by Paul M. Jones

I prefer to say, not that I “created” the pattern, but that I “recognized and named” it in 2014. Slim, for example, already had single-action controllers at that time, and the various Domain Logic patterns previously existed as well. If I “created” anything, it was the idea of the Responder; that is, of treating the Response-building work as a completely separate concern. But even that is just an application of Separated Presentation.

to adjust MVC to the context of web REST APIs.

APIs were one aspect, but the origin was more in the typical HTML page-based interface. For example, the word “Action” in Action Domain Responder was inspired both by “action” methods in Controllers, and by the “action” attribute on <form> tags in HTML interfaces.

because the idea is to adapt the MVC pattern to the context of an HTTP REST API, the Action (Controller) names are mapped to the HTTP request methods so we will have Actions with names as Get, Post, Put, Delete

Well, you can do that, but it’s not strictly necessary. I think it would be more accurate to say that it’s typical for an Action to be mapped to a route, and for the routes themselves to be mapped to GET/POST/PUT/DELETE etc.

As an organization pattern, all Actions on a resource should be grouped under a folder named after the resource.

That is one reasonable approach, but it’s not dictated by ADR. Alternatively, instead of grouping Actions by resource, you might organize them by Use Case, or by the intended Interactions with the underlying application.

This pattern was thought of specifically for REST APIs, so in this form it is not refined enough to be used in web applications with an HTML interface (ie. what would be the name of the action to show a form, prior to creating a resource?) … I think the pattern can easily be extended so that it is fully usable with an HTML interface: We can emulate some extra HTTP methods specifically to handle the HTML requests that a REST API does not have.

Given my notes above, I think it’s fair to say that new HTTP methods are not really needed. What is needed, as Herberto already points out, is a way to “show a form prior to creating a resource” (among other things).

Because the Actions do not necessarily map one-to-one with HTTP methods, it’s easy enough to specify that GET /{$resource}/new (or something similar) routes to an Action that gets default values for that resource from the Domain, then hands off to a Responder that builds the form HTML. You can see an example of that here:

You might route the first as GET /blog/new and the second as POST /blog. (That idea appears to originate from Ruby on Rails and has been imitated elsewhere.)

Aside from those notes, Herberto’s article is a fair summary and description of ADR. I’m glad he put it together.

You can read more about Action Domain Responder at http://pmjones.io/adr.

Open Source and Squeegee Men

It is the Christmas season, and Christmas is a time for giving gifts: to family, to friends, and to colleagues. Some even give gifts to strangers they may never meet, as an expression of charity or of generosity, or in thankfulness for their own abundance.


When someone receives a gift, they do well to return something to the giver … but the return is not primarily a material one. The recipient does the right thing to compensate the giver, not first with money or merchandise, but most importantly with gratitude and good will.


I think open source software is little like the giving and receiving of gifts.

Read the rest at https://24daysindecember.net/2018/12/11/open-source-and-squeegee-men/.

Done With Twitter

I've tried "taking a break" but I always come back. Time to be done with it: account deactivated.

What to Do About The Linux COC

(An open letter to the Linux community.)

You need to decide for yourself how dire your circumstances are now that the Contributor Covenant Code of Conduct (CCCOC) is in place. If you think the Social Justice capture of the Linux kernel is all-well-and-good, you need do nothing. Everything is running right on schedule.

But if you think this heralds the end of Linux as anything resembling a meritocracy (however flawed), as well as the beginning-of-the-end of a project that you love and depend on, then you need to take action. Nobody is coming to save you. You’re going to have to save yourselves.

Whereas the Social Justice Attack Survival Guide is a good defense, playing only defensively leaves the non Social Justice cohort of the Linux community indefinitely vulnerable to attack, individually and collectively. To end that vulnerability, you will need to achieve something very difficult. You will need to drive the rejection of the CCCOC, and demand restoration of the Code of Conflict (or perhaps the outright rejection of anything resembling a Code of Conduct at all).

You may ask, “Why should I have to do anything? They’re the ones who suck! They should do the right thing themselves, I shouldn’t have to make them.” And in a way, that’s all true – but it doesn’t matter. You can’t wait for “the management” to “come to their senses.” They have no incentive to change. You have to motivate them to change.

Here’s one form of motivation:

You go on strike.

Don’t resign. Don’t delete or disable your accounts. Keep them, because you’ll need them when this is over (if it ever is over). But stop volunteering:

  • Stop donating money. Email them and say how much you have given in the past, and why you won’t give any more.

  • Stop donating time and effort to commits. Email the project and list your commits, fixes, and features, and say why you won’t be committing any more.

  • Stop answering questions and writing documentation. Instead, respond along the lines of “I’d love to help … once the CCCOC is removed.”

  • If you are paid to work on the kernel, stop doing that work. Tell them why you are going on strike.

Go on strike, and speak up about having gone on strike, until the CCCOC is reverted and the Code of Conflict is put back in place. The longer you keep volunteering, the longer it looks like you are OK with the CCCOC.

They cannot survive (at least, not as easily) without your volunteer efforts. Stop volunteering, and speak out as to why you are stopping. Be prepared to do it for longer than you think you’ll have to.

Threats to their cash flow, to their free-resource flow, will be a serious motivator for them to listen to you.

That’s a starting point. If they need further motivation, their actions between now and later will make the followup approach more obvious.

Do it today. Not tomorrow, not next week, not “later” – today. The longer you wait, the more inertia will build up against you.

Now, I have to warn you: the consequences for you going on strike might be overwhelming. You are likely to find yourself the target of Social Justice, with all that entails. Each of you has to decide for yourself if you want to deal with that kind of fallout, and I’m not kidding when I say it is psychologically and emotionally draining. But you also have to decide for yourself if you want to just sit back and let Linux be co-opted in this way. The choice is yours.

And if you see someone else going on strike with you, support them.

Good luck.

Social Justice Attack Survival Guide

With the recent Social Justice capture of the Linux kernel, many in the open source world may find this guide from Vox Day to be useful. I present it here as a public service; you can find the original PDF here. If you are interested in how to resist the introduction of the Contributor Convenant and other Social Justice derived Codes of Conduct, you may wish to watch this presentation or see the slides for it.

This survival guide is intended for the use of the individual who finds himself under attack by Social Justice Warriors for standing up against them and their ever-mutating Narrative. It may be freely distributed so long as it is correctly credited to SJWs Always Lie: Taking Down the Thought Police by Vox Day.

The eight stages of the SJW attack sequence are as follows:

  1. Locate or Create a Violation of the Narrative.

  2. Point and Shriek.

  3. Isolate and Swarm.

  4. Reject and Transform.

  5. Press for Surrender.

  6. Appeal to Amenable Authority.

  7. Show Trial.

  8. Victory Parade.

The rest of this guide consists of the correct way to respond to an SJW attack once it has been identified, ideally at the earliest stage possible. Please note that the eight stages of response do not correspond directly to the eight stages of the SJW attack sequence.

1. Rely on the Three Rs: RECOGNIZE it is happening. REMAIN calm. REALIZE no one cares.

The first thing to do when attacked by SJWs is to recognize that you are under SJW attack, remain calm, and realize that no one else cares. You need to understand that the attack is happening, accept that is happening, and refrain from the temptation to try to make it not be happening. Do not panic! Don't go running to others for help or sympathy, don't try to convince everyone around you how outrageous or unfair the accusation is, and don't explain to anyone how little you deserve the way you are being treated. They don't care. They really don't. Think about how little you cared when someone else was previously being attacked by SJWs and how little you did to support them, let alone take action to stop the attack. That's exactly how much your colleagues and acquaintances care about you being attacked, and exactly how much they are going to do to stop it.

The truth is that it doesn't matter why SJWs are attacking you. The only thing that matters is understanding that you are under attack right now and no one else is going to do anything about it.

2. Don't try to reason with them.

The second thing is to recognize that there is no way you are going to be able to reason your way out of the situation. Most people who come under SJW attack have the causality backwards. They think the attack is taking place due to whatever it is that they did or said. That's not the case. The attack is taking place because of who you are and what you represent to the SJWs: a threat to their Narrative. In most cases, the SJWs attempting to discredit and disemploy you already wanted you out long ago, and they are simply using the nominal reason given as an excuse to get rid of you. And if the attack is more the result of SJW status-seeking rather than thought-policing, that's arguably even worse, because if the motivation concerns them rather than you, there is absolutely nothing you can do about it.

The most important thing to accept here is the complete impossibility of compromise or even meaningful communication with your attackers. SJWs do not engage in rational debate because they are not rational and they do not engage in honest discourse because they do not believe in objective truth. They have no interest whatsoever in talking to you or trying to understand you, indeed, they will avoid you and do their best to minimize their communications with you while constantly talking about you and “explaining” the real meaning of your words and your nefarious true intentions to everyone else. They will also try to isolate you and cut you off from access to any relevant authority, to the media, and to neutral parties, the better to spin the Narrative without your interference. This is why it is vital that you do not agree to any confidentiality agreements or consent to keep your mouth shut while the SJW-driven “investigation” is proceeding.

3. Do not apologize.

The third thing to remember when undergoing an SJW-attack is to never apologize for anything you have done. I repeat: do not apologize. Do not say you are sorry if anyone's feelings were hurt, do not express regret, remorse, or contrition, do not say anything that can be taken as an apology in any way. Just in case I am not being sufficiently clear, do not apologize!

Normal people seek apologies because they want to know that you feel bad about what you have done and that you will at least attempt to avoid doing it again in the future. When SJWs push you for an apology after pointing-and-shrieking at you, what they are seeking is a confession to bolster their indictment. They are like the police down at the station with a suspect in the interrogation room, badgering him to confess to the crime. And like all too many police these days, the SJWs don't really care if you did it or not, they're just looking for a confession that they can take to the prosecutor.

Be aware that once they have launched an attack on you, they will press you hard for an apology and repeatedly imply that if you will just apologize, all will be forgiven. Do not be fooled! I have seen people fall for it time and time again, and the result is always the same. The SJWs are simply looking for a public confession that will confirm their accusations, give them PR cover, and provide them with the ammunition required to discredit and disemploy you. Apologizing will accomplish nothing more than hand them the very weapons they require to destroy you.

4. Accept your fate.

It is psychologically much easier to survive an SJW attack if you accept early on in the process that you are probably going to lose your job or be purged from your church, your social group, or your professional organization. Remember, if the SJWs were not confident they could take you out, they would not have launched the attack in the first place. They prey upon those they believe, rightly or wrongly, to be vulnerable. Even if you survive the attack, it's highly unlikely that your reputation will survive unscathed as there are simply too many people who are inclined to split the difference in any conflict between two parties, no matter how crazy or dishonest they know one of the parties to be.

Be prepared to be disappointed by the behavior of some of the people you believe to be your friends. But don't be angry with them or allow the anger you feel for the SJWs to be displaced onto those who have disappointed you. While they may have disappointed you with their cowardice, they are not your problem, they did not put you in the position you find yourself, and they are not your enemy. Don't take your pain and anger out on them. Reserve that for the SJWs.

5. Document their every word and action

Most of the time, SJW purges are committed at least partially outside the organization's established rules and forms. You may not be an expert, but some of the people following along will be. Make sure every step in the process, and every piece of communication you receive from them, is documented, critiqued, and publicized. They will pull out all the stops to hide their actions in order to avoid public criticism, and in some of the more egregious cases, ridicule. By forcing them to show their hand in public, you allow others to see and understand what they are really up to. This may not be sufficient to save yourself from the ongoing attack, but it will almost certainly strengthen your negotiating position and will also help prevent the SJWs from blithely repeating the process against you or someone else in the future.

Whatever you do, do not agree to any gag orders or sign any confidentiality agreements that will handicap your ability to use the documentation you have acquired to prevent them from spinning a Narrative about what happened. SJWs rely on secrecy, and once they know you have their actions documented, they will try very hard to tie your hands in a manner that will prevent you from making that information public.

6. Do not resign!

Do not resign! You must always keep in mind that their real goal is not to formally purge you, but to encourage you to quit on your own. That allows them to publicly wash their hands of the affair and claim that your decision to leave was not their fault. They will often enlist more reasonable allies to approach you and tell you that it's not possible for you to continue any more, they will appeal to your desire to avoid conflict as well as to the good of the organization, and they will go on endlessly about the supreme importance of an amicable departure. Don't fall for it. Don't do their dirty work for them. Make them take the full responsibility for throwing you out, thereby ensuring they have to suffer the unpredictable long-term consequences of their actions.

No matter how deeply the deck is stacked against you, the outcome will always be in doubt unless you resign. You always have a chance to defeat them as long as you don't quit, and perhaps more importantly, refusing to quit buys you an amount of time that you can use to find another job before they manage to disemploy you. Considering how long you can reasonably expect to draw out the process, which will usually take not weeks, but months, you will considerably enhance your chances of finding alternative employment if you do not resign.

Do not resign! There is no advantage to you in doing so. As with apologizing, resigning is only going to make matters worse, not better, despite what the SJWs will promise you. They'll assure you that it will be best for everyone if you just quietly resign and go away, that it will be better for the organization to which your past contributions are greatly appreciated, and that the one last thing you can do for it now is to avoid making an uncomfortable scene. They'll promise that if you resign, you'll be able to quickly and quietly put the controversy behind you--and the moment you resign, they will alert the media, send out a statement to the entire organization, and begin waving your scalp like a bloody flag. This is because one of their primary goals is to maintain the illusion of their irresistible power and inevitable victory, so they need to advertise their victories in order to intimidate other potential crimethinkers into falling into line.

So don't believe them when they tell you that a resignation will make all the pain and humiliation go away, because SJWs always lie! And whatever you do, don't resign!

7. Make the rubble bounce.

Whether you survive the attempted purge or whether you don't, it's very important to observe who has defined himself as an ally, an enemy, or a neutral party during the process. The choices people make will pleasantly surprise you about as often as they disappoint you. Once everyone's choices have been made clear, your task is simple. Target the enemy at every opportunity. Hit them wherever they show themselves vulnerable. Play as dirty as your conscience will permit. Undermine them, sabotage them, and discredit them. Be ruthless and show them absolutely no mercy. This is not the time for Christian forgiveness because these are people who have not repented, these are people who are trying to destroy you and are quite willing to harm your family and your children in the process. Take them down and take them out without hesitation.

If you have any SJWs working under you, fire them. If you have an SJW relying upon you for something, play dumb and assure him that he'll get it on time, then fail to deliver, all the while promising that it's going to be done next week. Above all, understand that the normal rules of live and let live are no longer in effect. The more you disrupt their activities and their daily routine, the more difficult they will find it to purge you. Assume that you are on your way out--if you've followed the previous advice given, you should already have your landing zone prepared and are only waiting for the right moment to exit--and salt the earth. Leave devastation in your wake so that it will take weeks or even months for them to try to recover from the damage of your purging.

8. Start nothing, finish everything.

Even when the initial conflict is over, the SJWs are not going to leave you alone so long as they believe you to be a potentially vulnerable threat to them. This is why you have to be prepared to continue to up the ante until they finally reach the conclusion that they cannot possibly beat you and they are better off keeping their distance. Fortunately, SJWs are highly emotional, cowardly, and prone to depression, so demoralizing them tends to be considerably easier than you might imagine. They will still hate you, but after repeatedly meeting with staunch and confident opposition, they will usually decide to leave you alone and go in search of less difficult prey.

Reward enemies who leave you alone by leaving them in peace. Reward enemies who insist on continuing hostilities with disincentivizing responses that are disproportionate to their provocations. And never forget, no matter what they do, they cannot touch your mind, they cannot touch your heart, and they cannot touch your soul.

This guide consists of selections from “Chapter Seven: What to do when SJWs attack”

SJWs Always Lie: Taking Down the Thought Police

Vox Day, Castalia House, (2015) 200 pages

Available from Amazon.com and CastaliaHouse.com

Atlas ORM Integration with Symfony

Are you using Symfony 4? Do you want to use Atlas with it? We now have a Symfony bundle and Flex recipe that makes installation and integration a breeze. Two commands and one .env file edit, and you’re ready to go:

composer config extra.symfony.allow-contrib true
composer require atlas/symfony ~1.0

Build out all your mapper files from your database tables with a single command:

php bin/console atlas:skeleton

Then let Symfony inject the Atlas ORM object in your controller or application service constructors automatically (no further configuration needed):

namespace App;

use Atlas\Orm\Atlas;
use App\DataSource\Thread\Thread
use App\DataSource\Thread\ThreadRecord;

class ApplicationService
    public function __construct(Atlas $atlas)
        $this->atlas = $atlas;

    public function fetchThreadById($thread_id) : ThreadRecord
        return $this->atlas->fetchRecord(Thread::class, $thread_id);

That’s it – you can now use Atlas for all the heavy lifting of your database work:

If you’re looking for a good persistence model data mapper, give Atlas a try!

Atlas.Orm 3.0 ("Cassini") Now Stable

I am delighted to announce the immediate availability of Atlas.Orm 3.0 ("Cassini"), the flagship package in the Atlas database framework. Installation is as easy as composer require atlas/orm ~3.0.

Atlas.Orm helps you build and work with a model of your persistence layer (i.e., tables and rows) while providing a path to refactor towards a richer domain model as needed. You can read more about Atlas at the newly-updated project site, and you can find extensive background information in these blog posts:

If you want a data-mapper alternative to Doctrine, especially for your pre-existing table structures, then Atlas is for you!

Read the Reddit commentary on this post here.

Atlas.Query: Simple. Sensible. SQL.

I am happy to announce that Atlas.Query is now stable and ready for production
use! Installaton is as easy as composer require atlas/query ~1.0.

With Atlas.Query and any PDO instance, you can build and execute your queries in a single fluent series of method calls:

use Atlas\Query\Select;

$rows = Select::new($pdo)
    ->where('id IN ', $ids)

foreach ($rows as $row) {
    // ...

If you prefer, you can exercise fine control over your PDO connection, use a query factory, or build your queries in smaller steps:

use Atlas\Pdo\Connection;
use Atlas\Query\QueryFactory;

$connection = Connection::new(

$queryFactory = new QueryFactory();

$select = $queryFactory->newSelect($connection);
$select->where('id = ', $id);

$row = $select->fetchOne();

Atlas.Query provides the full power of SQL at your fingertips …


… along with UNIONs, paging, sub-selects, inline value binding, and all sorts of fetch and yield styles.

Atlas.Query comes with INSERT, UPDATE, and DELETE builders as well:

use Atlas\Query\Insert;

$insert = Insert::new($pdo);

// insert a row ...
        'title' => $title,
        'body' => $body,
    ->raw('created_at', 'NOW()')

// ... and get back the autoincrement value:
$post_id = $insert->getLastInsertId();

Do you work on different project with different database backends? Atlas.Query lets you use the same interface for them all, while not restricting you to a common subset of functionality. MySQL, PostgreSQL, SQLite, and SQL Server are all supported explicitly.

And if you discover you need more than just a query system, you’ll have a clear refactoring path towards Atlas.Orm. If you are looking for a modern, stable, easy-to-use query system, try Atlas.Query in your project!

You can read the Reddit commentary on this post here.

Atlas 3.x ("Cassini") and PHPStorm Completion

I’m proud to announce the release of Atlas.Orm 3.0.0-beta1, along with releases of the supporting Mapper, Table, Query, Cli, and Pdo packages. (Atlas is a data-mapper for your persistence model, not your domain model, in PHP.)

The goal for this release round was “better IDE return typehinting support” and I am happy to say that it has been a great success, though it did take some substantial renaming of classes. Unfortunately, this results in a big break from the prior alpha release; if you already have alpha-based data source skeleton classes, you will need to regenerate them with the new class names. Barring the unforeseen, this marks the first, last, and only time that regeneration will be necessary.


I am not a heavy user of IDEs; I lean more toward text editors like Sublime. However, I work with people who use the PHPStorm IDE extensively, and I have come to appreciate some of its features.

One of those features is code autocompletion. For example, if you type $foo = new Foo(), and then refer to $foo later, the IDE will pop up a list of methods and properties on that object.

This is very convenient, except that the IDE has to know what class is being referenced, in order to figure out what the hinting should be. If you are using a non- or loosely-return-typed factory/locator/container, which is what everything in PHP land does, the IDE cannot know by default how to map the requested name to an actual class. In this example …

class Factory
    public static function new($class)
        return new $class();

$foo = Factory::new(Foo::CLASS);

… the IDE has no idea what the return from the new() method is, or ought to be, so it cannot give you autocompletion hints on $foo.

That idiom is exactly what Atlas MapperLocator::get() and TableLocator::get() use: the get() param is a class name, and the locator then retains and returns an instance of that class. Likewise, the overarching Atlas class methods all take a mapper class name as their first param, which Atlas uses to figure out which mapper to use for that method call.

You can typehint those methods to abstract classes or interfaces, but then the IDE will not recognize any custom extensions or overrides on the returned concrete classes. What is needed is a way to determine the return type from the get() param, rather than from the method’s return typehint.


Lucky for us, the PHPStorm IDE allows for a .phpstorm.meta.php file (or a collection of files under a .phpstorm.meta.php/ directory) where you can map the factory method inputs to their return types. (See here for the documentation.)

On working with it, though, I found it to be a little inflexible. I expected to be able to map a string directly to a literal class name, but that didn’t really work. The IDE never seemed to pick up the return typehinting. Further, with class names the way they are in the 1.x, 2.x, and 3.x-alpha releases, I would need to add a series of param-to-type mappings for every single data source class (i.e., about 10 entries for each data source type). I imagined that would become cumbersome.

To adapt to this, I decided to modify the Atlas class naming with PHPStorm’s ‘@’ metadata token in mind. The ‘@’ token, per the above documentation, gets replaced with the factory method parameter value, making it perfect as a class name prefix. However, you can’t do any string manipulations on it; you cannot, say, call substr('@', 0, -6) . 'Record' and give it “FooMapper” to get back “FooRecord”. It would have to be ‘@Record’ or nothing.

This leads to the biggest change in Atlas since its inception: the data source mapper classes are no longer suffixed with “Mapper”. Previously, if you had a foo table, you would expect a mapper class name like App\DataSource\Foo\FooMapper. With this naming change, the mapper class name is now App\DataSource\Foo\Foo. That makes the data source mapper class name a good base as a prefix for all the other data source type classes, which in turn means the ‘@’ token can be used without need for string manipulation, and it works with only a very few lines of PHPStorm metadata code.

You can see the resulting .phpstorm.meta.php resource bundled with Atlas 3.x here. Put that at the root of your project, and PHPStorm will now successfully typehint all the Atlas method returns. (You might have to restart PHPStorm for it to take full effect.)


There was still another category of problems, though. While the overarching Atlas class now had IDE completion, the underlying Mapper and Table packages were missing some hints on their classes and methods.

For example, the base Mapper::fetchRecord() method is typehinted to return a Record, but the child FooMapper::fetchRecord() actually returns a FooRecord. The properties on the FooRecord are specific to the underlying FooRow from the table and any FooRelationships on the mapper, and that needs to be indicated by the IDE.

Likewise, the base Mapper::select() class is typehinted to return the base MapperSelect class; in turn, the MapperSelect::fetchRecord() method is typehinted to return a Record. But a $fooMapper->select()->fetchRecord() call actually returns a FooRecord. Those too need to be indicated by the IDE.

I ended up solving this in two steps:

  1. Each data source type now gets its own type-specific select class; whereas FooMapper::select() used to return a general-purpose Select, it now returns a FooSelect instance extended from the general-purpose select. This type-specific select class is generated by Atlas.Cli tooling.

  2. On the type-specific mapper and select classes, the Atlas.Cli tooling now generates a docblock of @method declarations with overriding type-specific returns. (You can see an example of this in the Atlas.Testing package Author and AuthorSelect classes.) PHPStorm picks up those docblocks and uses them to provide return typehints when those classes are used.

With those two additions to the Atlas.Cli skeleton generator, autocompletion in the PHPStorm IDE appears to be complete.


Of course, “there are no solutions, there are only tradeoffs.” This situation is no different. Yes, Atlas now has a relatively straightforward IDE completion approach, but at these costs:

  • If IDE completion is not important to you, these very significant changes from the alpha version to the beta version will feel useless or even counter-productive.

  • There are two more classes per data-source type: one type-specific MapperSelect, and one type-specific TableSelect (to typehint the Row returns).

  • Not having a “Mapper” suffix on the actual data-source mapper class may feel wrong or counter-intuitive.

For some, these tradeoffs will not be worth the effort to transition from the alpha release to the beta. However, after working with the beta for a while, I think they end up being a net benefit overall.

Atlas.Orm "Cassini" (v3) Early-Access Alpha Release

For those of you who don't know, Atlas is an ORM for your persistence model, not your domain model. Atlas 1 "Albers" (for PHP 5.6) was released in April 2017. Atlas 2 "Boggs" (for PHP 7.1) came out in October 2017.

And now, in April 2018, we have an early-access release of Atlas 3 "Cassini", the product of several lessons from a couple of years of use.

Architecture and Composition

One big architectural change is that Atlas 3 no longer uses the older Aura packages for SQL connections and queries; instead, Atlas has adopted these packages as its own, and upgraded them for PHP 7.1 using strict typing and nullable returns. Another big architectural change is that the table data gateway and data mapper implementations are now available as packages in their own right.

The end result is a mini-framework built from a stack of packages, where any "lower" package can be used indepdendently of the the ones "above" it. The package hierarchy, from bottom to top, looks like this:

  • Atlas.Pdo: Descended from Aura.Sql, this provides a database Connection object and a ConnectionLocator. If all you need is convenience wrapper around PDO with fetch and yield methods, this is the package for you.

  • Atlas.Query: Descended from Aura.SqlQuery, this is a query builder that wraps an Atlas.Pdo Connection. If you just want to build and perform SQL queries using an object-oriented approach, the Atlas query objects can handle that.

  • Atlas.Table: Extracted from Atlas 2, this is a table data gateway implementation that uses Atlas.Query under the hood. If you don't need a full data mapper system and only want to interact with individual tables and their row objects, this will do the trick.

  • Atlas.Mapper: Also extracted from Atlas 2, this is a data mapper implementation that models the relationships between tables. It allows you to build Record and RecordSet objects whose Row objects map naturally back to Table objects; you can write them back to the database one by one, or persist an entire Record graph back to the database in one go.

  • Atlas.Orm: Finally, at the top of the hierarchy, the overarching ORM package provides a convenience wrapper around the Mapper system, and provides several strategies for transaction management.

There are also two "side" packages:

  • Atlas.Info: Descended from Aura.SqlSchema, this inspects the database to get information about tables, columns, and sequences.

  • Atlas.Cli: This command-line interface package uses Atlas.Info to examine the database, then create skeleton classes for your persistence model from the database tables.

Separation, Completion, and Hierarchy

One goal for Atlas 3 was to split up the Table and Mapper subsystems into their own packages, so they could be used in place of the full transactional ORM if needed. Along with that, I wanted better IDE autocompletion, especially at the query-building level, particularly in support of different SQL dialects (e.g., PostgreSQL has a RETURNING clause, but nothing else does).

The first idea long these lines was to have parallel packages, one for each SQL driver: Atlas.Query-Mysql, Atlas.Query-Pgsql, Atlas.Query-Sqlite, etc. However, I shortly realized that typehinting at higher levels would have been a problem. If a generic Table class returns a TableSelect that extends a generic Select object, then providing a driver-speific MysqlSelect had to return a MysqlTableSelect for a MysqlTable. That in turn would have meant parallel packages for each driver all the way up the hierarchy: Table, Mapper, and Orm. Generics at the language level might have solved this, but PHP doesn't have them, so that idea was out.

Then the idea was to have a single "master" ORM package with all the subsytems included in it as virtual packages, and template methods where driver-specific implementations could be filled in. However, that ended up being an all-or-nothing approach, where the "lower" level packages could not be used independently of the "higher" level ones.

I could think of only one other alternative that would enable IDE autocompletion for driver-specific functionality, and keep the packages well-separated. That was to make Atlas.Query itself more general-purpose. As a result, the returning() method is available, even if the particular backend database will not recognize it. I'm not especially happy about this, since I'd rather the classes expose only driver-specific functionality, but the tradeoff is that you get full IDE completion. (I rationalize this by saying that the query objects are not there to prevent you from writing incorrect SQL, just to let you write SQL with object methods; you could just as well send a RETURNING clause to MySQL by putting it in a query string. Again, generics at the PHP level would help here.)

Further, on the query objects, I found myself wanting to perform the query represented by the object directly from that object, rather than manually passing it through PDO each time. As such, the query objects now take a PDO instance (which gets decorated by an Atlas.Pdo Connection automatically) so you can do things like this:

$result = Select::new($pdo)
    ->columns('foo', 'bar')
    ->where('baz = ', $baz)

Gateways, Mappers, and Relationships

With that in place, the next step was to extract the table data gateway subsystem to its own separate package. The new Atlas.Table library is not too different from the Atlas 2 version. The biggest single change is that the identity map functionality has been moved "up" one layer to the Mapper system. This keeps the package more in line with expectations for table data gateway implementations.

That, in turn, led to extracting the data mapper subsystem to its own package as well. Atlas.Mapper is now in charge of identity mapping the Rows that serve as the core for each Record, but the transaction management work has been moved up one layer to the overarching ORM package.

Of particular note, the new Atlas.Mapper package does away with the idea of a
"many-to-many" relationship as a first-class element. It turned out that managing many-to-many relateds was both counterintuitive and counterproductive in subtle but significant ways, not least of which was having to keep track of new or deleted records in two places (the "through" association and the "foreign" far side of the association). Under the hood, Atlas 2 had to load up the assocation mapping records anyway, so forcing an explicit with() call when fetching a many-to-many relationship through the association mapping seems like a reasonable approach. With that, you only need to manage the association mapping to add or remove the "foreign" records on the far side of the relationship.

Also in the relationship department, the "ManyToOneByReference" relationship has been renamed to "ManyToOneVariant". (I think that name flows better.)

Finally, 1:1 and 1:M relationships now support different kinds of cascading delete functionality. These methods on the relationship definition will have these effects on the foreign record when the native record is deleted:

  • onDeleteSetNull(): Sets $foreignRecord keys to null.

  • onDeleteSetDelete(): Calls $foreignRecord::setDelete() to mark it for deletion.

  • onDeleteCascade(): Calls $foreignMapper::delete($foreignRecord) to delete it right away.

  • onDeleteInitDeleted(): Presumes that the database has deleted the foreign record via trigger, and re-initializes the foreign record row to DELETED.

Further, the 1:M relationship will detach deleted records from foreign RecordSets, and the 1:1 relationship will set the foreign record value to false when deleted. This helps with managing the in-memory objects, so you don't have to detach or unset deleted records yourself.

ORM Transaction Management

At the very top of the framework hierarchy, then, we have the Atlas.Orm package. Almost all functionality has been extracted to the underlying packages; the only parts remaining are the overarching Atlas object with its convenience methods, and the transaction management system.

Whereas Atlas 2 provided something similar to a Unit of Work implementation, I have come around to the idea that Unit of Work is a domain-layer pattern, not a persistence-layer pattern. It's about tracking changes on domain objects and writing them back to the database approriately: "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work." (cf. POEAA: UnitOf Work).

With that in mind, Atlas 3 provides more typical transaction management strategies, though with some automation if you feel you need it:

  • The default AutoCommit strategy starts in "autocommit" mode, which means that each interaction with the database is its own micro-transaction (cf. https://secure.php.net/manual/en/pdo.transactions.php). You can, of course, manually begin/commit/rollback transactions as you wish.

  • The AutoTransact strategy will automatically begin a transaction when you perform a write operation, then automatically commit that operation, or roll it back on exception. (This was the default for Atlas 2.)

  • The BeginOnWrite strategy will automatically begin a transaction when you perform a write operation. It will not commit or roll back; you will need to do so yourself. Once you do, the next time you perform a write operation, Atlas will begin another transaction.

  • Finally, the BeginOnRead strategy will automatically begin a transaction when you perform a write operation or a read operation. It will not commit or roll back; you will need to do so yourself. Once you do, the next time you perform a write or read operation, Atlas will begin another transaction.

Style and Approach

Even though I was the lead on PSR-1 and PSR-2, I find myself beginning to drift from them somewhat as I use PHP 7 more. In particular, I find things line up more nicely when you put the opening brace on the next line, for a method with a typed return and an argument list that spreads across multiple lines. That is, instead of this ...

    public function foo(
        LongClassName $foo,
        LongerClassName $bar
    ) : ?VeryLongClassName {
        // ...

... I'm leaning toward this:

    public function foo(
        LongClassName $foo,
        LongerClassName $bar
    ) : ?VeryLongClassName
        // ...

The extra bit of whitespace in this situation gives a nice visual separation.

I'm also beginning to drift a little from some of my devotion to interfaces. One of the problems with exposing interfaces is that strict backwards compatibility becomes harder to maintain without also bumping major versions; if you find you need even a single new feature, you can't add it to the interface without breaking BC. As such, to maintain both Semantic Versioning and to avoid adding off-interface methods in sub-major releases, Atlas 3 avoids interfaces entirely in favor of abstract class implementations. Further, these abstract classes do not use the "Abstract" prefix, as I have done in the past. This means Atlas can typehint to Abstract classes without it looking ugly, and can avoid technical BC breaks when interfaces are added to. This has its own problems too; it is not a solution so much as a tradeoff.

I'm also trying to avoid docblocks as much as possible in favor of typehinting everthing possible.

Upgrading from Atlas 2 to Atlas 3

Whereas the upgrade path from Atlas 1 to Atlas 2 was relatively straightforward, the jump from Atlas 2 to Atlas 3 is a much bigger one. The most significant changes are in the method names and signatures at the Connection and Query levels; the Table classes now each have their own Row instead of using an all-purpose generic row; the Mapper classes now separate the relationship-definition logic to its own class as well. I will be preparing an upgrade document to help ease the transition.

But if you're just getting started with Atlas, you'll want to try v3 "Cassini" first.

The Conquest Code of Conduct

If you're tired of SJW COCs in open-source projects, try this one on for size:

Conquest's Second Law: "Any organization not explicitly right-wing sooner or later becomes left-wing."

tl;dr: No Socialism or Social Justice.

All contributions and communication are welcome, so long as they do not (within this project space) espouse, entertain, advocate for, or otherwise positively discuss the political ideals associated with Social Justice, Progressivism, Communism, Socialism, Fascism, Marxism, or anything else generally reminiscent of any political philosophy to the left of Classical Liberals or Libertarians.

If you suspect or are subjected to criminal behavior within this project space, first notify the appropriate authorities; then, if you wish, you may notify the project owner. The project owner makes no promises in advance regarding accusations or complaints.

The project owner is the final arbiter on all contributions and communication within this project space.

Line Coverage in Unit Tests

The novice says, "I do not strive for 100% line coverage in tests; I only write tests for the code that is important."

The master says, "If the code is not important, why is it there at all? I will strive to test every line I write; if a line is not important, it should be removed."

(See also The Way of Testivus.)

Atlas 2.1.0 Released with "Polymorphic Association" Support

I’m happy to announce that I released Atlas 2.1.0 late yesterday. (Atlas is a data mapper for your persistence model in PHP – not your domain model.)

In addition to some minor added informational and convenience functionality, the big addition in this release is support for many-to-one relationships by reference (aka “polymorphic association”). You can see the documentation for it here.


Atlas uses SQL terms for relationships instead of OOP ones (e.g., “many-to-one” instead of “has one”). As such, the OOP term “polymorphic assocation” just wasn’t a good name for the feature.

However, some research revealed that PostgresANSI SQL has a constraint type named REFERENCES that supports the feature natively:

After trying out several alternative names, “many-to-one by reference” was a much better fit than “polymorphic association.”


Because Atlas is for the persistence model, and not for the domain model, I had to wonder if this kind of behavior belongs in the database work at all. Should it happen in the domain instead?

After working through the problem, the answer turned out to be that it has to go in the database work. You simply don’t know which foreign tables to select from in the first place, without that information being represented in a relationship description. The reference column determines what the foreign table should be. If there are different values in the reference column, then you have to select from different tables to get the related rows. That can’t happen once you’re in the domain layer; it must happen in the persistence layer.


Relationships-by-reference may not be a good data design choice if you are starting from scratch. See this 2009 presentation from Bill Karwin for some other alternatives:


These each have different tradeoffs, and in one case require that your database system supports parent tables.

Of course, if you already have a database design that uses many-to-one relationships by reference, then you’re probably stuck with it. Atlas can now help you out in this situation.

UPDATE: This Reddit comment leads me to understand that I read the REFERENCES Postgres doc too hastily. In context of the linked mailing list message, I understood "refcolumn" to be on the native table, not the foreign one. So it's a standard foreign key constraint, not a specialized/extended form provided by Postgres; I confess I find it easy to believe that Postgres often supports things that other databases do not.

You can read the Reddit commentary on this post here.


The novice says: "Nothing can ever be perfect; anything I choose will be imperfect. Therefore, all choices are equally bad, so I may choose whatever I feel like."

The master says: "Some things are less imperfect than others; I will make the least-imperfect choice that I can."