Jekyll2024-05-06T01:20:48+00:00https://softonsofa.com/feed.xmlSoftOnSofaWriting codes for OSS and Aspire FT in Singapore. Contributor to open-source frameworks and packages, father, a close friend with barbell.OAuth authorisation basics2020-06-30T04:01:01+00:002020-06-30T04:01:01+00:00https://softonsofa.com/oauth-authorisation-basicsOAuath - how to grant access to my data to another app

Something for the eyes:

image

Imagine you need to connect your application with another one to exchange some data for your users.

Real-world (almost) scenario

Let’s talk about sligthly naive example of an app which facilitates budgeting and reminders for the payouts and receivables. On the other hand, your users use Xero for the accounting purposes. The scenario here will be that we need to get particular data about invoices from Xero in order to run our logic.

Here comes the OAuth protocol - it will make it super easy for the user to connect your app with Xero in 3 simple steps:

  1. User click connect with Xero in your app
  2. User is redirected to the Xero authorisation page, where they choose which organisation to allow be accessed by your app
  3. User gets back to your app and it’s done

Under the hood there will be likely more than just that, but it’s the implementation detail, which user’s don’t have to be bothered with.

How can I use it?

  • From now on you can access all resources in Xero on behalf of your User as per scope requested during step 2 above (which you define for your needs in your implementation)
  • Anytime you can disconnect the Organisation you are using in your app OR disconnect whole Xero integration

Caveats

If you happen to have multiple use-cases for Xero in your application, you may want to ask User to connect Xero again for different purpose. Now, let’s assume you allow user to disconnect it for each use-case separately - here you want to be careful and make sure you don’t actually disconnect Organisation or whole Xero integration, for you’d end up with all use-cases broken, not just one of them.

]]>
Interface HowTo2020-05-15T04:01:01+00:002020-05-15T04:01:01+00:00https://softonsofa.com/interface-howtoInterface HowTo

A great intro to refresh the basics

In short: an interface defines what functionality you need in a class you depend on (while not caring about the implementation). It helps in designing the code and allows deferring implementation details, so they don’t get in the way.

TLDR;

  1. define functional methods, you MUST NEVER define implementation details as part of the interface
    • you MUST NEVER define setters on an interface - they are ALWAYS implementation detail
    • you MUST NEVER define an interface for so-called fluent api/interface (object returning itself) - fluent api is ALWAYS implementation detail
  2. you SHOULD avoid using associative arrays in both input & output - prefer VOs/POPOs/DTOs and Entities

Let’s think of an example feature request Integrate 3rd party API to allow our users to make transfers

Usual approach - DON’T:

  1. review API documentation
  2. pull or write SDK for it
  3. write the configuration for the API connection
  4. write the actual functionality that serves the users

Much Better way:

  1. write the actual functionality that serves the users (based on mockups & specs)
  2. write implementation to support that functionality
  3. pull or write SDK that will be necessary for the implementation
  4. write the configuration for the API connection

Why is the usual approach not what you want?

  • Because the most important thing (value for the user) comes last. By the time we had a chance to actually design how it works, we already put a lot of constraints on ourselves by whole implementation.
  • If we discover at that point that we made wrong assumptions, it is hard and takes time to make changes.
  • We may spend a significant amount of time implementing things that we don’t actually need

The right might not feel natural initially, and so requires a bit different approach. Let’s see an example of the code in the order we would write it:

// 1 entry point for user's input
class TransferController
{
    function makeTransfer(TransferRequest $request, TransferService $transferService)
    {
        try {
            $transferService->send($request->getTransfer());

            return new OkResponse; // 200 OK
        } catch (ClientTransferException $e) {
            // API validation error caused by the Client (balance, invalid bank account number etc)
            return new ClientErrorResponse($e->getMessage()); // 400 Bad Request
        } catch (ServerTransferException|Throwable $e) {
            // API validation error caused by Server implementation (bug, runtime error etc)
            return new ServerErrorResponse($e->getMessage()); // 500 Internal Server Error
        }
    } 
}

// 2 input validation, parsing, sanitizing and turning it into domain-specific objects
class TransferRequest extends ApiRequest
{
    function rules(): array
    {
        return [
            'amount' => 'required|int',
            'holder_name' => 'required|string',
            'currency_code' => new ValidCurrencyRule($this),
            'account_number' => new ValidAccountNumberRule($this),
        ];
    }

    function getTransfer(): TransferEntity
    {
        return new TransferEntity(
            Money::make($this->input('amount'), $this->input('currency_code')),
            TransactionParty::make($this->input('account_number', $this->input('holder_name')))
        );
    }
}

// 3 expected low-level functionality that we will need
interface TransferService
{
    function send(TransferEntity $transfer): void;
}

At this point we:

  1. are done with write the actual functionality that serves the users
  2. don’t have to get back to this part of the code again
  3. can easily write automated tests (we may already have them - TDD comes naturally with such code)
  4. know exactly what the next step is

The next step is driven by the interface here: $transferService->send(TransferEntity $transfer) - let’s write the implementation then!

// 1
class Transferwise implements TransferService
{
    function send(TransferEntity $transfer): void
    {
        // we need to call API somehow - let's assume there's SDK that we will use
    }
}
// 2
class Transferwise implements TransferService
{
    function __construct(TransferWise\Client $client)
    {
        $this->client = $client;
    }

    function send(TransferEntity $transfer): void
    {
        $this->client->sendTransfer(
            // 1 pass data from the $transfer
            $transfer->getAmount() / 100,
            $transfer->getCurrencyCode(),
            ...
            // 2 let's assume we need more than just that, eg. some configuration
        );
    }
}
// 3
class Transferwise implements TransferService
{
    function __construct(TransferWise\Client $client, Transferwise\Config $config)
    {
        $this->client = $client;
        $this->config = $config;
    }

    function send(TransferEntity $transfer): void
    {
        $this->client->sendTransfer(
            $transfer->getAmount() / 100,
            $transfer->getCurrencyCode(),
            $this->config->default_transfer_type,
            $this->config->markup_amount,
        );
    }
}

This pretty much covers points 2 & 3. Last thing would be to sprinkle the class with standard things like LOGGING, ERROR HANDLING etc - those should come last, as it’s low level stuff that should never get in the way of functionality/value that we provide to the user.

// 4
class Transferwise implements TransferService
{
    function __construct(TransferWise\Client $client, Transferwise\Config $config)
    {
        $this->client = $client;
        $this->config = $config;
        $this->logger = new NullLogger;
    }

    function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    function send(TransferEntity $transfer): void
    {
        $this->logger->info('useful log');

        try {
            $this->client->sendTransfer(
                $transfer->getAmount() / 100,
                $transfer->getCurrencyCode(),
                $this->config->default_transfer_type,
                $this->config->markup_amount,
            );
        } catch (Transferwise\SomeError $e) {
            $this->logger->info('useful log');
            throw new ClientTransferException('Transfer Failed', 0, $e);
        } catch (Throwable $e) {
            $this->logger->info('useful log');
            throw new ServerTransferException('Transfer Failed', 0, $e);
        }
    }
}

And finally we write configuration for the API connection:

class TransferwiseServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(Transferwise\Config::class, fn () => Config::fromArray([
            'default_transfer_type' => config('...'), // or env(...) if different in dev/prod env
            'markup_amount' => config('...'), // or env(...) if different in dev/prod env
        ]));
    }
}

Why not setters & fluent api?

It might be tempting to define methods for fluent interface:

interface TransferService
{
    // NEVER DO THIS:
    function setSomething(SomeGenericType $something): self;
    function setAmount(Money $amount): self;
    function setTransfer(TransferEntity $transfer): self;
    function send(): void
}

// NEVER DO THIS:
$transferService
    ->setSomething($something)
    ->setAmount($amount)
    ->setTransfer($transfer)
    ->send();

Such approach breaks rule 1 completely and creates very rigid structure. That is because the ONLY thing we care about from user’s perspective is send() method, and having fluent api defined on the interface binds us to this approach for all implementations.

You can still use fluent api if this is your preferred way. It’s great for builder objects, but not for functional services.

An example of proper interface and fluent builder - both achieve exactly the same goal and are valid implementations from user’s perspective:

// Standard approach
$transferService->send(
    new TransferEntity($amount, $recipient, $description)
);

// Fluent api approach
$transferService->send(
    TransferEntity::builder()
        ->setRecipient($recipient)
        ->setAmount($amount)
        ->setDescription($description)
);

Why types rather than (associative) arrays?

Associative arrays in general SHOULD only be used within private scope but in public api prefer types. Standard arrays are perfectly fine, as long as they represent a simple list and we don’t depend on the keys.

Example:

public function getTransfer(): TransferEntity
{
    $transfer = new TransferEntity;
    $transfer->property_a = 'value for a';
    $transfer->property_b = 12345;
    $transfer->property_c = 'value for c';
}

// VS

public function getTransferData(): array 
{
    return [
        'property_a' => 'value for a',
        'property_b' => 12345,
        'property_c' => 'value for c',
    ];
}

Why array is really BAD

  1. consuming array as output from a class - you HAVE TO KNOW the keys inside -> you need to waste time to find the definition and/or usage for the array. As array is just a primitive type, IDE won’t be able to offer FIND REFERENCES to help you.
  2. usually it requires if and fallback values: $array['key'] ?? null or $array['key'] ?? 'default'
  3. it’s easy to make typo that goes unnoticed - you can easily ship code to production without spotting because of the point 2
  4. static analysis tools in CI won’t catch errors just like IDE
  5. if you need to change/add key to the array, you waste time finding where it is used - again, no FIND REFERENCES in IDE

Why object is helpful

By implementing object, even simple POPO, you get rid of ALL of those problems:

class TransferEntity
{
    public ?string property_a = null;
    public ?int property_b = null;
    public ?string property_c = 'default';
}
  1. consuming the object is fast and easy -> IDE has your back and completes properties + their types
  2. no need for if / isset checks
  3. typos will be caught as early as in the IDE when you type
  4. if something still went unnoticed, static analysis will be able to catch it during a commit or later in the CI
  5. changing payload means that IDE helps you with FIND REFERENCES

There is no downside to this approach:

  • performance-wise we don’t need to worry about primitives vs objects
  • creating a class takes just a few seconds more that an array
  • we can write fewer tests, having strict types on the objects
]]>
20 Eloquent tricks - more2018-04-14T04:00:00+00:002018-04-14T04:00:00+00:00https://softonsofa.com/20-eloquent-tricks-moreRecently I stumbled upon interesting article by Povilas Korop on laravel-news 20 eloquent tips and tricks. First of all thanks for sharing your knowledge Povilas – it helps many people out there, keep it up!

As you may know, I’ve worked with Eloquent for long and love its features, at the same time I’ve become very sensitive about some dangers lurking for inexperienced developers that come with all the magic of Eloquent :) Let’s use this opportunity to look at the tips from different perspective and learn even more by going through some of them in more details!

In order to make it valuable for you I’m keeping it as concise as possible:

  1. in/decrement: GOOD

  2. findOrFail: GOOD, but there’s a catch in findOrFail. Laravel internally handles ModelNotFoundException here and it means that any call that fails here, will trigger a 404 NOT FOUND response from your app. This works fine when called for example for something like this:

     Route::get('products/{id}', function ($id) {
    
      return Product::findOrFail($id);
    
     });
    

    Laravel uses the same pattern for implicit route model binding described here

    However, if you’re calling your query deeper in the code of your application, you do not want to automatically trigger 404 for any not-found record, as it might have nothing to do with the routing or even HTTP layer at all.
    That being said, findOrFail is cool for top, HTTP layer. Other than that I recommend something between the lines of (yes, oldskool, even archaic, no magic involved… you get the idea):

     $model = $someQuery->find($id);
    
     if (is_null($model)) {
      // handle logic when record is not found
     }
    
  3. boot GOOD

  4. default ordering on relationships – definitely no (unless the relationship itself explicitly states that):

     // Organization
     public function approvedUsers() {
      return $this->hasMany('App\\User')->where('approved', 1)->orderBy('email');
     }
    
     // then somewhere you need this
     $organization->approvedUsers()->latest()->get();
    

    Now you’re scratching your head and trying to figure out why you don’t get latest users. After a while you notice that they are ordered by email, so you’re tracking it down – relationship is found, so the question arises – can I change the relationship? Not really, it would break some functionality, probably untested, so it is not the way to go. Eventually it seems that the only way is a NEW relationhsip. Not good at all.

    You could make it better, if you really need default ordering, by creating custom and explicit relation in the first place – just like here

  5. props: GOOD. There’s one exception – I recommend not using $appends EVER as this one very easily leads to huge problems if abused. Imagine using accessor that does some heavy logic (which it should not), like querying relation and returning its value:

     public function getOrganizationNameAttribute()
     {
      return $this->organization->name;
     }
    

    this is evil as it is, trust me. Now, imagine you have an API endpoint that serves your model directly, and some time later your colleague needs to add organization_name to the JSON result. Easy does it, right? Just added to $appends and it is automagically in the JSON!

    Only now you realize that your endpoint serves data 20 times slower now and you have no idea why. Here it is – magic has its cost. You endpoint used, say, 5 queries before, but now it uses 5 + N queries, because accessor runs the query under the hood. Finding it out is not trivial and even noticing it might take long, which can hurt the business. Don’t go this path, be like new Leo and save yourself some headache 😉

  6. findMany: GOOD. I’d only add note about different return values find(int|string 1) : ?Model while find(array|Arrayable $ids) : Collection. Which means the first returns Model or null, but the latter returns Collection always (even if empty).

  7. dynamic wheres: NEVER do this. It is illogical, confusing, impossible to understand for anyone without decent knowledge about Eloquent.
    Next thing is this: if you ever need to restructure your DB or just investigate usage and find all queries against a column, you’d find easily where('some_column'), orderBy('some_column') etc by simply grepping against 'some_column', but you won’t find whereSomeColumn($value).
    Another catch is this: you know already that Eloquent provides those magic calls (dynamic wheres), so when you see whereDate you can think it is the same. But wait, there’s no date column in the table, so WTF? Oh, sure, this is actually real method whereDate($column, $value). You get the idea, trust me, you neighbor and their dog will hate you if you use it even once.

  8. order by relation: Custom relation definitely GOOD, but ordering nope. While it is valid, it’s dangerous as well. You should NEVER depend on the collection ordering if you don’t know how many items collection has (and it must be rather small collection). Imagine this code when your page grows from 20 Topics to 20 thousands Topics – then every single pageload is looong seconds, sometimes memory exhausted errors occur etc. Databases are pretty good with ordering, use them.

  9. when: I totally agree with the comment: It may not feel shorter or more elegant – True, it is not shorter, nor more elegant, nor better in any way for me. Here’s what I recommend:

     if ($request->has('role')) { // I know the intent already
      $query->where('role_d', $request->get('role')); // then following action
     }
    
     // vs
    
     // Reading this I am not sure what false means, no idea what $role will be
     // in the closure, I have to spend a few seconds analyzing it every time.
     // It adds up in big codebase and I love to avoid unnecessary overload
     $query->when(request('role', false), function ($q, $role) {
      return $q->where('role_d', $role);
     });
    

    One may argue that you HAVE TO avoid if statements, but here’s the thing – removing them doesn’t make you functional programmer, using function () {} rather than if doesn’t make you functional programmer, it doesn’t add any value to the business either.

  10. withDefault: OKAY in general. I prefer explicit code, so don’t use it myself, but it’s matter of preference. However using it strictly for presentation purpose as in the example – no like.

  11. accessor ordering: Smart – as in No. 8 be careful in cases, where you cannot be sure bout collection size.

  12. global scope: OKAY. Personally I wouldn’t do that in a global scope, as global scopes are good for only so many cases, and only to apply some where constraints (SoftDeletes is a good example, Active might be applicable in many cases etc).

  13. raw: GOOD

  14. replicate: GOOD

  15. chunk: VERY GOOD. I would add that you NEVER want to call Model::all() unless you have a dictionary-like model, say, a list of countries. A model that you can be sure will never grow (there won’t be more than 300 countries in our lifetime I guess).

  16. generators: GOOD

  17. updated_at: GOOD

  18. update return value: GOOD

  19. orWhere: This one is actually incorrect, both in the code and logically. I believe it comes from the bad example in the Laravel’s own docs. I corrected the example and added some notes to the docs recently, so feel free to look it up now docs

  20. orWhere again: Same as No. 19.

]]>
A story about Laravel resources2018-03-11T04:00:00+00:002018-03-11T04:00:00+00:00https://softonsofa.com/a-story-about-laravel-resourcesA story about laravel resources

