Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Allow HTTP client requests with retries to optionally throw RequestExceptions #40076

Closed
wants to merge 20 commits into from

Conversation

mattkingshott
Copy link
Contributor

This PR addresses #40055 by adding functionality to the HTTP client so that you can prevent it from throwing a RequestException when retries are configured.

The problem

Presently, and as highlighted in the docs, the HTTP client does not throw exceptions when a request fails. As a result, you can make use of methods like failed on the response e.g.

$response = Http::get('http://example.com');

if ($response->failed())
{
    // Handle the error
}

However, if you configure retries on the HTTP client, it will throw a RequestException if all attempts fail.

$response = Http::retries(3, 1000)->get('http://example.com');

This means:

  1. You cannot configure throw() on the client.
  2. You cannot chain methods e.g. throw()->json().
  3. You cannot use throwIf.
  4. You cannot use the response's error handling methods e.g. $response->failed().

In addition, you have to wrap the request within a try/catch block if you want to handle the error.

The solution

The PR adds a fourth parameter throw to the retries method, which allows you to control whether the HTTP client should throw an exception when the retry limit is reached.

By default, this parameter is set to true to preserve backward compatibility.

Http::retries(3, 1000); // throws a RequestException if all retries fail
Http::retries(3, 1000, null, true); // throws a RequestException if all retries fail
Http::retries(3, 1000, null, false); // does not throw a RequestException if all retries fail

TBlindaruk and others added 20 commits December 14, 2021 22:46
…nce key from cache in Redis driver (#40039)

* [8.x] Fix possible out of memory error when deleting values by reference key from cache in Redis driver

* formatting

Co-authored-by: Denis Kuzmenchuk <[email protected]>
Co-authored-by: Taylor Otwell <[email protected]>
* Add setApplication method to FilesystemManager

* Update FilesystemManager.php

* Update src/Illuminate/Filesystem/FilesystemManager.php

Co-authored-by: Noël Hagestein <[email protected]>

* Update FilesystemManager.php

Co-authored-by: Taylor Otwell <[email protected]>
Co-authored-by: Noël Hagestein <[email protected]>
### Summary

This pull request adds a new way to define attribute "accessors / mutators" as they are currently called in the 
documentation: https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators

Currently, accessors and mutators are added to a model by defining `get{Foo}Attribute` and `set{Foo}Attribute` methods on the model. These conventionally named methods are then used when the developers attempts to access the `$model->foo` property on the model.

This aspect of the framework has always felt a bit "dated" to me. To be honest, I think it's one of the least elegant parts of the framework that currently exists. First, it requires two methods. Second, the framework does not **typically** prefix methods that retrieve or set data on an object with `get` and `set` - it just hasn't been part of Laravel's style (e.g., `$request->input()` vs. `$request->getInput()`, `$request->ip()` vs. `$request->getIp`, etc.

This pull request adds a way to define attribute access / mutation behavior in a single method marked by the `Illuminate\Database\Eloquent\Casts\Attribute` return type. In combination with PHP 8+ "named parameters", this allows developers to define accessor and mutation behavior in a single method with fluent, modern syntax by returning an `Illuminate\Database\Eloquent\Casts\Attribute` instance:

```php
/**
 * Get the user's title.
 */
protected function title(): Attribute
{
    return new Attribute(
        get: fn ($value) => strtoupper($value),
        set: fn ($value) => strtolower($value),
    );
}
```

### Accessors / Mutators & Casts

As some might have noticed by reading the documentation already, Eloquent attribute "casts" serve a very similar purpose to attribute accessors and mutators. In fact, they essentially serve the same purpose; however, they have two primary benefits over attribute accessors and mutators.

First, they are reusable across different attributes and across different models. A developer can assign the same cast to multiple attributes on the same model and even multiple attributes on different models. An attribute accessor / mutator is inherently tied to a single model and attribute.

Secondly, as noted in the documentation, cast classes allow developers to hydrate a value object that aggregates multiple properties on the model (e.g. `Address` composed of `address_line_one`, `address_line_two`, etc.), immediately set a property on that value object, and then `save` the model like so:

```php
use App\Models\User;

$user = User::find(1);

$user->address->lineOne = 'Updated Address Value';

$user->save();
```

The current, multi-method implementation of accessor / mutators currently **does not allow this** and it can not be added to that implementation minor breaking changes.

However, this improved implementation of attribute accessing **does support proper value object persistence** in the same way as custom casts - by maintaining a local cache of value object instances:

```php
/**
 * Get the user's address.
 */
protected function address(): Attribute
{
    return new Attribute(
        get: fn ($value, $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
            $attributes['city'],
        ),
        set: fn ($value) => [
            'address_line_one' => $value->addressLineOne,
            'address_line_two' => $value->addressLineTwo,
            'city' => $value->city,
        ],
    );
}
```

### Mutation Comparison

In addition, as you may have noticed, this implementation of accessors / mutators **does not** require you to manually set the properties in the `$this->attributes` array like a traditional mutator method requires you to. You can simply return the transform value or array of key / value pairs that should be set on the model:

"Old", two-method approach:

```php
public function setTitleAttribute($value)
{
    $this->attributes['title'] = strtolower($value);
}
```

New approach:

```php
protected function title(): Attribute
{
    return new Attribute(
        set: fn ($value) => strtolower($value),
    );
}
```

### FAQs

**What if I already have a method that has the same name as an attribute?**

Your application will not be broken because the method does not have the `Attribute` return type, which did not exist before this pull request.

**Will the old, multi-method approach of defining accessors / mutators go away?**

No. It will just be replaced in the documentation with this new approach.
@GrahamCampbell GrahamCampbell changed the title Allow HTTP client requests with retries to optionally throw RequestExceptions [8.x] Allow HTTP client requests with retries to optionally throw RequestExceptions Dec 16, 2021
* @return $this
*/
public function retry(int $times, int $sleep = 0, ?callable $when = null)
public function retry(int $times, int $sleep = 0, ?callable $when = null, bool $throw = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't change the method signature on a stable release unfortunately.

Copy link
Contributor Author

@mattkingshott mattkingshott Dec 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I submit this to master then? So for v9

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if we can't solve it differently then that would be best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I’ll submit it for master.

@mattkingshott mattkingshott changed the base branch from 8.x to master December 16, 2021 19:31
@mattkingshott mattkingshott changed the title [8.x] Allow HTTP client requests with retries to optionally throw RequestExceptions [9.x] Allow HTTP client requests with retries to optionally throw RequestExceptions Dec 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants