willdurand / Negotiation

Content Negotiation tools for PHP.
https://williamdurand.fr/Negotiation/
MIT License
1.41k stars 62 forks source link

Quality-of-source factor #77

Closed SteveTalbot closed 8 years ago

SteveTalbot commented 8 years ago

Hi,

I'd like to propose a simple way to specify relative priorities of media types, languages, encodings or character sets on the server side, without introducing a backward-compatibility break.

Current behaviour

The programmer specifies a list of available media types, in order of preference, when calling the getBest() function.

The client can specify quality scores against each acceptable type in the Accept header. The default score if not specified is 1.0.

The negotiator finds matches as per RFC 7231, and sets the quality of a match equal to the quality score from the Accept header.

If there is more than one match, the best match is the one with the highest quality score.

In the event of multiple matches with the same quality score, the negotiator returns the match that occurred earliest in the programmer's list.

Proposed behaviour

I'd like to be able to specify a quality of source factor, like in Apache's content negotiation. For example:

$priorities = array('text/html; charset=UTF-8; q=1.0', 'application/pdf; q=0.9');

The negotiator would still find matches as per RFC 7231, but would set the quality of the match equal to the quality score from the Accept header multiplied by the quality-of-source factor specified by the programmer.

The best match is the one with the highest quality.

In the event of multiple matches with the same quality, the negotiator returns the match that occurred earliest in the programmer's list. So if the programmer has not specified any quality-of-source factors, the behaviour would be exactly the same as it is now.

Implementation

Because the priorities are already parsed the same way as the headers, it's a one-line change to implement this. Simply change line 64 of AbstractNegotiator from:

return new Match($header->getQuality(), $score, $index);

to:

return new Match($header->getQuality() * $priority->getQuality(), $score, $index);

Making this change doesn't break any of the existing unit tests. I'm happy to submit a pull request.

Thanks, Steve

neural-wetware commented 8 years ago

It's a simple enough change and would be backwards compatible. Can you give an example of how this is useful? Otherwise, it's just bloat.

SteveTalbot commented 8 years ago

The Apache documentation uses images as a simple example for content type negotiation. If the image has transparent areas, a format that can represent alpha-channel transparency (e.g. PNG) would be of higher quality than one with simple transparency (e.g. GIF), which in turn would be better than the many formats that can't represent transparency at all. So, even if the client has a stronger preference for GIF than PNG, it might be better to send the PNG.

For language negotiation, imagine we have an official document that was originally written in English, officially translated and approved by the government in French, and unofficially translated to German. The quality of source of the English variant is the highest, followed by the French, then the German. If the client sends a higher quality factor for German than for French, it may still be more appropriate to send the French version, because it is an official version.

We're thinking of using negotiation for a RESTful API. We'll set the quality of source factor to reflect the richness of information that can be sent in each format. The client can send an Accept header indicating which response formats it supports and prefers; the API will respond with the best response format, taking into account the quality of source and the client's preference.

neural-wetware commented 8 years ago

I think this looks good. It's up to @willdurand to merge.