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. :-)

puppy-wendy

winter-wendy

mature-wendy

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/.

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):

<?php
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)
    ->columns('*')
    ->from('posts')
    ->where('id IN ', $ids)
    ->fetchAll();

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(
    'mysql:host=localhost;dbname=testdb',
    'username',
    'password'
);

$queryFactory = new QueryFactory();

$select = $queryFactory->newSelect($connection);
$select->columns('*');
$select->from('posts');
$select->where('id = ', $id);

$row = $select->fetchOne();

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

$select
    ->columns(...)
    ->from(...)
    ->join(...)
    ->where(...)
    ->groupBy(...)
    ->having(...)
    ->orderBy(...)
    ->limit(...)
    ->offset(...);

… 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 ...
$insert->into('posts')
    ->columns([
        'title' => $title,
        'body' => $body,
    ])
    ->raw('created_at', 'NOW()')
    ->perform();

// ... 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.

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.

II.

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.)

III.

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.

IV.

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')
    ->from('table_name')
    ->where('baz = ', $baz)
    ->fetchAll();

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.

I.

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.”

II.

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.

III.

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:

https://www.slideshare.net/billkarwin/practical-object-oriented-models-in-sql/22

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.

Perfection

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."

Best Practices

Best practices evolve. Even though they change, they tend to change in the direction of "better", not "worse".


The novice thinks he is an individual.

The novice says: "Best practices are always changing; why bother adhering to something that I know will change? I am free to do what I feel like without referring to best practices."

This is not freedom; it is license.

The master realizes he stands at the end of a long trail of experience and knowledge from others, that surpasses his own personal experience and knowledge.

The master says: "This is my current understanding; these are my expected circumstances; these are the known best practices; these are their tradeoffs. I will choose the best practice I can for the tradeoffs I am willing to endure."

This is not slavery; it is clear thinking.

Considering Typehints As Communication

Typehints help communicate across time and space, to people who may never meet you or who might not be able to interrogate you about your code, so those people can understand how you expect the code to work.

Adding typehints is a succinct, more-complete form of communication than not-adding them. (It is rare, perhaps impossible, for all communication can be fully complete all the time.)

Further, you don't know in advance which parts of the codebase are going to last for a long time, and which are going to be replaced in relatively short order. It's probably better to to add the typehints when you know what they are, rather than to wait and see if you'll "need" them later.

Typehints can be considered low-cost mistake-proofing against misunderstanding in an obvious place (i.e., where they are used), without having to look elsewhere ("just read the tests!" [groan]).

Solving The "Widget Problem" In ADR

The “widget problem” is when you have several panels or content areas on an HTML page that have different data sources. You might have a main content area, then a calendar off to the side, with perhaps a list of recent news items or blog posts, a todo or reminder widget, and maybe other information panels. The problem is that they each have different data sources, and may not always be displayed in every circumstance -- perhaps they are only shown to some users based on their preferences, or under certain conditions.

So how, in Action-Domain-Responder, do we get the “right” data for the set of widgets that are actually going to be displayed? (We’ll presume here that the entire page is being rendered server-side, for delivery as a whole to the client.)

The answer is “the same as with anything else” – we just have more kinds of data to get from the domain. The domain has all the data needed, and the knowledge necessary to figure out which data elements to return.

Let’s start with the Action, which is intentionally very spare: it only collects input, calls the Domain with that input, then invokes the Responder:

<?php
class PageAction
{
    public function __construct(
        PageService $domain,
        PageResponder $responder
    ) {
        $this->domain = $domain;
        $this->responder = $responder;
    }

    public function __invoke(HttpRequest $request)
    {
        $payload = $this->domain->fetchPageData(
            $request->getAttribute('sessionId'),
            $request->getAttribute('pageName')
        );
        return $this->responder->respond($request, $payload);
    }
}

The domain work is where the heavy lifting happens. The example below returns the domain objects and data wrapped in a Domain Payload object.

<?php
class PageService
{
    // presume $userService, $sessionService, and $database
    // dependencies are injected via constructor

    public function fetchPageData($sessionId, $pageName)
    {
        $session = $this->sessionService->resume($sessionId);
        $user = $this->userService->fetch($session->userId);

        // the main page data
        $mainData = $this->fetchMainData($pageName);
        if (! $mainData) {
            return new Payload('NOT_FOUND');
        }

        // an array of widgets to show
        $widgets = [];

        // different users might prefer to see different widgets
        foreach ($user->getWidgetsToShow() as $widgetName) {
            $method = "fetch{$widgetName}Data";
            $widgets[$widgetName] = $this->$method();
        }

        $this->sessionService->commit($session);

        return new Payload('FOUND', [
            'user' => $user,
            'page_name' => $pageName,
            'main_data' => $mainData,
            'widgets' => $widgets
        ]);
    }

    protected function fetchMainData($page_name)
    {
        return $this->database->fetchRow(
            "SELECT * FROM pages WHERE page_name = ? LIMIT 1",
            $page_name
        );
    }

    protected function fetchTodoData() { ... }

    protected function fetchRemindersData() { ... }

    protected function fetchUpcomingEventsData() { ... }

    protected function fetchCalendarData() { ... }

}

Finally, the Responder work becomes as straightforward as: “Is there Todo data to present? Then render the Todo widget via a Todo helper using the Todo data.” It could be as simple as this:

<?php
class PageResponder
{
    // ...

    public function respond(Request $request, Payload $payload)
    {
        if ($payload->getStatus() == 'NOT_FOUND') {
            return new Response(404);
        }

        $output = $payload->getOutput();

        $html = '';
        $html .= $this->renderHeader($output['page_name'];
        $html .= $this->renderNav();
        $html .= $this->renderMain($output['main_data']);

        foreach ($output['widgets'] as $widgetName => $widgetData) {
            $method = "render{$widgetName}Html";
            $html .= $this->$method($widgetData);
        }

        return new Response(200, $html);
    }

    protected function renderHeader($request, $pageName) { ... }

    protected function renderNav($request) { ... }

    protected function renderMain($request, $mainData) { ... }

    protected function renderTodoHtml($request, $widgetData) { ... }

    protected function renderRemindersHtml($request, $widgetData) { ... }

    protected function renderUpcomingEventsHtml($request, $widgetData) { ... }

    protected function renderCalendarHtml($request, $widgetData) { ... }
?>

One alternative here is for some client-side Javascript to make one additional call per widget or panel to retrieve widget-specific data, then render that data on the client. The server-side work becomes less complex (one action per widget, and transform the data to JSON instead of HTML) – but the client-side work becomes more complex, and you have more HTTP calls back-and-forth to build the page.

Avoid Dependency Injection

At least, avoid it when building DDD Aggregates:

Dependency injection of a Repository or a Domain Service into an Aggregate should generally be viewed as harmful. The motivation may be to look up a dependent object instance from inside the Aggregate. The dependent object could be another Aggregate, or a number of them. ... Preferably, dependent objects are looked up before an Aggregate command method is invoked, and passed into it.

... Take great care not to add unnecessary overhead that could be easily avoided by using other design principles, such as looking up dependencies before an Aggregate command method is invoked, and passing them into it.

This is only meant to warn against injecting Repositories and Domain Services into Aggregate instances. Of course, dependency injection is still quite suitable for many other design situations. For example, it could be quite useful to inject Repository and Domain Service references into Application Services.

-- "Implementing Domain Driven Design", Vaughn Vernon, p 387.


On a related note, regarding where Entity validation logic goes, we have this ...

Validation is a separate concern and should be the responsibility of a validation class, not a domain object.

-- ibid., p 208

... and this ...

Embedding validation logic inside an Entity give it too many responsibilities. It already has the responsibility to address domain behavior as it maintains its state.

-- ibid., p 212

... but then we see this:

How do clients ensure that Entity validation occurs? And where does validation processing begin? One way places a validate() method on all Entities that require validation. ... Any Entity can safely have its validate() method invoked. ...

However, should Entities actually validate themselves? Having its own validate() method doesn't mean the Entity itself performs validation. Yet, it does allow the Entity to determine what validates it, relieving clients from that concern:

public class Warble extends Entity {
    ...
    @Override
    public void Validate(ValidationNotificationHandler aHandler) {
        (new WarbleValidator(this, aHandler)).validate();
    }
}

... The Entity needs to know nothing about how it is validated, only that it can be validated. The separate Validator subclass also allows the validation process to change at a diferent pace from the Entity and enables complex validations to be thoroughly tested.

-- ibid., p 214-215

It seems like a small step after that to inject a fully-constructed validation object into the Entity at construction time, and have the validate() method call that, instead of creating a new validation object inside the Entity.

Choose Dependency Injection — If You Can

Some people say, "You don't need to use dependency injection for everything. Sometimes dependency injection is not the best choice."

It occurs to me that the people who say this are the ones who can't use it for everything. They say "choose what's best for your situation", but their situation precludes the use of dependency injection in the first place.

Anyone who says "X is not always the best choice", but does not have X as an available option, is being disingenuous. They are not choosing against X based on an examination of the tradeoffs involved. Instead, they are making a virtue out of necessity, then posing as virtuous for not having better choices available.

Dependency injection is, by default and until proven otherwise, the best choice -- when you have that choice available to you.

If that choice is not available to you, if you cannot construct an object using any form of dependency injection (constructor injection, setter injection, etc.), then you need to consider if the code in question has been designed poorly.

Atlas.Orm 2.0 Is Now Stable

I am very happy to announce that Atlas, a data-mapper for your persistence layer in PHP, is now stable for production use! There are no changes, other than documentation updates, since the beta release two weeks ago.

You can get Atlas from Packagist via Composer by adding ...

    "require": {
        "atlas/orm": "~2.0"
    }

... to your composer.json file.

The updated documentation site is at atlasphp.io (with both 1.x and 2.x documentation).

Submit issues and pull requests as you see fit!

A Few Right Ways, But Infinitely More Wrong Ways

A response to the saying: "There's no one 'right' way to do things. There are different ways of doing something that are 'right'. So stop criticizing my chosen way of doing things -- you cannot prove that it is wrong."

For any question, there is a certain number of right answers, but an infinite number of wrong ones.

Likewise, there may be more than one right way, but that number is small in comparison to the infinite number of wrong ways.

Each right way is ephemeral and contingent, and has its own tradeoffs.

Each right way is dependent on your current understanding as applied to your current circumstances.

As your understanding changes with experience, and as your circumstances change over time, the way that is thought to right will also change.

Sometimes that means realizing that your earlier understanding was wrong.

The novice says: "My new idea cannot be wrong, because there is no one right way."

The master asks: "Is it more likely that I have a new way that is better, or a new way that is worse?"

The novice demands "proof" that their way is wrong.

The master asks which ways are "better" and which are "worse", and picks the best one for the situation.

Sometimes the best way is still "bad", but better than all the others; the master knows this, and does not defend it as "right."

Thomas Aquinas on Immigration

Immigration should have as its goal integration, not disintegration or segregation. The immigrant should not only desire to assume the benefits but the responsibilities of joining into the full fellowship of the nation. By becoming a citizen, a person becomes part of a broad family over the long term and not a shareholder in a joint stock company seeking only short-term self-interest.

Secondly, Saint Thomas teaches that immigration must have in mind the common good; it cannot destroy or overwhelm a nation.

This explains why so many Americans experience uneasiness caused by massive and disproportional immigration. Such policy artificially introduces a situation that destroys common points of unity and overwhelms the ability of a society to absorb new elements organically into a unified culture. The common good is no longer considered.

Source: What Does Saint Thomas Say About Immigration? -