Hacker News new | past | comments | ask | show | jobs | submit login
Laravel Transporter (juststeveking.uk)
41 points by mooreds 42 days ago | hide | past | favorite | 23 comments

I do appreciate the structural parity that this has with Laravel's Eloquent ORM models--that is, a class to represent a specific database resource. It makes API requests feel like a close analogue to that Laravel-esque functionality.

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;

A bit more standard way of generating HTTP API clients, would be to use openapi spec [1] and then generate php client. It makes you app client independent of framework and also language itself.

[1] https://github.com/OpenAPITools/openapi-generator

This is the approach I use, but I've always found some sort of issue in every generator I use. A valid openapi spec does not imply a generator will be able to handle it. You'd think have a specification would mean the generators are "bug free" but it's not the case, and bug reports are lost in a sea of issues: https://github.com/OpenAPITools/openapi-generator/issues so i don't see things improving anytime soon.

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.

Seems like a Solution in search for a Problem. Instead of hardcoding the URL to the Request it can be set in the config like it was done with jsonplaceholder.api.token.

And creating a Request for every API-Endpoint instead of a single API-Client Class seems overkill.

I think it makes sense if you're doing the same 5 requests over and over throughout your application. As it stands, I generally build a service class for these scenarios:

  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.

I'll half-agree and half-disagree with other comments, I think.

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.

On the other hand PHP has this wonderful feature in associative arrays. Easily serializable, fast, deep equality, plain and simple, generic and flexible data structures that seamlessly convert to JSON (and thus schema) and have well supported language constructs.

Yep. I like PHP, warts and all. I know some consider PHP's arrays to be one of those warts, since many other languages would divide their functionality into single element arrays or lists and some kind of key/value structure. I appreciate the semantic case for that, but it seems rare for it to be a significant issue in practice. (Which I've found to be true of many of PHP's warts, particularly once PHP 7 arrived.)

I deal mostly in PHP and never thought that making API requests was hard. Am I lost?

The hard part is dealing with the fact that APIs are not standardized, even within the same company.

It is not difficult but it can end up being more hard-coded than it should be.

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.

My feeling is the opposite- this is just overengineering, adding more code to test and maintain. The simple solution is what countless others have suggested - build a simple client, configure it using standard Laravel mechanisms and dependency injection. An API Call is just a method on the service class.

Isn't this package just a different solution for a Repository Pattern?

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.

I find the naming of "Laravel Forge" a bit unfortunate. When I read code like "use App\Transporter\Forge\ForgeRequest", one of two things come to mind:

- "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?!"

I don't see value in using this wrapper library over Laravel's HTTP Client or Guzzle. What Laravel includes is more than adequate already. Also, I never hardcode URLs and tokens. There's a config in Laravel for that. Nobody is going to call Guzzle or HTTP directly every time anyway. It will be a Service or a Client class that exposes each endpoint as a method and abstracts all underlying logic.

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.

This feels like over-engineering a problem at it's finest. Taking 3 lines of ubiquitous code and abstracting in a new way, when it already does what you need out of the box.

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.

[1] https://docs.guzzlephp.org/en/stable/quickstart.html

Exactly what I came here to say.

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.

The endpoint URL should probably be stored in the environmental variable file.

> The endpoint URL should probably be stored in the environmental variable file.

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.

To be fair it does seem to fit the way I think Laravel applications are built.

>> What happens if the URL changes?

> 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

> a tiny webservice which makes a single request to one URL path

that's how they all start out, don't they?

totally agree with other commenters - guy just over-engineered non-existent problem

Please fix the contrast ratio of quotes and inline code segments in dark mode on this website. It's black text on dark blue background, which is barely readable.

Applications are open for YC Winter 2022

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact