The "pds/composer-script-names" standard is now stable!
I am happy to announce that the PHP Package Development Standards publication pds/composer-script-names has been released as stable at version 1.0.0.
Don't listen to the crowd, they say "jump."
I am happy to announce that the PHP Package Development Standards publication pds/composer-script-names has been released as stable at version 1.0.0.
The PHP Package Development Standards (PDS) initiative researches the PHP package ecosystem to extract common practices existing in real packages. It rationalizes and refines those practices, then publishes them as PDS packages for reference by PHP package authors.
After several weeks of effort, and two weeks of private review, the pds/composer-script-names publication is now open for public review.
This publication defines a standard for Composer script names regarding, as well as a naming standard for those scripts, revealed by examining the entire Packagist ecosystem.
Please leave your review comments as issues on the research repository so we can maintain a good historical record.
When participating in public review, please:
Review the research methodology and the results to make sure I have not missed anything important, that the assessments make sense, and that there are no other stronger interpretations.
Review the final conclusions and recommendations to make sure they follow reasonably from the research.
Note for typos, grammatical errors, and other drafting issues.
Thanks!
Matthias Noback's Advanced Web Application Architecture (AWAA from here on) is excellent throughout. You should buy it and heed its advice. It is a wonderful companion or followup to my own Modernizing Legacy Applications in PHP -- which is still free, though of course I'll happily take your money.
Having read through the book, I paid special attention to the sections on Application Services. The pattern is a good one, and you should use it, but I find I must offer an alternative approach to the AWAA treatment of the pattern.
tl;dr:
AWAA suggests that an Application Service may cover either single or multiple use cases.
An application service could have a single method (like the
__invoke()
method). In that case the service represents a single use case as well. But since several uses cases may share the same set of dependencies you may also create a single class with multiple methods. (pp 255-256)
While it is true that some form of efficiency is gained when use cases "share the same set of dependencies," I think that the gains are illusory.
Instead, I say an Application Service should cover one-and-only-one use case:
One Application Service per use case makes the available use cases more visible when browsing through the directory structure. It becomes trivial to see what is supported by the system, and what may not be.
Doing so sets up a standard practice within the system, which in turn helps to avoid questions like "Should I put this use case in an existing Application Service, or should I create a separate one?" With this practice in place, there's no need to think about it: always create a separate one.
It reduces merge/rebase conflicts when multiple developers are working in the Application Service code. For example, if three developers are working on three separate use cases inside a single Application Service, the chances for conflict are increased. If the use cases are each in separate Application Services, the chances for conflict are reduced.
While most use cases may start out simple, some will become more complex, and it is difficult to predict which ones. As a use case becomes more complex, you already have a separate "space" to work in if it is in separate Application Service. The need for extracting and creating a new Application Service is pre-empted when each use case already has its own Application Service.
Finally, a single use case Application Service maps very nicely to a single ADR Action, or to a single-action MVC controller. It makes dependency injection much more specific and targeted.
AWAA says Application Service methods are Command methods:
Application service methods are command methods: they change entity state and shouldnât return anything. (p 257)
But then it goes on to say ...
However, when an application service creates a new entity, you may still return the ID of the new entity. (p 257)
That way, the code that called the Application Service can "use the ID that was returned by the application service to fetch a view model from its view model repository." (p 257)
Here is an example of what the book means; these examples are mine, and not from AWAA. First, the Presentation code, e.g. from a controller action method:
$orderId = $this->createOrderService->__invoke(/* ... */);
$order = $this->orderViewRepository->getById($orderId);
/* ... present the $order in a Response ... */
And the relevant Application Service code:
$order = new Order(/* ... */);
$order = $this->orderWriteRepository->save($order);
return $order->id;
However, I disagree that an Application Service ought to be treated in general as a Command.
While some Application Services might be Commands, the fact that sometimes the Application Service should return something indicates that they do not as a whole follow the Command pattern. It is true that a Command may "publish events" through an event system, but a Command is never supposed to "return" anything.
Instead, I suggest treating the Application Service as a Facade.
That is, an Application Service should be seen as a Facade over the underlying domain operations. A Facade is allowed to return a result. In the case of an Application Service, it should return a result suitable for Presentation.
Modifying the above example, the code becomes something more like the following. First, the Presentation code:
$order = $this->createOrderService->__invoke(/* ... */);
/* present the $order in a Response */
And the relevant Application Service code:
$order = new Order(/* ... */);
$this->orderWriteRepository->save($order);
return $this->orderViewRepository->getById($order->id);
The details may be up for discussion, but the larger point stands: treating the Application Service as a Facade, not a Command, creates a more consistent and predictable idiom, one that actually adheres to the stated pattern.
Once we treat the Application Service as a Facade instead of a Command, we might ask what it should return. In the above simple cases, the Application Service returns a View Model (per AWAA). However, that presumes the operation was a success. What if the operation fails, whether due to invalid input, raised errors, or uncaught exceptions, or some other kind of failure?
My opinion is that the Presentation code should not have to handle any errors raised or exceptions thrown from an Application Service. If the Presentation code has to catch
an exception that has come up through the Application Service, and then figure out what those errors mean, the Presentation code is doing too much -- or at least doing work outside its proper scope.
As a solution for that constraint, I have found that wrapping the Application Service result in a Domain Payload is very effective at handling both success and failure conditions. This allows a consistent return type from each Application Service in the system, and allows the Presentation code to standardize how it presents both success and failure.
For example, the above Presentation code can be modified like so (note the use of an ADR Responder to handle the Response-building work):
$payload = $this->createOrderService->__invoke(/* ... */);
return $this->responder->__invoke($payload);
Likewise, the relevant Application Service code:
try {
$order = new Order(/* ... */);
$this->orderWriteRepository->save($order);
$result = $this->orderViewRepository->getById($order->id);
return new Payload(Status::CREATED, order: $result);
} catch (Throwable $e) {
return new Payload(Status::ERROR, error: $e);
}
The inclusion of a Domain Status constant lets the Presentation code know exactly what happened in the Application Service. This means the Presentation code does not have to figure out what the results mean; the Payload tells the Presentation code what the results mean via the Status.
Regarding event subscribers and eventual consistency, AWAA advises:
Instead of making the change inside the event subscriber, delegate the call to an application service. (p 270)
If the Application Service actually is a Command, as AWAA suggests, this may be fine.
However, when using a Payload as the return from each Application Service, no Application or Domain activity should delegate to any other Application Service. The Payload is always-and-only to be consumed by the Presentation code, never by any other kind code.
I suggest that if you find you need to use the logic from one Application Service anywhere else, and you are using Payload returns, you should extract that logic from the Application Service and put it into the Domain where it belongs. Then event subscribers can call the relevant Domain element, instead of an Application Service.
These alternative offerings should in no way discourage you from buying and reading Advanced Web Application Architecture by Matthias Noback. It is a very good piece of work with a lot to offer. My suggestions here are more an alternative or refinement than a refutation or rejection. As a developer, you should consider Noback's advice as well as the advice presented here, and determine for yourself which is more appropriate to your circumstances.
I am fond of saying that in server-side applications, the Template is not the View. Instead, the HTTP Response is the View. That is, the Response presented by the server is not only the templated content; the presentation includes both the headers and the content in the Response.
But what if you can build the HTTP Response inside a Template?
That is just what the new Qiq helpers using Sapien, let you do. Using the qiq/helper-sapien package, you can set Response headers, cookies, etc. from inside your template code ...
{{ response()->setVersion('1.1') }}
{{ response()->setCode(200) }}
{{ response()->setHeader('content-type', 'text/plain') }}
Hello, world!
... and get back a complete Response object for sending:
$response = $template->response()->render($template);
$response->send();
In addition, you can use setFile()
to build a file-download Response ...
{{ response()->setFile('/path/to/file.txt') }}
... or setJson()
to build a JSON Response:
{{ response()->setJson(['foo' => 'bar']) }}
Does this mean the Template is the View after all? If so, it's only because the entire Response is built by the Template. Perhaps this could be called a ResponseView.
Will this make a Qiq template into a complete ADR Responder? Maybe, maybe not -- but it does mean that Responder implementations have the option to hand off most or all Response-building work to Qiq template code.
I am pleased to announce that I have released Capsule version 3.4.0, now with argument inheritance. (You can see the rest of the changes here.)
As an autowiring dependency injection system for PHP 8.0+, Capsule supports injection of constructor arguments. With the 3.4.0 release, each class defined in Capsule now "inherits" the constructor arguments of its parent. This means you can specify a constructor argument on a parent class, and the child class will use it (that is, unless you set an overriding value on the child class).
Let's say you have this class hierarchy:
abstract class Strategy
{
public function __construct(
protected string $path
) {
}
abstract public function doSomething() : string;
}
class FooStrategy extends Strategy
{
public function doSomething() : string
{
// ...
}
}
class BarStrategy extends Strategy
{
public function doSomething() : string
{
// ...
}
}
If you define the constructor arguments for the parent ...
$def->{Strategy::CLASS}
->argument('path', '/path/to/resources');
... both FooStrategy
and BarStrategy
will automatically "inherit" the
defined $path
value. You won't have to re-define it for every Strategy
class extension.
You can always override the "inherited" values by specifying them for the child class directly ...
$def->{FooStrategy::CLASS}
->argument('path', '/path/to/other-resoruces');
... in which case the children of that class will inherit the override value. In this way, constructor arguments can be propagated down the inheritance hierarchy.
If you decide that want to disable or interrupt the "inheritance" of arguments for a class, call inherit(null)
on its definition:
$def->{BarStrategy::CLASS}
->inherit(null);
The same people who told you ...
- Brexit would never happen,
- that Trump would never win,
- that when he did win it was because of Russian collusion but also because of racism,
- that you must follow lockdowns while they donât,
- that masks donât work,
- that masks do work,
- that social justice protests during pandemic lockdowns are a form of âhealth intervention,â
- that ransacking African American communities in the name of fighting racism is a âmostly peacefulâ form of protest,
- that poor and underserved children locked out of shuttered schools are âstill learning,â
- that Jussie Smollett was a victim of a hate crime,
- that men are toxic,
- that there is an infinite number of genders,
- that COVID couldnât have come from a lab
- until maybe it did,
- that closing borders is racist
- until maybe it isnât,
- that you shouldnât take Trumpâs vaccine,
- that you must take the vaccine developed during the Trump administration,
- that Andrew Cuomo is a great leader,
- that Andrew Cuomo is a granny killer,
- that the number of COVID deaths is one thing and then another
... are the same people telling you now that the vaccine is safe, that you must take it, and that if you donât, you will be a second-class citizen.
With light editing, from https://www.tabletmag.com/sections/news/articles/vaccines-konstantin-kisin.
It means that a small group â letâs call them the nomenklatura â has absolute control over a much larger one, in every aspect of life, society, industry, and commerce. That control is enforced by:
- a secret police,
- a system of incentives to induce âgood citizensâ to betray âdissidentsâ to those police,
- and a system of concentration camps for the dissidents.
Within the nomenklatura there is intense jockeying for advancement, plenty of scheming and maneuvering, and enough backstabbing to make any intrigue writerâs salivary glands go into tenth gear. For there are large differences in the degree of privilege enjoyed according to oneâs altitude within the nomenklatura.
Yet all this is rationalized as being for âthe people:â the subject group, who must stand in endless lines merely to buy a loaf of bread.
Lightly edited for formatting, via http://www.libertystorch.info/2021/11/22/when-systematic-analysis-is-no-longer-necessary/.
I am happy to announce the 1.0.0 release of Sapien. Like its predecessor ext-request, Sapien provides server API (SAPI) Request and Response objects -- but in userland, for PHP 8.1.
The Sapien classes do not model HTTP messages per se. Instead, the Request is a readonly value object composed of other readonly value objects representing the PHP superglobals and data structures derived from or composed of them. Similarly, the Response is a wrapper around and buffer for the various response-related global PHP functions.
Read more about Sapien at http://sapienphp.com.
The Sapien project has a long history, starting about 5 years ago as PECL extension in C. It had a second major version bump as part of an RFC to PHP itself, which did not pass.
In the after-action report on the failed RFC, I recorded the prevailing opinion that things like request and response objects should stay in userland. The problem with that, in the case of ext-request, was that readonly immutable objects were possible more effectively in C code than in userland PHP.
However, with the advent of PHP 8.1 and readonly
properties, all the functionality of the C code is now available in userland PHP. In a way, that means Sapien is a third version of ext-request, but fully in userland.
Sapien makes extensive use of readonly
, presenting a Request value object that is designed to be unchanging after instantiation. And because it's much easier to work in PHP than in C, there are many more value objects composed into the Request than in the C versions, such as:
Content, built up from various content-related headers and the request body itself
Upload, including the new full_path
value from $_FILES
entries.
Url, computed from the various $_SERVER
values
Accept value object collections for Type, Charset, Encoding, and Language
XForwarded, distilled from the various x-forwarded-*
headers
Forwarded value object collections derived from the Forwarded
header
Authorization\Scheme value objects for basic, bearer, and digest authorization
The Response is fundamentally the same as it was in ext-request v1, though the custom file- and JSON-specific response logic has been extracted to specialized classes. Even so, the generic response is capable of handling all sorts of content, including iterables and resources.
So, if you're starting a new PHP 8.1 project, or refactoring a legacy system to PHP 8.1, try out Sapien, because request and response objects make life so much easier.
I'm happy to announce the first release of Qiq, a template system for developers who prefer native PHP templates -- but with a light dusting of syntax sugar when you want it. It offers partials, layouts, sections, and a wide range of HTML helpers for tags and forms, along with explicit but concise escaping.
I don't like compiled templates or specialized template languages. Smarty, Twig, etc., are all just too heavy-handed. I don't need a new language, and I don't need to "secure" my templates against designers on my team. I am generally happy with plain PHP as a template language.
However, I do find escaping tedious -- necessary, and easy enough, but tedious. A template helper to output escaped HTML, like this ...
<?= $this->h($var) ?>
... is not that bad -- but even so, it could be a little easier. Imagine this little bit of syntax sugar:
{{h $var }}
All that happens is that {{h ... }}
is replaced with <?= $this->h(...) ?>
.
Once that is in place, it becomes easy to support helpers, control structures, and other code, all while keeping native PHP as the fallback syntax, because the {{ ... }}
tags are essentially stand-ins for PHP tags.
Qiq is PHP -- just with some syntax sugar. Try it out on your next project!
Some high points from a successful virtual beat-down of SJWs:
Know what to expect. The cancel crowd has its own bullet-point playbook. And theyâll respond aggressively to any symbolic act that threatens their status, or erodes the impression that they are the ones calling the shots. Remember that behind the social-justice veneer lies the brutal logic of power and ego.
To maximize the pain you feel, theyâll tag activist groups on social media to inflate their numbers and reach.
Theyâll bombard every organization youâre part of with demands to censure, discipline, disown, fire, or expel youâoften phrasing their appeals in the passive aggressive guise of âconcernâ and âdisappointment.â
At other times, they will insult, taunt, and, threaten you in a manner resembling middle-school children having a recess meltdown.
In my case, the ringleader called me âa full on misogynist and racist,â âshameful bigot,â âhypocrite,â âclueless,â âtone-deaf,â âsnowflake,â and âsoulless troll.â
She assailed my âprivilege and patriarchy,â âlack of basic empathy and ethics,â and âzero self-awareness.â
She also questioned whether Iâm really a human, and called on NeurIPS to ban me, and for my department to expunge me.
Her goal, in short, was to ruin my life.
The cancelers will dig up anything they can from your past. And if they canât find any, theyâll make it up.
This will all seem terrifying, but much less so if you realize that youâre just the latest victim in what is basically a mechanical and dehumanizing process. Insofar as you donât actually get fired from your job or suffer some other equivalent setback, these are all just words, and they donât define who you are.
...
Donât back down. Donât apologize. Donât make clarifications, and donât try to appease the mob. All of these will only be taken as concessions, and embolden the mob to demand more. The real Achillesâs Heel of the cancel crowd is its short attention span. Once they bully someone into submission, they move on to the next victim. Itâs a system designed for quick wins. If you donât back down, theyâll raise the pitch as far as they canâbut eventually theyâll be at a loss for what to do next, and all but the most fanatical will lose interest. The few that remain, now bereft of their backup, are just what you need to teach all of them a lesson, as we did in my case.
...
Turn their weapons against them. You may find this to be the most controversial principle, but itâs also arguably the most crucialâas the cancelers wonât stop until they fear that theyâll endure the same consequences that they seek to impose on others. In my case, I watched as investors and customers leaned on the ringleaderâs company to rein her in. Even companies that posture heavily in the area of social justice donât actually want to be stained by the disgraceful behavior of mob leaders. Indeed, I have no doubt that it was an ultimatum from her employer that finally led the ringleader to stop her Twitter outbursts and apologize publicly to her victims, for all to see. Some will say that once we resort to this step, we become as bad as the cancelers. But thatâs a false equivalence. The cancel crowd tries to ban people because of their views. We try to stop bullyingâbehavior that is reprehensible regardless of ideology.
(Edited for formatting.)
See also the Social Justice Attack Survivial Guide.