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

final attribute on the CURL class breaks caching plugin functionality. #802

Open
gnif opened this issue Apr 5, 2023 · 6 comments
Open

Comments

@gnif
Copy link

gnif commented Apr 5, 2023

As a hosting provider that provides tailored WordPress hosting for numerous clients we are often tasked with working around poorly written plugins and/or code that does not cache fetched results from external sources. As such we have developed a plugin that extends the Curl transport with a caching mechanism based on a whitelist of URLs.

As of the update to the 2.0.0 requests library, it is now impossible to do this anymore due to the use of the final keyword on the Curl class.

Here is how we were doing this:

  // register our transport wrapper
  Requests::add_transport('\HF\Cache\cURL');

  // force our transport to load first
  Requests::$transport[serialize(['ssl' => true ])] = '\HF\Cache\cURL';
  Requests::$transport[serialize(['ssl' => false])] = '\HF\Cache\cURL';
  Requests::$transport[serialize([]              )] = '\HF\Cache\cURL';

Our class would then override the request method and either inherit the default functionality or respond with a cached value:

class cURL extends \Requests_Transport_cURL
{
  public function request($url, $headers = array(), $data = array(), $options = array())
  {
    // custom caching logic here
    
    // inherit default logic
    return parent::request($url, $headers, $data, $options);
  }
}

Instead we now get the fatal error:

Class HF\Cache\cURL may not inherit from final class (WpOrg\Requests\Transport\Curl)

Please remove the final attribute from the Curl class.

@jrfnl
Copy link
Member

jrfnl commented Apr 5, 2023

@gnif Thanks for reporting this. To be honest, I don't see anything in the above issue which justifies the need to extend the Requests class.

Is there any particular reason why you are not using a code pattern along the lines of the below ?

class cURL_With_Caching
{
  private $curl;
  public function __construct() {
    $this->curl = new WpOrg\Requests\Transport\Curl();
  }
  public function request($url, $headers = array(), $data = array(), $options = array())
  {
    // custom caching logic here
    
    // use default logic
    return $this->curl->request($url, $headers, $data, $options);
  }
}

@gnif
Copy link
Author

gnif commented Apr 5, 2023

I had not considered this however it does seem like a bit of a hack as to remain compatible I would need to implement Requests\Transport and all three methods it exposes. While these would be single lines and simple to do, the entire point of extending a class is to add extra functionality and/or inherit the original code, wrapping it as you suggest seems like a step backwards.

Edit: Note I am happy with the proposed solution, it just feels backwards to me.

@jrfnl
Copy link
Member

jrfnl commented Apr 5, 2023

@gnif An alternative might be to not add a new transport at all, but to hook into Requests via the hooking mechanism ?
Would the requests.before_request hook suit your needs ?

https://requests.ryanmccue.info/docs/hooks.html

@gnif
Copy link
Author

gnif commented Apr 8, 2023

No because the hooking mechanism doesn't allow for overriding and/or preventing the default behaviour of the request. The entire point of this plugin is to use and return a locally cached request if one exists instead of performing an external request again.

See:

$options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]);

$callback(...$parameters);

@Art4
Copy link

Art4 commented Apr 9, 2023

@gnif I'd like to recommend you this explanation, why you should implement the Transport interface instead of using extends.

https://ocramius.github.io/blog/when-to-declare-classes-final/

@gnif
Copy link
Author

gnif commented Apr 9, 2023

To be frank I feel that all proposed solutions here are not ideal (including mine). Even if I implement the Transport interface I still have to use the hack I demonstrated in the original post here to force my transport to load first.

Like I said, I am happy with this solution, but IMO it's still not great.

If there is no desire to make any changes as a result of this issue, feel free to close it. I appreciate the effort those have put in here to assist me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants