Laravel Transporter
juststeveking.uk
juststeveking.uk
That said, REST API endpoint are effectively just functions--you pass in params, you get a response. They only have a single operation possible on them, unlike ORM models which can do many things.
Whenever I've implemented a custom API client in PHP, boring old OOP seems to solve the problems stated in the article reasonably well:
class ApiConsumer {
function __construct($baseUrl, $credentials);
function operationFoo($paramOne, $paramTwo): FooResponse;
function operationBar($paramOne, $paramTwo): BarResponse;
}I've also wondered if the "logic-less" template approach is the right approach for these generators. You can't fix bugs or add workarounds just by providing a custom template, you need to change the underlying Java code to provide the correct data models to the logic-less templates.
And creating a Request for every API-Endpoint instead of a single API-Client Class seems overkill.
class GoogleMapsApiService {
public function geocodeAddress(string $address);
public function getDirections(string $address);
}
That sort of thing. It's all reusable and I can just call `app(GoogleMapsApiService::class)->getDirections('123 Main St')` to request the data. This just goes a step further and makes it more flexible (and probably more testable) by turning it into a class. Creating the class is a simple command line run (if you're familiar with Laravel, much of your application is scaffolded this way).I think it loses its effectiveness/value if you're using it for 20 different requests that are only used once each, but for a scenario where you're making 1 request in 10 different places like you might be with geocoding, it's not a bad implementation in my opinion.
First, I think this is a clever way to wrap API requests in what strikes me as the modern PHP style, which these days seem to largely driven by the de facto standards of PHP-FIG in general and Laravel and Symfony in particular. Use a CLI tool to generate a class that extends your base class and adds configuration, extend that class to build new more granular methods, maybe make a static facade to call everything. This is just super Laravel-ish and absolutely what I expect from modern PHP apps in 2021.
Second, it is overengineered, because that is what modern PHP apps do. Spend a bit of time tracing just how much goes into encapsulating a PSR-7-compliant request, and think about how often code that's using those requests literally doesn't need anything more than the $REQUEST superglobal. (Gasp! They said "global!" Think of the children!) It's great to have standards for packages, but it seems like I'm spending way, way more time -- and occasionally writing more actual code -- figuring out how to do things The Right Way (tm).
In the argument for Laravel Transporter, Mr. McDougall shows a bit of pretty clear code and then says, "But, this is very procedural." I mean, yes? It's kind of a procedure? "What happens if the URL changes? What happens if the query parameters change?" If I was insistent on wrapping this up in a class, I'd probably just write an API class that you call with something like
$api = new FooApi();
$todo = $api->getCompletedTodos();
// or if I am feeling all static
$todo = Api::getCompletedTodos();
and if the URL changes or the query parameters change, I still basically only have one place to change it.I'm not saying I pine for the days of 100% Procedural All Pasta All The Time. I'm just wondering whether maybe PHP developers should spend some more time with languages that not only aren't PHP, but aren't Java, then come back to PHP -- and start questioning the orthodoxy a little.
The hard part is dealing with the fact that APIs are not standardized, even within the same company.
The author of this package is attempting to help Laravel users standardize their API client code and centralize those hard-coded bits in one place.
It isn't groundbreaking, but it could be useful in larger projects or across several projects.
Create a repository class which contains all the objects and methods for the API and then use this repository as a bridge between the API and your controllers.
Business and data access logic can be tested separately and it's easier to maintain.
- "what's a Forge? Is this a fancy name for the Factory pattern?"
- "are they forging (as in forgery) requests here? Or further on, 'services.forge.token' - sounds strange?!"
What's more important when doing API implementation seriously is defining and enforcing API contract and getting an exception as soon as the contract is broken. I'm thinking about something similar to BaseModel from pydantic in Python. This library provides nothing of this kind.
The base url and header details such as api token can all be set in the client in an OOP way already[1]
> What happens if the URL changes?
Cool urls don't change. Use configs.
You don't sprinkle URLs throughout your codebase, as the OP apparently assumes one would normally do. You create a single class (or something similar) which abstracts the API calls. If the endpoint changes, you change it in one place.
I honestly don't see how this solution makes this easier. If anything, I feel like it makes things more complicated and harder to understand.
As I mentioned, if the endpoint changes, you change it in one place. Storing your endpoint URLs in an env file is your personal preference, but that's all. Should has no place here.
It doesn't matter whether it's an environmental variable file, a YAML configuration, a JSON file, or a secure remote KV-store. Barring low water marks, it doesn't really matter where you store your configuration—just make sure to store it in one place.
> Cool urls don't change. Use configs.
I also kinda think that for many webapps, the risk of this is somewhat overstated. I've got tons of apps with a few hard-coded URL paths that point at other internal apps that I own. They don't break because I don't randomly remove URL paths from services. When I do need to change something, find-and-replace + an integration test keep me safe 99.99% of the time (and the blast radius from a failure like this is usually very small - obvious 404 error bubbles up immediately the first time someone hits something broken).
That said, I agree that keeping constants like URL paths in configs is a fine idea. I also feel that common sense is required - a tiny webservice which makes a single request to one URL path probably is fine to just hard-code it (and in fact putting all constants into config might be overly obfuscating), but a big app which makes dozens or hundreds of calls obviously benefits from a config-oriented approach
that's how they all start out, don't they?