Leo is a brilliant developer. He turns every idea into code in no time. He’s prepared for any task thrown at him and dives into coding straight away, delivering successfully and rapidly.

These days he’s been working on a public API for the application he and his team is building. A few resources with a few endpoints were not a big challenge for Leo obviously. He caught some models in the wild and turned them into beatiful REST API data source. Done deal.

A few days passed and Whoops! Something went wrong unfortunately.

But worry not, for Leo is well-prepared – he wrote tests for the API, so whatever went wrong, was caught during proper build process before going live, so all that needs to be done is tracking down the culprit and fixing the issue.

Leo found out that JSON from GET /users/{uuid} endpoint returned posts related collection, even though it wasn’t requested by the consumer (test in this case). After scratching his head for 15 minutes, playing with the endpoint manually, checking the transforming method, he was a bit lost. Finally Leo found out that his colleague, Francis, added a key to the $appends array on the User model, which in turn called the accessor behind the scenes, and the latter did some calculations on related posts collection. This ended up in loading the relation implicitly.

Now, this is a puzzle… Leo had to discuss it with Francis, and they realized, that using $appends is a very bad idea, but it is too hard now to change it, because features planned for the release are necessary for the business. They agreed, that Leo is gonna make a quick fix in his transformer class and instead of deceptive whenLoaded he will simply rely on old-skool, a bit rusty nowadays, yet effective if statement.

Screw it, he thought to himself, I’m never gonna show this sh.t on twitter, so let it be…

Issue fixed, case closed!


Things were going great for Leo until one Friday afternoon, when sales team manager Jonathan, approached him.

Leo wasn’t sure whether it was a threatening expression on Jonathan’s face, or it was just a Friday prank. He’s serious, too serious, Leo whispered to his neighbour, Alice, and he was damn right…

The API that Leo created has been working for a few weeks now. It was fine and healthy, there were some inconsistencies in response times without any clear reason, but nobody complained.

Today, however, somebody did…

Jonathan was furious about some sensitive users’ data leaking through the API. He was so angry while shouting he spit on Leo’s shiny MBP, and it wasn’t cool. Not cool at all if you ask me!

It took a while before Leo actually learned from Jonathan what the problem was. Apparently, Jonathan had a friendly meeting with one of the customers, who also was API consumer, and after a drink or two, the customer said something between the lines of

I’m really glad you guys share all that info there, in your API, but you may want to actually hide it. If I didn’t know you from college I could simply steal your customers, you know… I bet Donnie from XYZ is already doing that mate hahahaha!

WAT?!

Jonathan thought it was a joke first, but after asking his college friend a few questions, he was horrified and dashed back to the office.

Leo started to panic. He knew, it could end up badly, so he reassured Jonathan that there was no issue in the API and it must have been a glitch that made the guy receive some excess data. He also promised to stay in the office as long as necessary to check everything in and out and fix if there was anything wrong.

He knew, his girlfriend was preparing for a Friday night out already, so he acted quickly. After browsing through all the codes related to /users resource in his API, Leo couldn’t find the problem, so he asked Alice for help. They were always there for each other, so she agreed to stay after-hours and help. Alice wasn’t too familiar with the structure of Resources, so after reading the code, she resorted to checking the docs on the framework’s website.

Spot on! During browsing the docs page, she noticed something familiar: when($this->isAdmin(), ...) – they had the same construct in their code. However, both she and Leo missed the important bit in the beginning:

image

That moment Leo realized he was screwed…

It isn’t about the authenticated user, it is the requested data where that method is being called, he muttered, then yelled FFS!!

The story went on, Leo fixed everything in the API, he asked his old-skool colleague (Dinosaur they called him) Richard for advice this time. He learned it the hard way, but he did, and these days he makes his code predictable.

Now he knows and now he spreads the word out to all the New Kids On The Block:

Save yourself that Friday Night with your bae!

]]>
How to add custom casters in Laravel Tinker2018-03-04T04:00:00+00:002018-03-04T04:00:00+00:00https://softonsofa.com/how-to-add-custom-casters-in-laravel-tinkerI made a series about psysh and Laravel’s tinker – powerful REPL where you can play with your whole application.

ICYMI check it out here: tinker like a boss and here’s example of what we can achieve:

