Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/Provider/Yandex/Model/YandexAddress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/

namespace Geocoder\Provider\Yandex\Model;

use Geocoder\Model\Address;

/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class YandexAddress extends Address

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

final?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes. I'll make sure to send a PR. Thanks

{
/**
* @var string|null
*/
private $precision;

/**
* The name of this location.
*
* @var string|null
*/
private $name;

/**
* @return null|string
*/
public function getPrecision()
{
return $this->precision;
}

/**
* @param null|string $precision
*
* @return YandexAddress
*/
public function setPrecision($precision)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

VO with setter?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes. I agree with you. But I'm not sure how to make it work without a setter. Look at the LocationBuilder. Do you have any suggestions?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see much sense in LocationBuilder, I'd write something like

$adminLevels = ...;

$locations[] = new YandexLocation(
    isset($flatArray['name']) ? $flatArray['name'] : null,
    isset($flatArray['precision']) ? $flatArray['precision'] : null,
    $adminLevels,
    ...
);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The LocationBuilder is meant to remove that large array all providers were building.

Your suggestion means that the (already big and ugly) constructor of Address has to be overridden. Is that somehow avoidable? Could we make the constructor prettier?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your suggestion means that the (already big and ugly) constructor of Address has to be overridden. Is that somehow avoidable? Could we make the constructor prettier?

OK, it is "big and ugly". What's the problem with it? If Address requires a lot of params, it is natural that constructor is "big and ugly". Setter doesn't make code "prettier", it just makes object less explicit, it hides the problem (if it does exist).

If you ask me, many arguments for data object is OK, it is not a service.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Okey, Thank you. But how to call the constructor to subclasses of Address with the LocationBuilder? (which should be renamed to AddressBuilder)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wikipedia states that the builder pattern solves the the telescoping constructor anti-pattern problem. C#, for example, in the past had no default values for properties and default values for arguments, so there were 2 ways to make default values: constructor overloading and the builder pattern. Regarding to your case with Address I don't see why you need the builder.

Technically, it just replaces

$a = ...;
$b = ...;
$c = ... ? ... : null;

$address = new Address($a, $b, $c);

with

$builder->setA(...);
$builder->setB(...);
$builder->setC(... ? ... : null);

$address = $builder->build();

What problem does the LocationBuilder solve?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The problem is that all providers is not setting all data. Some data might me permanently ignored by some providers. (MaxMindBinary is a perfect example). The LocationBuilder brings an easy way (with setter/typehints) to add some/all data. The logic (order of constructor parameters) is hidden away. Before the LocationBuilder was introduces we used an assoc array.

Example:

$builder->setE($httpResponse['e']);
$address = $builder->build();

Compared with:

$e = $httpResponse['e'];
$address = new Address(null, null, null, null, $e);

But how to call the constructor to subclasses of Address with the LocationBuilder?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Compared with:

Why not? Provider makes translation from external API to your generic Address class, it is OK to have some dirty code here, isn't it? Offtopic: https://wiki.php.net/rfc/named_params

If you can't avoid some setter-like method, I suggest to use with-er syntax:

$address = $address->withFoo('bar'); // with* clones the object and return changed one

At least, it makes object immutable.

{
$this->precision = $precision;

return $this;
}

/**
* @return null|string
*/
public function getName()
{
return $this->name;
}

/**
* @param null|string $name
*
* @return YandexAddress
*/
public function setName($name)
{
$this->name = $name;

return $this;
}
}
9 changes: 9 additions & 0 deletions src/Provider/Yandex/Tests/YandexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,13 @@ public function testReverseWithRealCoordinatesWithTRLocaleAndLocalityToponym()
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
}

public function testReverseMetroStationToGetName()
{
$provider = new Yandex($this->getAdapter(), 'metro');
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(60.036843, 30.324285));

$first = $results->first();
$this->assertEquals('метро Озерки', $first->getName());
}
}
66 changes: 34 additions & 32 deletions src/Provider/Yandex/Yandex.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

use Geocoder\Exception\UnsupportedOperation;
use Geocoder\Model\AddressCollection;
use Geocoder\Model\LocationBuilder;
use Geocoder\Provider\Yandex\Model\YandexAddress;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
use Geocoder\Provider\AbstractHttpProvider;
Expand Down Expand Up @@ -116,57 +118,57 @@ private function executeQuery($query, $locale, $limit)

