Avoiding Quasi-Immutable Objects in PHP

tl;dr: Immutability in PHP is most practical when the object properties are scalars or nulls. Using streams, objects, or arrays as properties makes it very difficult, sometimes impossible, to preserve immutablity.

One of the tactics in Domain Driven Design is to use Value Objects. A Value Object has no identifier attached to it; only the combination of the values of its properties gives it any identification. If you change any of the properties in any way, the modification must return an entirely new instance of the Value Object.

This kind of behavior means the Value Object is “immutable.” That is, the particular instance is not allowed to change, though you can get back a new instance with modified values. The code for an immutable object looks something like this:

class ImmutableFoo
    protected $bar;

    public function __construct($bar)
        $this->bar = $bar;

    public function getBar()
        return $this->bar;

    public function withBar($newBar)
        $clone = clone $this;
        $clone->bar = $newBar;
        return $clone;

(Note how $bar is accessible only through a method, not as a public property.)

When you create an ImmutableFoo instance, you cannot change the value of $bar after instantiation. Instead, you can only get back a new instance with the new value of $bar by calling withBar():

$foo = new ImmutableFoo('a');
$newFoo = $foo->withBar('b');

echo $foo->getBar(); // 'a'
echo $newFoo->getBar(); // 'b'
var_dump($foo === $newFoo); // (bool) false

With this approach, you are guaranteed that one place in the code cannot change the $foo object at a distance from any other place in the code. Anything that ever gets that instance of $foo knows that its properties will always be the same no matter what.

The immutability approach can be powerful in Domain Driven Design and elsewhere. It works very easily in PHP with scalar values and nulls. That’s because PHP returns those by copy, not by reference.

However, enforcing immutability in PHP is difficult when the immutable object properties are non-scalar (i.e., when they are streams, objects, or arrays). With non-scalars, your object might seem immutable at first, but mutablity reveals itself later. These objects will be “quasi-“, not truly, immutable.

Streams as Immutable Properties

If a stream or similar resource has been opened in a writable (or appendable) mode, and is used as an immutable property, it should be obvious that object immutability is not preserved. For example:

file_put_contents('/tmp/bar.txt', 'baz');

$foo = new ImmutableFoo(fopen('/tmp/bar.txt', 'w+'));
$bar = $foo->getBar();
fpassthru($bar); // 'baz'

fwrite($bar, 'dib');

fpassthru($foo->getBar()); // 'dib'

As you can see, the effective property value has changed, meaning immutability has been compromised.

One way around this might be to make sure that immutable objects themselves check that stream resources are always-and-only in read-only mode. However, even that is not a certain solution, because the resource pointer might be moved by reading operations in different parts of the application code. In turn, that means reading from the stream may yield different results at different times, making the value appear mutable.

As such, it appears that only “read-only” streams can be used as immutable properties, and then only if the immutable object restores the stream, its pointers, and all of its meta-data to their initial state every time the stream is accessed.

Objects as Immutable Properties

Because PHP returns objects as references, rather than as copies, using an object as a property value compromises the immutability of the parent object. For example:

$foo = new ImmutableFoo((object) ['baz' => 'dib']);
$bar = $foo->getBar();
echo $bar->baz; // 'dib'

$bar->baz = 'zim';
echo $foo->getBar()->baz; // 'zim'

As you can see, the value of $bar has changed in the $foo instance. Any other code using $foo will see those changes as well. This means immutability has not been preserved.

One way around this is to make sure that all objects used as immutable properties are themselves immutable.

Another way around this is to make sure that getter methods clone any object properties they return. However, it will have to be a recursively deep clone, covering all of the cloned object’s properties (and all of their properties, etc.). That’s to make sure that all object properties down the line are also cloned; otherwise, immutability is again compromised at some point.

Arrays as Immutable Properties

Unlike objects, PHP returns arrays as copies by default. However, if an immutable object property is an array, mutable objects in that array compromise the parent object’s immutability. For example:

