Skip to content

Conversation

@Nyholm
Copy link
Member

@Nyholm Nyholm commented Feb 19, 2017

This is an implementation of a query object suggested in #574. Ping @unkind

Changes:

  • All providers are stateless (removed LocaleTrait)
  • Geocoder extends Provider
  • Removed LocaleAwareProvider
  • Added LocaleAwareGeocoder (no methods)
  • Added StatefulGeocoder (this will make migrating easy)
  • Added GeocodeQuery, ReverseQuery
  • Added Provider::geocodeQuery,Provider::reverseQuery

Only GoogleMaps and Bing are using the new Provider interface.

Why we need this

The Query objects keeps providers stateless. The query object is easily extendable with extra data like bounds, locale, limit and also arbitrary data. Allowing arbitrary data will solve #546.

Using an query object over setters on the providers keeps the public interface of the providers more clean.

This PR will also solve #501.

TODO:

  • Make sure all providers are using the new Query objects
  • Update the tests
  • Update ProviderAggregator
  • Update change log

@willdurand willdurand requested a review from Baachi February 19, 2017 16:26
@Nyholm Nyholm added this to the 4.0.0 milestone Feb 19, 2017
*/
public function withBounds(Bounds $bounds)
{
$this->bounds = $bounds;
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest to make query object immutable, i.e.

$query = clone $this;
$query->bounds = $bounds;

GeocodeQuery is a value object.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was thinking about making them immutable. I will make the query objects easier to reused and you will not get any unexpected behavior. Though, they are "builder". I compare the GeocodeQuery to Doctrines QueryBuilder. It would not make much sense to have the QueryBuilder immutable, would it? Or maybe Im missing something.

Copy link
Contributor

@unkind unkind Feb 19, 2017

Choose a reason for hiding this comment

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

It would not make much sense to have the QueryBuilder immutable, would it?

I'm not that familiar with Doctrine's QB, but as I can see:

  • query builder is not perfect match for GeocoderQuery, query builder is not a query, it is a service; query, on other hand, is a message, queries should be compared by their values -> equality is not based on identity;
  • query builder probably was designed in the mutable way because of performance reasons, because cloning that object is more expensive than small object like GeocoderQuery; and number of method calls is much more higher than in your case: for complex query you may trigger clone 20-30 times very easy.

However, even for SQL query builder it probably makes sense to keep it immutable too. Sometimes you need to execute very similar queries, but with little difference (e.g. simple query + the same with COUNT(*)). AFAIU, Doctrine suggests to clone QB explicitly when you need it, but it looks like a trade-off.

I'd rather compare your query with something like Request from PSR-7 if you are in doubt. Request and Query are very similar concepts. Whenever your modify your request/query/command/event, you create different message.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okey, Thanks =)

Thanks for the review.

/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface Query
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if this interface is necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are correct.

* @return \Geocoder\Collection
*/
private function executeQuery($query)
private function executeQuery(Query $query, $url)
Copy link
Contributor

@unkind unkind Feb 19, 2017

Choose a reason for hiding this comment

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

I'd pass locale and limit variables or even copy-paste URL generation for both queries.


public function __clone()
{
$this->bounds = clone $this->bounds;
Copy link
Contributor

@unkind unkind Feb 20, 2017

Choose a reason for hiding this comment

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

Bounds is an immutable structure: sharing same instance of Bounds is safe.

@Nyholm
Copy link
Member Author

Nyholm commented Feb 20, 2017

Thank you @unkind. I've update the PR.

@Nyholm Nyholm mentioned this pull request Apr 3, 2017
@Nyholm Nyholm changed the title [WIP] Implement a Query object Implement a Query object Apr 9, 2017
@Nyholm
Copy link
Member Author

Nyholm commented Apr 9, 2017

Today Ive done nothing to this PR but adding tests and making sure all providers implements the new interfaces.

{
return $this->reverseQuery(ReverseQuery::fromCoordinates($latitude, $longitude));
}
} No newline at end of file

Choose a reason for hiding this comment

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

Missing newline

<?php

/**
* This file is part of the Geocoder package.

Choose a reason for hiding this comment

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

Is this header mandatory? If so, it's missing from a few places.

private $data;

/**
* @param Coordinates $coordinates

Choose a reason for hiding this comment

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

This does not seem to be correct

}

/**
* @param $text

Choose a reason for hiding this comment

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

Type?

{
return new self($coordinates);
}
/**

Choose a reason for hiding this comment

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

Missing newline

/**
* @param int $limit
*
* @return $this

Choose a reason for hiding this comment

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

Should be the class name

*/
const ENDPOINT_URL = 'https://freegeoip.net/json/%s';

Choose a reason for hiding this comment

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

Indent

*
* @return GoogleMaps
*/
public function setRegion($region)

Choose a reason for hiding this comment

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

Is the set prefix preserved for BC reason?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it is a typo

* @param string $locale A locale (optional).
*/
public function __construct(HttpClient $client, $apiKey, $locale = null)
public function __construct(HttpClient $client, $apiKey)

Choose a reason for hiding this comment

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

IIRC this was $httpClient a few thousand lines above.

Copy link
Member Author

Choose a reason for hiding this comment

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

I do not understand. We always refer to the HttpClient as $client

/**
* @var Bounds
*/
private $bounds;

Choose a reason for hiding this comment

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

Currently this cannot be set at all

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a bunch of setter.

@sagikazarmark
Copy link

I like the idea of immutability in Providers.

As for the queries: in case of the reverse query it seems to be a bit of overkill: basically coordinates are packed into an object, which gets unpacked in the provider as is. If it's a matter of consistency and/or probable future extension then it's probably fine.

Also, it feels weird that the limit can be passed in the query as well. One could say limit is a Provider configuration. I don't have too much context, so I might be wrong about it.

@Nyholm
Copy link
Member Author

Nyholm commented Apr 15, 2017

Thank you for the review. Really appreciated.

As for the queries: in case of the reverse query it seems to be a bit of overkill: basically coordinates are packed into an object, which gets unpacked in the provider as is. If it's a matter of consistency and/or probable future extension then it's probably fine.

Yes, it is a matter of consistency and about being able to pass arbitrary data to the provider.

Also, it feels weird that the limit can be passed in the query as well. One could say limit is a Provider configuration.

Hm, I've never considered that.. I compare it to Doctrine. The Limit it part of the MySQL query and not config.

@Nyholm Nyholm merged commit 24fc43a into master Apr 28, 2017
@Nyholm Nyholm deleted the issue-574 branch April 28, 2017 15:01
@willdurand
Copy link
Member

Good job!

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.

5 participants