$data = $json['response']['GeoObjectCollection']['featureMember'];

$results = [];
$locations = [];
foreach ($data as $item) {
$builder = new LocationBuilder();
$bounds = null;
$details = ['pos' => ' '];
$flatArray = ['pos' => ' '];

array_walk_recursive(
$item['GeoObject'],

/**
* @param string $value
*/
function ($value, $key) use (&$details) {
$details[$key] = $value;
function ($value, $key) use (&$flatArray) {
$flatArray[$key] = $value;
}
);

if (!empty($details['lowerCorner'])) {
$coordinates = explode(' ', $details['lowerCorner']);
$bounds['south'] = (float) $coordinates[1];
$bounds['west'] = (float) $coordinates[0];
if (!empty($flatArray['lowerCorner']) && !empty($flatArray['upperCorner'])) {
$lowerCorner = explode(' ', $flatArray['lowerCorner']);
$upperCorner = explode(' ', $flatArray['upperCorner']);
$builder->setBounds(
(float) $lowerCorner[1],
(float) $lowerCorner[0],
(float) $upperCorner[1],
(float) $upperCorner[0]
);
}

if (!empty($details['upperCorner'])) {
$coordinates = explode(' ', $details['upperCorner']);
$bounds['north'] = (float) $coordinates[1];
$bounds['east'] = (float) $coordinates[0];
}

$coordinates = explode(' ', $details['pos']);
$coordinates = explode(' ', $flatArray['pos']);
$builder->setCoordinates((float) $coordinates[1], (float) $coordinates[0]);

$adminLevels = [];
foreach (['AdministrativeAreaName', 'SubAdministrativeAreaName'] as $i => $detail) {
if (isset($details[$detail])) {
$adminLevels[] = ['name' => $details[$detail], 'level' => $i + 1];
foreach (['AdministrativeAreaName', 'SubAdministrativeAreaName'] as $i => $name) {
if (isset($flatArray[$name])) {
$builder->addAdminLevel($i + 1, $flatArray[$name], null);
}
}

$results[] = array_merge($this->getDefaults(), [
'latitude' => (float) $coordinates[1],
'longitude' => (float) $coordinates[0],
'bounds' => $bounds,
'streetNumber' => isset($details['PremiseNumber']) ? $details['PremiseNumber'] : null,
'streetName' => isset($details['ThoroughfareName']) ? $details['ThoroughfareName'] : null,
'subLocality' => isset($details['DependentLocalityName']) ? $details['DependentLocalityName'] : null,
'locality' => isset($details['LocalityName']) ? $details['LocalityName'] : null,
'adminLevels' => $adminLevels,
'country' => isset($details['CountryName']) ? $details['CountryName'] : null,
'countryCode' => isset($details['CountryNameCode']) ? $details['CountryNameCode'] : null,
]);
$builder->setStreetNumber(isset($flatArray['PremiseNumber']) ? $flatArray['PremiseNumber'] : null);

@willdurand willdurand May 22, 2017 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if we go for PHP >= 7, we might want to use ??

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree. But should PHP >= 7 be a requirement for 4.0?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See #634

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think so. It is time :p

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Wohoo!

$builder->setStreetName(isset($flatArray['ThoroughfareName']) ? $flatArray['ThoroughfareName'] : null);
$builder->setSubLocality(isset($flatArray['DependentLocalityName']) ? $flatArray['DependentLocalityName'] : null);
$builder->setLocality(isset($flatArray['LocalityName']) ? $flatArray['LocalityName'] : null);
$builder->setCountry(isset($flatArray['CountryName']) ? $flatArray['CountryName'] : null);
$builder->setCountryCode(isset($flatArray['CountryNameCode']) ? $flatArray['CountryNameCode'] : null);

/** @var YandexAddress $location */
$location = $builder->build(YandexAddress::class);
$location->setPrecision(isset($flatArray['precision']) ? $flatArray['precision'] : null);
$location->setName(isset($flatArray['name']) ? $flatArray['name'] : null);
$locations[] = $location;
}

return $this->returnResults($results);
return new AddressCollection($locations);
}
}