=> Carbon\Carbon @1519569240 {#745
 +date: "2018-02-25 14:34:00.000000",
 +weekday: "Sunday",
 +day_of_year: 55,
 +unix: 1519569240,
 +diff_for_humans: "6 days ago",
 }

There is, however, missing piece of the puzzle in the series. It describes how you can custimze presenters in REPL (called casters in PsySH), but I describe generic, global casters that reside in PsySH config. This is very handy, but you cannot distribute them among your team members (or anyone else for that matter) in the repo.

Today we are going to add that piece to the puzzle and see how to customize and override psysh casters in Laravel app.

We need just a few simple steps:

  1. Override tinker command
  2. Override service provider
  3. Create our casters and define them in the application config

Override original ServiceProvider and Command

First let’s create our customized Command, which will allows us to provide array with customized casters:

namespace App\Console;

class TinkerCommand extends \Laravel\Tinker\Console\TinkerCommand
{
    protected array $casters = [];

    /**
     * Set custom casters for this tinker session.
     *
     * @param array $casters
     */
    public function setCasters(array $casters)
    {
        $this->casters = $casters;

        return $this;
    }

    /**
     * Get an array of custom and Laravel's own casters.
     *
     * @return array
     */
    protected function getCasters()
    {
        return array_merge(parent::getCasters(), $this->casters);
    }
}

Next step is to use this command instead of the original – we can achieve that by replacing the service provider:

// in config/app.php replace the line:
Laravel\Tinker\TinkerServiceProvider::class,

// with:
App\Providers\TinkerServiceProvider::class,

// and create the your own provider:
class TinkerServiceProvider extends \Laravel\Tinker\TinkerServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(
            'command.tinker',
            fn () => (new \App\Console\TinkerCommand)->setCasters(
                config('tinker.casters', [])
            )
        );

        $this->commands(['command.tinker']);
    }
}

Create and register casters

Finally, let’s create example caster and set it up in the config/tinker.php configuration file:

// config/tinker.php
return [
    'casters' => [
        // Either inline function:
        Carbon\Carbon::class => fn (Carbon\Carbon $carbon) => [
            'date' => $carbon->date,
            'weekday' => $carbon->format('l'),
            'day_of_year' => (int) $carbon->format('z'),
            'unix' => (int) $carbon->format('U'),
            'diff_for_humans' => $carbon->diffForHumans(),
        ],
        // OR class-based static method:
        Carbon\Carbon::class => 'App\Console\Casters::carbon',
    ],
];

// app/Console/Casters.php
class Casters
{
    public static function carbon(\Carbon\Carbon $carbon)
    {
        return [
            'date' => $carbon->date,
            'weekday' => $carbon->format('l'),
            'day_of_year' => (int) $carbon->format('z'),
            'unix' => (int) $carbon->format('U'),
            'diff_for_humans' => $carbon->diffForHumans(),
        ];
    }
}

And we are up & running!

Let’s see that in action before we dive into the code:

=> Carbon\Carbon @1519569240 {#745
 +date: "2018-02-25 14:34:00.000000",
 +weekday: "Sunday",
 +day_of_year: 55,
 +unix: 1519569240,
 +diff_for_humans: "6 days ago",
}

PS. Soon you will be able to customize casters without hassle, as I’ve already pushed a PR, which provides this functionality out-of-the-box:

https://github.com/laravel/tinker/pull/39

]]>
Too much magic will kill you or at least bite your ass2017-07-13T04:00:00+00:002017-07-13T04:00:00+00:00https://softonsofa.com/too-much-magic-will-kill-you-or-at-least-bite-your-assTinker like a boss helpers2017-07-09T04:00:00+00:002017-07-09T04:00:00+00:00https://softonsofa.com/tinker-like-a-boss-helpersTinker like a boss in psysh2016-09-26T04:00:00+00:002016-09-26T04:00:00+00:00https://softonsofa.com/tinker-like-a-boss-in-psyshThey can bite how not to use accessors in eloquent2016-09-20T04:00:00+00:002016-09-20T04:00:00+00:00https://softonsofa.com/they-can-bite-how-not-to-use-accessors-in-eloquentQuerying relations in laravel get models where latest related is2015-12-02T04:00:00+00:002015-12-02T04:00:00+00:00https://softonsofa.com/querying-relations-in-laravel-get-models-where-latest-related-is