There are no solutions, only tradeoffs.

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.

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:

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.

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.

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:

Locate or Create a Violation of the Narrative.

Point and Shriek.

Isolate and Swarm.

Reject and Transform.

Press for Surrender.

Appeal to Amenable Authority.

Show Trial.

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”

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:

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:

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!

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 …

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

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.

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.

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:

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 …

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.