$foo = new ImmutableFoo([
    0 => (object) ['baz' => 'dib'],

$bar = $foo->getBar();
echo $bar[0]->baz;

$bar[0]->baz = 'zim';
echo $foo->getBar()[0]->baz; // 'zim'

Because the array holds an object, and because PHP returns objects by reference, the contents of the array have now changed. That means $foo has effectively changed as well. Again, immutability has not been preserved.

Likewise, if the array holds a reference to a stream resource, we see the problems described about streams above.

The only way around this is for the immutable object to recursively scan through array properties to make sure that they contain only immutable values. This is probably not practical in most situations, which means that arrays are probably not suitable as immutable values.

Settable Undefined Public Properties

Finally, PHP allows you to set values on undefined properties, as if they were public. This means it is possible to add mutable properties to an immutable object:

$foo = new ImmutableFoo('bar');

// there is no $zim property, so PHP
// creates it as if it were public

$foo->zim = 'gir';
echo $foo->zim; // 'gir'

$foo->zim = 'irk';
echo $foo->zim; // 'irk'

Immutability of the object is once again compromised. The only way around this is to impelement __set() to prevent setting of undefined properties.

Further, it might be wise to implement __unset() to warn that properties cannot be unset.


If you want to build a truly immutable object in PHP, it appears the best approach is the following:

  • Default to using only scalars and nulls as properties.
  • Avoid streams as properties; if a property must be a stream, make sure that it is read-only, and that its state is restored each time it is used.
  • Avoid objects as properties; if a property must be an object, make sure that object is itself immutable.
  • Especially avoid arrays as properties; use only with extreme caution and care.
  • Implement __set() to disallow setting of undefined properties.
  • Possibly implement __unset() to warn that the object is immutable.

Overall, it seems like immutability is easiest with only scalars and nulls. Anything else, and you have a lot more opportunity for error.

Remember, though, there’s nothing wrong with fully- or partially-mutable objects, as long as they are advertised as such. What you want to avoid are quasi-immutable objects: ones that advertise, but do not deliver, true immutability.

(For some further reading, check out At What Point Do Immutable Classes Become A Burden?)


FIG Follies, Part 3

This is the third of three posts I intend to make regarding the condition and actions of the FIG, and what they reveal.

The Future

[By way of presenting some context: I was the driving force behind PSRs 1 and 2; I conceived and then led PSR-4; I was a primary author on some of the early bylaws, including the voting protocol and the sponsorship model. I am a founding member of the FIG, and one of the two longest continually-serving members (along with Nate Abele). My perspective on the FIG spans its lifetime of 7+ years. It is that perspective which informs these posts.]

Regarding the rivalrous visions for the FIG, I see only two ways forward.

The first is that somehow the tension between the two visions is permanently resolved. It is this ongoing implicit tension that is the cause of recurring conflict. There is no common vision between all FIG members, and this causes resentment and distrust; sharing a common vision will provide a level of shared values and views.

I think the only way to relax that tension in the group is for one of the rival visions to supersede the other in an unmistakable and permanent way. That means one set of vision-holders would have to explicitly renounce its own vision in favor of the other. This could be accomplished by voice (i.e., by stating so on the mailing list) or by exit (i.e., by leaving the FIG).

Even though that would leave the FIG (per se) intact, I opine that sort of capitulation is unlikely from either set of vision-holders. After such protracted contention, the holders of each vision seem unlikely to withdraw, and even more unlikely to adopt the competing vision for FIG. (As a defender against the introduction of the newer “grand” vision, I certainly don’t to plan abandon the “founding” vision voluntarily.)

The second is that the holders of each vision all surrender simultaneously, and disband the FIG.

Disband The FIG?

There is already some level of interest for this idea, both from the wider community and within FIG itself, especially in relation to the FIG 3.0 proposal. From the thread Alternative to FIG 3.0 – Is it time to call FIG complete? (you should read the whole thing) we have these points from Joe Ferguson in two different messages:

As someone who feels like FIG 3.0 is a step in the right direction, and can empathize with those against it, I’d like to offer an observation:

Maybe it’s time to call FIG complete. FIG accomplished it’s primary goals (Bringing frameworks together to build interoperable packages / standards).

My comment is that maybe its time PHP get a standards body, led by the people who want to see FIG 3.0 happen. Let your work in FIG stand on it’s own. PSRs aren’t going anywhere. Start a Standards Body Group that adopts the work you’ve done here and picks up all the currently in progress PSRs and continues working on them. Close FIG down since it’s work as a Framework Interoperability Group has been completed.

This would be a perfect time to start fresh, get your bearings, and sort your stuff out with all the lessons learned from FIG. I would rather see this than people oppose FIG 3.0 and it being shoehorned in.

If you take away absolutely nothing else from my comments: FIG 3.0 is such a shift that it warrants the consideration of a new organization with leadership based on the people doing the work to make things happen in FIG.

I feel a sort of resignation toward this idea. I don’t really enjoy it, but something like it has to happen, and it’s probably for the best.

It certainly would eliminate the tension between the competing visions for the FIG, as there would be no FIG to contest over.


Let’s say, for a moment, that FIG dissolves itself, and the FIG 3.0 plan architected by Larry Garfield and Michael Cullum is realized as a new organization with a new name. What would be the benefits?

  • As a brand-new organization, it can define its vision and mission without in-group rivalry.

  • It can curate its membership, bringing in only the people and projects that adhere to its vision (and keeping out those who do not).

  • It can establish any organizational structure (hierarchical or otherwise) from the outset, without having to worry about precedent or prior expectations.

  • It can have any code of conduct it wants as part of its foundational structure.

  • Whatever negative baggage is perceived as being part of the FIG is dropped.

I’m sure there are other benefits as well.


There is one major drawback that I’ve heard voiced regarding a “disband the FIG and start a new group” approach. It is that new standards groups have a hard time getting off the ground. As one example, the PHP-CDS project does not seem to have gained much traction.

My guess is that an organization that forms after the FIG disbands would not have that trouble, especially given that FIG would no longer exist.

Note that some member projects such as Composer, Drupal, PEAR, phpBB, and Zend Framework have shown explicit support for the FIG 3.0 re-organization plan. I imagine that those projects alone would grant legitimacy to any new non-FIG group in which they were members.

Furthermore, as the architects of the FIG 3.0 plan, Larry Garfield is the Drupal representative to FIG, and Michael Cullum (while not a voting member) is directly involved with phpBB. The new group is guaranteed to have at least those two members from the start.

I have heard other drawbacks as well, but they seem primarily administrative in nature.

Administrative Issues

What happens to the accepted PSRs?

Accepted PSRs remain in perpetuity as the true product of the FIG, for any and all to adopt or ignore as they see fit. They were built to be immutable, and they will live on for as long as anyone cares to refer to them. Any new group that forms after the FIG would be free to adopt, or ignore, the finished PSRs (though it would not be free to claim them as their own).

What happens to the in-process PSRs?

There are at least two options here.

  1. In-process PSRs might convert to *-interop projects, a la container-interop and async-interop. Indeed, Woody “Shadowhand” Gilk has had the foresight and initiative to create an http-interop group already.
  2. If the migration out of FIG produces a new group, e.g. a FIG 3.0 style organization, that other group might adopt them in some fashion, perhaps by making those PSR owners initial members in the new group.

What about new PSRs?

After disbanding, no new PSRs would be admitted in any state.

What about the FIG website, Twitter account, Github account, etc. ?

I think this is the toughest problem with disbanding. It will be necessary to prevent the misappropriation of the FIG or PSR names after the group disperses, and at the same time make sure the various artifacts of the FIG (i.e., the PSRs) continue to be publicly available.

To that end, I suggest appointing an archivist (perhaps more than one) to receive ownership of the Github, Twitter, Google Mail, etc. accounts, as well as the FIG domain names. The job of archivist is is to hold the FIG name and accounts in trust and guard them from ever being used again. The archivist(s) might also merge errata and insubstantial changes on existing accepted PSRs, but I expect the activity on those should be minimal.

I assert that the archivist should be some nominally neutral party, one who has no designs on the FIG name or brand, and who has not participated in the production of PSRs or bylaws.

Finally, I would suggest that no follow-on group be allowed to claim to be the “successor” to the FIG, or somehow “approved” by FIG, but that’s a difficult thing to prevent. Members exiting the FIG would have to be on their honor for that one.


There is a tension in the FIG between two rival visions: a “founding” vision (mostly implicit through the history of the group) and a “grand” vision (explicitly described in the FIG 3.0 proposal). This tension reveals itself through things like the vote to remove me involuntarily, as well as ongoing conflict over internal processes, the direction and audience of PSRs, and other bureaucratic maneuvers.

The only ways to relax that tension are for one vision to supersede the other permanently and explicitly, or for the FIG to disband so there is no organization to contend over. Whereas I feel the former is unlikely to occur, the latter becomes the better course.

Disbanding leaves the products of the FIG intact (i.e., the PSRs). It also frees the holders of the “grand” vision to form a group of their own in their own way, to achieve their own ends without a competing group in place, and without compelling the holders of the “founding” vision to accede to a vision they do not share. The administrative issues of disbanding are minor in comparison.

Fig Follies, Part 2

This is the second of three posts I intend to make regarding the condition and actions of the FIG, and what they reveal.

The Present

The complaint against me (and the subsequent show trial and vote) are only a symptom of an underlying cause. The true cause behind the complaint, as well as many other events, is that there is a rivalry of visions regarding the FIG.

One vision holds that the FIG should remain true to its originating mission: focus on member projects, find existing commonalities among them through research and discussion, and codify those commonalities as PSRs. It is a more bottom-up approach, and is represented mostly implicitly, in the minds and actions of those who hold it. (Some artifacts of this vision remain on the FIG website, but I realize now they are not explicit enough.) I will call this the “founding” vision.

Another vision holds that the FIG should change its mission and broaden its scope to the entirety of PHP land, and in doing so accept the role that some people feel it should have: that of an overarching PHP Standards Group for all PHP coders, member projects or not. It is a more top-down approach, and is represented explicitly by the FIG 3.0 proposal. I will call this the “grand” vision.

These two visions are mutually exclusive. They are rivalrous.

This rivalry of visions might not matter, if the FIG had not already gained a level of perceived authority to many people in PHP land. It has earned a level of legitimacy through wide adoption of PSRs 0, 1, 2, 3, and 4 (and to some extent 6 and 7). That authority and legitimacy are valuable commodities, hard to come by among PHP developers (who are notoriously independent-minded). That means the FIG is perceived as a high-value property, which makes it worth being rivalrous over.

Those holding the “founding” vision, under which the vast majority of accepted PSRs have been published, believe the high value of the FIG derives from that “founding” vision and mission. Those holding the “grand” vision wish to use that value to launch what is effectively a new organization, without any achievements of its own, and claim the value built under the “founding” vision as its own.

To me, as a holder of the “founding” vision, the “grand” vision is an expression of conceit, of hubris. I think the holders of the “grand” vision believe they are entitled somehow to capture the successes earned by the “founding” vision, and claim those successes as their own. They have not earned those successes through the vision they espouse.

Until the conflict between these visions has been resolved, the contention within the FIG will continue, because neither set of vision-holders wants to relinquish the FIG territory.

I see only two ways out of this dilemma. I will present them not tomorrow, but the day after.

FIG Follies, Part 1

This is the first of three posts I intend to make regarding the condition and actions of the FIG, and what they reveal.

The Past

The show trial and subsequent vote to remove me has concluded, and I remain: the complainants were defeated, 15 to 9.

Now that the vote is done, I can assert openly that this was a “clearing the decks” operation. It was intended (in large part) to remove the most-vocal opponent to the FIG 3.0 proposal by Larry Garfield and Michael Cullum, and to prepare the way for implementing the Contributor Covenant (or some other SJW-inspired code of conduct). I predicted that conversations about both would resume very soon after the vote no matter which way it went, and that looks to have been prescient.

The complainants, and their secretarial collaborator, wanted a vote (not mediation) from the outset. I guess they figured it would be a slam-dunk to have me removed. What they didn’t expect was that roughly half of the participants would be either against my removal, or against the complainants themselves.

So instead of a slam-dunk, they had actual resistance on their hands. That’s why the secretarial collaborator dragged it out past the 2-week point, so there could be some chance of rallying support for the “removal” side. Little support was raised that was not shortly pushed-back against.

Then the complainants realized they had no options other than a vote, which they now thought they might lose. This is why they revived the idea of “alternative resolutions”. But they themselves presented no alternatives other than “shut up” and “go away”.

Even at the end, to keep their actions and their bias hidden, the secretaries suggested (to me personally) making the vote private, on authority they have not been granted.

Remember: the secretaries, in particular Michael Cullum, overstepped their bounds once again to enable this drama.

Even so, I must caution against reading too much into the results of the vote. The voters did not approve of me per se, so much as they disapproved of the complainants, the complaint itself, or the act of throwing someone out. It is not so much a vindication for me personally, as a repudiation of the complainants.

This is now all in the past, and a permanent part of the FIG. Tomorrow I will talk about the current state of the FIG.

Exporting Globals in PHP

I am currently modernizing a legacy PHP application for a client. (The codebase was written earlier this year, in fact; new code can be “legacy” from the outset.) The original developer pulled a dirty trick with global that I had not seen before, and I thought I had seen everything.

Legacy codebases often use global to import a variable into the local scope, usually a global function. For example, they might drag in a database connection:

// define $db in a config file somewhere
$db = new DatabaseConnection(...);

// this function uses the $db connection via global
function fetch_user_by_id($id)
    global $db;
    return $db->fetchAssoc("SELECT * FROM users WHERE id = ?", $id);

I see that kind of thing all the time in legacy PHP. However, what I have not seen before is a function exporting a global.

Take a look at the following code. If the $bar variable is not already defined in the global scope, PHP will define it in the global scope for you automatically when you call foo().


function foo()
    global $bar;
    $bar ++;

// $bar is not defined yet, so PHP will show an
// "undefined variable" notice
echo $bar. PHP_EOL;

// calling foo() defines $bar in the global scope,
// and increments it

// $bar is now available in the global scope, having
// been exported from function foo()
echo $bar. PHP_EOL;

The legacy developer did that because he wanted to keep the variable initialization outside of the global scope for some reason, even though he used the variable in the global scope elsewhere.

It is exceptionally difficult to track down where an exported global is coming from when refactoring a legacy application. If you must write legacy code using globals, initialize them in the global scope. Better yet, don’t use globals at all: pass values as function arguments, or use dependency injection techniques.

Telegraph: A Lambda-Style PSR-7 Middleware Dispatcher

On reflecting over the discussions surrounding the proposed PSR for HTTP middleware (on which I am coordinator), I realized there’s no reason there should *not* be a “request-only” PSR-7 middleware dispatcher.

So, here is Telegraph to fill that void.

Built over this past weekend, Telegraph is essentially a copy of Relay, which is a “request+response” middleware dispatcher. (There are a couple of minor differences.)

I also took the time to port the existing Relay middleware over to Telegraph. You can find the Telegraph middleware here.

I was asked over the weekend, “Is this a sign that the HTTP middleware proposal is moving from request+response to request-only?” (/me shrugs) I just figure if there’s such a need for something like that, perhaps it should be made available.

Oh, and I was able to quality-check and release it using Producer.


Multi-Project Issue Tracking With Producer

With Producer, you can get a list of the open issues from your remote origin by running producer issues from the project repository:

$ cd ~/Code/radarphp/Radar.Adr
$ producer issues

    14. Separate Package for ResponderAcceptsInterface?

    29. Service level actions?


However, I’m the lead on about 40 different packages and projects, and at one point or another many of them have issues to be tracked on Github. It’s tedious to go to each package repository to list its issues separately. I want to be able to see a list of all issues on all my projects; then I can review them all at once to see what gets my attention.

To get a list of all open issues on several projects, you can create a bash script that changes to each project directory and runs project issues in each one:

cd ~/Code/atlasphp/Atlas.Cli; producer issues;
cd ~/Code/atlasphp/Atlas.Orm; producer issues;
cd ~/Code/auraphp/Aura.Accept; producer issues;
; ...
cd ~/Code/radarphp/Radar.Project; producer issues;
cd ~/Code/relayphp/Relay.Relay; producer issues;
cd ~/Code/relayphp/Relay.Middleware; producer issues;

Call the script all-issues.sh, make it executable with chmod +x all-issues.sh, and then you can issue ./all-issues.sh to get a list of all open issues on all your projects. Pipe the result to a file for easy viewing if you like!

Producer 2.0.0 Released!

Just a short note to say that most (all?) of the feedback from last month’s inital release of Producer has been incorporated into today’s 2.0.0 stable release!

The major changes are:

  • You are no longer required to install Producer globally. You can now install it as a require-dev in your project and call it as ./vendor/bin/producer. (Personally, I prefer to have it global, but that’s mostly because I manage so many different libraries.)

  • Along with that, Producer now recognizes a project-specific .producer/config file so you can override Producer settings on a per-project basis.

  • Finally, Producer does not install phpunit and phpdoc any more. You will need to install them yourself, either globally or as part of your package. The benefit here is that you can now specify custom paths to phpunit and phpdoc commands in your .producer/config file.

(Producer is a command-line quality-assurance tool to validate, and then release, your PHP library package. It supports Git and Mercurial for version control, as well as Github, Gitlab, and Bitbucket for remote origins.)

PSR-7 and Session Cookies

One of the great things about PHP is its session handling capabilities. One call to session_start() and a huge amount of heavy lifting is done for you. It’s a great aid when writing page scripts.

However, as you start to need finer control over the HTTP response in your project, some of the automatic session behaviors begin to get in the way. In particular, when you are using PSR-7 to build your HTTP response, you realize that session_start() and session_regenerate_id() both automatically do the equivalent of calling setcookie() to write headers directly to the output. This means you cannot buffer those calls into the Response object for later sending.

How then can we use PHP’s session handling, when we want finer control over when and how cookies get sent?

The first trick is to tell PHP not to send a cookie when it does session work. This is accomplished with three ini_set() calls:

ini_set('session.use_trans_sid', false);
ini_set('session.use_cookies', false);
ini_set('session.use_only_cookies', true);

These direct PHP not to use transparent session IDs, not to use cookies, and (counterintuitively) to use only cookies. If I understand correctly, the combination of the last two means that PHP will read only from the cookies, and from nowhere else, to find the session ID value.

With those settings, a call to session_start() will cause PHP to read from the cookie values for the session ID, but it will not cause PHP to set any cookies for the session.

The second trick is to compare the session ID in the incoming request, to the session_id() value at the time you want to send the response. If they are different, that means a session has been started or regenerated, at which point you can send the session cookie manually. The following is an example Relay-compatible middleware that puts session cookie handling logic into effect:


When you examine the class, note that the cookie-creation code is intended to be the same as in the PHP session handling code itself. Note also that you can extract the relevant logic (“compare the Request session ID to the current one, and send a cookie if they’re different”) and use it in a non-middleware-based application.

With SessionHeadersHandler in place, subsequent middleware decorators can call session_start() and session_regenerate_id(), and PHP will no longer automatically write out a session cookie on its own. The handler will set the cookie into a PSR-7 Response object for later sending.

Unfortunately, this is only a partial solution for session headers. The handler does not deal with things like session cache expire and limiter headers. However, it does give you control over when session cookie itself get sent, and that’s a great aid when you want to work with PSR-7 Response objects.

Producer: Validate and Release PHP Library Packages

tl;dr: Producer will look over your Composer-based library package just before you are ready to tag it for release, make sure it appears ready-to-go, and then do the release for you through Github, Gitlab, or Bitbucket. Producer works with both Git and Mercurial.

I. History

Back when I was working on Solar, we needed a process to package up each release of the entire framework and make sure I hadn’t forgotten anything. Thanks to the magic of the Internet Archive, you can see it here. You can read more about the 10 year old (!!!) process here; the script is of course PEAR-centric, since PEAR was the main packaging system available in PHP at that time.

After Solar was done, we began extracting its individual components as 30 or so separate packages in Aura. As before, I needed a process to make sure each package release was actually ready-to-go, so that earlier PEAR-centric release script evolved into a collection of Composer-centric commands. These release and management tools are specifically for Aura, with its particular conventions and expectations in mind, and have served well for 3 major versions of the project over the last 5+ years.

But now I have started some non-Aura projects: Relay, Radar, Arbiter, Bookdown, and most recently Atlas. These projects do not have the benefit of the automated release process, with all of its checks and validation, that Aura does.

With that in mind, then, I have extracted a substantial amount of the Aura package release process into a new project, Producer, so that I can use it with any non-Aura library package. That means you can use it with your library package, too.

II. Why Use Producer?

When you think your Git or Mercurial library package repository is ready for a tagged version release, Producer help to validate that it is actually in a high-quality releasable state. Then, if Producer thinks everything looks good, it will release your library package through its remote origin API (i.e., through Github, Gitlab, or Bitbucket).

(Note that Producer is not for regular daily development work. It is specifically for the day you want to release the package. At that time, it can be easy to forget steps in a release process; Producer manages those steps for you.)

III. Validating A Package For Release

Most of the things Producer checks for are there because I forgot to check them myself at some point in the past, and it was embarassing for one reason or another.

For example, you don’t want to make a release from a local copy that has not been updated to match the remote copy, or when the local copy has some modified or uncommitted files. So the very first thing Producer does is to pull down changes from the remote origin, push up local changes to the remote, and then check the local status to see if there are uncommitted files. If the local status check fails, Producer won’t release the package.

After it’s sure the local copy is in a clean state, Producer will run composer validate to make sure it has no obvious errors. Obviously, if composer.json is not valid, the package is not in a releasable state.

Next, Producer looks to see if you have a particular set of non-empty informational files in place. These are administrative, but necessary: README, LICENSE, CONTRIBUTING, and CHANGES (not CHANGELOG yet; more on that later). The files may or may not have .md extensions. These files ought to be present in any packaged release, so Producer will fail if they are not present, or if they are empty.

Of course, Producer can’t tell if their contents are sensible or not, though it can tell if the LICENSE notes the current year. If it does not, Producer will tell you to update the copyright year in the LICENSE. You don’t want to release a package with an outdated copyright year!

After that, Producer will run your test suite with PHPUnit. Producer expects that you have a phpunit.xml.dist file at the root of your package, so if that’s missing, the release will fail. Likewise, if the tests fail, Producer will not release the package.

As part of the test-running, Producer will issue composer update to make sure all the require-dev packages are in place. After the tests pass, Producer will check the local copy status yet again, to make sure the tests have cleaned up after themselves properly. This also has the benefit of checking that your “ignore” files are set up properly; at the very least, composer.lock and vendor/ should be ignored, so their presence in the status check will cause Producer to stop its release process.

We want to have well-documented code, so Producer runs PHPDocumentor to check all the docblocks in the package src/ directory. (Yes, this presumes that you have a src/ directory in your library package.) If any PHP docblocks are missing or malformed, Producer will stop the release. Similarly, if the @package tags are missing or incorrect, Producer will tell you which files and stop the release. (In this case, “correct” means “the same as the Composer package name”.)

As the next-to-last step in the validation process, Producer examines your CHANGES file commit timestamp. If the CHANGES file is not part of the very last commit, Producer will balk, and tell you to update your CHANGES so that all changes have been mentioned in the release notes. (Producer uses CHANGES because that’s what I use in Aura; at some point in the future, this may become CHANGELOG, but for now just the release-specific change listing seems enough.)

Finally, Producer will go to your remote origin API and retrieve a list of open issues for the repository. This is not technically a validation step, only a reminder in case there issues you have forgotten to address. If there are open issues, Producer will not stop the release process.

IV. Releasing A Package

The above validations can be run on their own using the producer validate command; they are useful as a pre-flight or pre-check to the actual release process. When the validate command finishes successfully, you can move on to the release command; the release process is exactly the same as the validation process, with the added step of actually releasing the package when validation passes.

At this point, Producer has already looked at your .git or .hg configuration to determine the remote origin API; this can be Github, Gitlab, or Bitbucket. It then prepares and sends a release through that API, using the CHANGES file for the release notes.

Afterwards, it will sync the local copy with the remote origin to pull down any newly-created tags.

That’s it; you’re done! Producer has now run a series of “final checks” on your repository and released it with a new version number.

V. Questions

When I announced Producer over the weekend, it got posted to Reddit by somone other than me (for once ;-). I hope I have answered the “why is Producer useful?” question with this blog post. Two others remain:

  1. Q: “Why does Producer run composer update as part of validation? It should only look at the current state of the repo, not modify the repo.”

    A: Perhaps “validation” is not the right word to use. It’s intended as a pre-flight or preparatory step towards releasing, to make sure that the package will actually install what it requires through Composer. In addition, for my own projects at least, the tests use the Composer autoloader for the src/ files, so composer update is a necessary preliminary to running the tests.

  2. Q: “Why require PHPDocumentor and PHPUnit as part of Producer? What if I have those already installed somewhere else?”

    A: For myself, global installs of these kinds of ecosystem-level tools seem reasonable, but that may be a function of the fact that I mostly manage library packages; having 30+ installs of PHPUnit and PHPDocumentor is just not to my liking. Having said that, I can see how some folks would have different requirements, so I’ll see if I can modify that requirement in a future version of Producer.

Finally, if you have questions/comments/critique, please raise them as issues over at Github. Thanks, and I hope Producer is as useful to you as its earlier versions in Aura have been to me!