diff --git a/.gitignore b/.gitignore
index ac7011ce..c96f0893 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,10 @@
# PHPUnit
/phpunit.xml
+/phpunit.xml.dist
+/phpunit.ci.xml
/build
+
+#PHPStorm folder
+/.idea
+
diff --git a/composer.json b/composer.json
index 7071c2e7..d0ee2341 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,7 @@
"php-http/cache-plugin": "^1.4",
"php-http/guzzle6-adapter": "^1.0",
"phpunit/phpunit": "^5.4",
- "phpunit/phpunit-selenium": "dev-master",
+ "phpunit/phpunit-selenium": "^3.0",
"symfony/cache": "^3.2",
"symfony/phpunit-bridge": "^2.7|^3.0"
},
diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
index fc2291d3..3ef41e8e 100644
--- a/docker/php/Dockerfile
+++ b/docker/php/Dockerfile
@@ -2,12 +2,13 @@ FROM php:latest
# APT packages
RUN apt-get update && apt-get install -y \
- zlib1g-dev \
+ zlib1g-dev libzip-dev zip\
git \
&& rm -rf /var/lib/apt/lists/*
# PHP extensions
-RUN docker-php-ext-install zip
+RUN docker-php-ext-configure zip --with-libzip \
+ && docker-php-ext-install zip
# XDebug extensions
RUN pecl install xdebug \
diff --git a/src/Service/Place/Base/Place.php b/src/Service/Place/Base/Place.php
index 60167ac2..a6c7f03a 100644
--- a/src/Service/Place/Base/Place.php
+++ b/src/Service/Place/Base/Place.php
@@ -119,6 +119,11 @@ class Place
*/
private $reviews = [];
+ /**
+ * @var int|null
+ */
+ private $userRatingsTotal;
+
/**
* @var string[]
*/
@@ -393,6 +398,30 @@ public function setRating($rating)
$this->rating = $rating;
}
+ /**
+ * @return bool
+ */
+ public function hasUserRatingsTotal()
+ {
+ return $this->userRatingsTotal !== null;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getUserRatingsTotal()
+ {
+ return $this->userRatingsTotal;
+ }
+
+ /**
+ * @param int|null $userRatingsTotal
+ */
+ public function setUserRatingsTotal($userRatingsTotal)
+ {
+ $this->userRatingsTotal = $userRatingsTotal;
+ }
+
/**
* @return bool
*/
diff --git a/src/Service/Place/Base/Review.php b/src/Service/Place/Base/Review.php
index 3e2b0bb2..a233e3ee 100644
--- a/src/Service/Place/Base/Review.php
+++ b/src/Service/Place/Base/Review.php
@@ -26,6 +26,11 @@ class Review
*/
private $authorUrl;
+ /**
+ * @var string|null
+ */
+ private $profilePhotoUrl;
+
/**
* @var string|null
*/
@@ -41,6 +46,11 @@ class Review
*/
private $time;
+ /**
+ * @var string|null
+ */
+ private $relativeTimeDescription;
+
/**
* @var string|null
*/
@@ -99,6 +109,30 @@ public function setAuthorUrl($authorUrl)
$this->authorUrl = $authorUrl;
}
+ /**
+ * @return bool
+ */
+ public function hasProfilePhotoUrl()
+ {
+ return $this->profilePhotoUrl !== null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getProfilePhotoUrl()
+ {
+ return $this->profilePhotoUrl;
+ }
+
+ /**
+ * @param string|null $profilePhotoUrl
+ */
+ public function setProfilePhotoUrl($profilePhotoUrl)
+ {
+ $this->profilePhotoUrl = $profilePhotoUrl;
+ }
+
/**
* @return bool
*/
@@ -171,6 +205,30 @@ public function setTime(\DateTime $time = null)
$this->time = $time;
}
+ /**
+ * @return bool
+ */
+ public function hasRelativeTimeDescription()
+ {
+ return $this->relativeTimeDescription !== null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRelativeTimeDescription()
+ {
+ return $this->relativeTimeDescription;
+ }
+
+ /**
+ * @param string|null $relativeTimeDescription
+ */
+ public function setRelativeTimeDescription($relativeTimeDescription)
+ {
+ $this->relativeTimeDescription = $relativeTimeDescription;
+ }
+
/**
* @return bool
*/
diff --git a/src/Service/Place/Detail/Request/PlaceDetailRequest.php b/src/Service/Place/Detail/Request/PlaceDetailRequest.php
index 04adcafe..eb675776 100644
--- a/src/Service/Place/Detail/Request/PlaceDetailRequest.php
+++ b/src/Service/Place/Detail/Request/PlaceDetailRequest.php
@@ -17,15 +17,63 @@
class PlaceDetailRequest implements PlaceDetailRequestInterface
{
/**
+ * A textual identifier that uniquely identifies a place, returned from a Place Search.
+ *
* @var string
*/
private $placeId;
/**
+ * The language code, indicating in which language the results should be returned, if possible.
+ * Note that some fields may not be available in the requested language.
+ *
* @var string|null
*/
private $language;
+ /**
+ * The region code, specified as a ccTLD (country code top-level domain) two-character value.
+ * Most ccTLD codes are identical to ISO 3166-1 codes, with some exceptions.
+ * This parameter will only influence, not fully restrict, results.
+ * If more relevant results exist outside of the specified region, they may be included.
+ * When this parameter is used, the country name is omitted from the resulting formatted_address for results in the specified region.
+ *
+ * @var string|null
+ */
+ private $region;
+
+ /**
+ * A random string which identifies an autocomplete session for billing purposes.
+ * Use this for Place Details requests that are called following an autocomplete request in the same user session.
+ *
+ * @var string|null
+ */
+ private $sessionToken;
+
+ /**
+ * The types of place data to return.
+ * If you do not specify at least one field with a request, or if you omit the fields parameter from a request,
+ * ALL possible fields will be returned, and you will be billed accordingly.
+ *
+ * @var string[]|null
+ */
+ private $fields;
+
+ const FIELDS_BASIC = [
+ 'icon', 'name', 'url', 'photo', 'permanently_closed', 'utc_offset',
+ 'address_component', 'adr_address', 'formatted_address', 'geometry',
+ 'place_id', 'plus_code', 'type', 'vicinity'
+ ];
+
+ const FIELDS_CONTACT = [
+ 'formatted_phone_number', 'international_phone_number',
+ 'opening_hours', 'website'
+ ];
+
+ const FIELDS_ATMOSPHERE = [
+ 'price_level', 'rating', 'review', 'user_ratings_total'
+ ];
+
/**
* @param string $placeId
*/
@@ -44,10 +92,13 @@ public function getPlaceId()
/**
* @param string $placeId
+ * @return static
*/
public function setPlaceId($placeId)
{
$this->placeId = $placeId;
+
+ return $this;
}
/**
@@ -68,10 +119,216 @@ public function getLanguage()
/**
* @param string|null $language
+ * @return static
*/
public function setLanguage($language)
{
$this->language = $language;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasRegion()
+ {
+ return $this->region !== null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRegion()
+ {
+ return $this->region;
+ }
+
+ /**
+ * @param string|null $region
+ * @return static
+ */
+ public function setRegion($region)
+ {
+ $this->region = $region;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasSessionToken()
+ {
+ return $this->sessionToken !== null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getSessionToken()
+ {
+ return $this->sessionToken;
+ }
+
+ /**
+ * @param string|null $sessionToken
+ * @return static
+ */
+ public function setSessionToken($sessionToken)
+ {
+ $this->sessionToken = $sessionToken;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasSpecificFields()
+ {
+ return !empty($this->fields);
+ }
+
+ /**
+ * @return string[]|null
+ */
+ public function getFields()
+ {
+ return $this->fields;
+ }
+
+ /**
+ * @param string[] $fields
+ * @param bool $intersect
+ * @return string[]|null
+ */
+ private function prepareFields($fields, $intersect = true)
+ {
+ if (empty($fields) || !is_array($fields)) {
+ return [];
+ }
+
+ $res = array_unique(array_filter(array_map('trim', array_filter($fields, 'is_string'))));
+
+ return $intersect ? array_intersect($res, static::getAllAvailableFields()) : $res;
+ }
+
+ /**
+ * @return string[]|null
+ */
+ public static function getAllAvailableFields()
+ {
+ return array_merge(static::FIELDS_BASIC, static::FIELDS_CONTACT, static::FIELDS_ATMOSPHERE);
+ }
+
+ /**
+ * @param string|string[] $fields
+ * @return static
+ */
+ public function withFields($fields)
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ if (is_array($fields) && !empty($fields = $this->prepareFields($fields))) {
+ $this->fields = empty($this->fields) ? $fields : array_unique(array_merge($this->fields, $fields));
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string|string[] $fields
+ * @return static
+ */
+ public function withOnlyFields($fields)
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ if (is_array($fields) && !empty($fields = $this->prepareFields($fields))) {
+ $this->fields = $fields;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string|string[] $fields
+ * @return static
+ */
+ public function withoutFields($fields)
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ if (is_array($fields) && !empty($fields = $this->prepareFields($fields, false))) {
+ $fields = array_diff(empty($this->fields) ? static::getAllAvailableFields() : $this->fields, $fields);
+ $this->fields = empty($fields) ? null : $fields;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return static
+ */
+ public function withoutBasicFields()
+ {
+ return $this->withoutFields(static::FIELDS_BASIC);
+ }
+
+ /**
+ * @return static
+ */
+ public function withoutContactFields()
+ {
+ return $this->withoutFields(static::FIELDS_CONTACT);
+ }
+
+ /**
+ * @return static
+ */
+ public function withoutAtmosphereFields()
+ {
+ return $this->withoutFields(static::FIELDS_ATMOSPHERE);
+ }
+
+ /**
+ * @return static
+ */
+ public function withBasicFields()
+ {
+ return $this->withFields(static::FIELDS_BASIC);
+ }
+
+ /**
+ * @return static
+ */
+ public function withContactFields()
+ {
+ return $this->withFields(static::FIELDS_CONTACT);
+ }
+
+ /**
+ * @return static
+ */
+ public function withAtmosphereFields()
+ {
+ return $this->withFields(static::FIELDS_ATMOSPHERE);
+ }
+
+ /**
+ * @return static
+ */
+ public function withAllFields()
+ {
+ $this->fields = null;
+
+ return $this;
}
/**
@@ -85,6 +342,18 @@ public function buildQuery()
$query['language'] = $this->language;
}
+ if ($this->hasRegion()) {
+ $query['region'] = $this->region;
+ }
+
+ if ($this->hasSessionToken()) {
+ $query['sessiontoken'] = $this->sessionToken;
+ }
+
+ if ($this->hasSpecificFields()) {
+ $query['fields'] = join(',', $this->fields);
+ }
+
return $query;
}
}
diff --git a/src/Service/Serializer/Place/Base/Place.xml b/src/Service/Serializer/Place/Base/Place.xml
index 099363b3..5219cb65 100644
--- a/src/Service/Serializer/Place/Base/Place.xml
+++ b/src/Service/Serializer/Place/Base/Place.xml
@@ -143,6 +143,12 @@
xml-key-as-attribute="false"
/>
+
+
+
+
+
+
[],
'alt_ids' => [],
'reviews' => [],
+ 'user_ratings_total' => null,
'types' => [],
'permanently_close' => null,
], $options);
@@ -79,6 +80,7 @@ protected function assertPlace($place, array $options = [])
$this->assertSame($options['types'], $place->getTypes());
$this->assertSame($options['permanently_close'], $place->isPermanentlyClose());
$this->assertSame(isset($options['rating']) ? (float) $options['rating'] : null, $place->getRating());
+ $this->assertSame(isset($options['user_ratings_total']) ? (int) $options['user_ratings_total'] : null, $place->getUserRatingsTotal());
$this->assertGeometry($place->getGeometry(), $options['geometry']);
$this->assertOpeningHours($place->getOpeningHours(), $options['opening_hours']);
@@ -200,22 +202,26 @@ protected function assertReview($review, array $options = [])
}
$options = array_merge([
- 'author_name' => null,
- 'author_url' => null,
- 'text' => null,
- 'rating' => null,
- 'time' => null,
- 'language' => null,
- 'aspects' => [],
+ 'author_name' => null,
+ 'author_url' => null,
+ 'profile_photo_url' => null,
+ 'text' => null,
+ 'rating' => null,
+ 'time' => null,
+ 'relative_time_description' => null,
+ 'language' => null,
+ 'aspects' => [],
], $options);
$this->assertInstanceOf(Review::class, $review);
$this->assertSame($options['author_name'], $review->getAuthorName());
$this->assertSame($options['author_url'], $review->getAuthorUrl());
+ $this->assertSame($options['profile_photo_url'], $review->getProfilePhotoUrl());
$this->assertSame($options['text'], $review->getText());
$this->assertSame((float) $options['rating'], $review->getRating());
$this->assertSame((new \DateTime('@'.$options['time']))->getTimestamp(), $review->getTime()->getTimestamp());
+ $this->assertSame($options['relative_time_description'], $review->getRelativeTimeDescription());
$this->assertSame($options['language'], $review->getLanguage());
$this->assertCount(count($options['aspects']), $aspects = $review->getAspects());
diff --git a/tests/Service/Place/Base/PlaceTest.php b/tests/Service/Place/Base/PlaceTest.php
index f6e15649..3304e877 100644
--- a/tests/Service/Place/Base/PlaceTest.php
+++ b/tests/Service/Place/Base/PlaceTest.php
@@ -64,6 +64,8 @@ public function testDefaultState()
$this->assertNull($this->result->getPriceLevel());
$this->assertFalse($this->result->hasRating());
$this->assertNull($this->result->getRating());
+ $this->assertFalse($this->result->hasUserRatingsTotal());
+ $this->assertNull($this->result->getUserRatingsTotal());
$this->assertFalse($this->result->hasUtcOffset());
$this->assertNull($this->result->getUtcOffset());
$this->assertFalse($this->result->hasVicinity());
@@ -176,6 +178,14 @@ public function testRating()
$this->assertSame($rating, $this->result->getRating());
}
+ public function testUserRatingsTotal()
+ {
+ $this->result->setUserRatingsTotal($userRatingsTotal = 48);
+
+ $this->assertTrue($this->result->hasUserRatingsTotal());
+ $this->assertSame($userRatingsTotal, $this->result->getUserRatingsTotal());
+ }
+
public function testUtcOffset()
{
$this->result->setUtcOffset($utcOffset = 100);
diff --git a/tests/Service/Place/Base/ReviewTest.php b/tests/Service/Place/Base/ReviewTest.php
index 7f6cd7a9..fdb9f7d9 100644
--- a/tests/Service/Place/Base/ReviewTest.php
+++ b/tests/Service/Place/Base/ReviewTest.php
@@ -38,12 +38,16 @@ public function testDefaultState()
$this->assertNull($this->review->getAuthorName());
$this->assertFalse($this->review->hasAuthorUrl());
$this->assertNull($this->review->getAuthorUrl());
+ $this->assertFalse($this->review->hasProfilePhotoUrl());
+ $this->assertNull($this->review->getProfilePhotoUrl());
$this->assertFalse($this->review->hasText());
$this->assertNull($this->review->getText());
$this->assertFalse($this->review->hasRating());
$this->assertNull($this->review->getRating());
$this->assertFalse($this->review->hasTime());
$this->assertNull($this->review->getTime());
+ $this->assertFalse($this->review->hasRelativeTimeDescription());
+ $this->assertNull($this->review->getRelativeTimeDescription());
$this->assertFalse($this->review->hasLanguage());
$this->assertNull($this->review->getLanguage());
$this->assertFalse($this->review->hasAspects());
@@ -66,6 +70,14 @@ public function testAuthorUrl()
$this->assertSame($authorUrl, $this->review->getAuthorUrl());
}
+ public function testProfilePhotoUrl()
+ {
+ $this->review->setProfilePhotoUrl($profilePhotoUrl = 'photo_url');
+
+ $this->assertTrue($this->review->hasProfilePhotoUrl());
+ $this->assertSame($profilePhotoUrl, $this->review->getProfilePhotoUrl());
+ }
+
public function testText()
{
$this->review->setText($text = 'foo');
@@ -90,6 +102,14 @@ public function testTime()
$this->assertSame($time, $this->review->getTime());
}
+ public function testRelativeTimeDescription()
+ {
+ $this->review->setRelativeTimeDescription($time = "a month ago");
+
+ $this->assertTrue($this->review->hasRelativeTimeDescription());
+ $this->assertSame($time, $this->review->getRelativeTimeDescription());
+ }
+
public function testLanguage()
{
$this->review->setLanguage($language = 'fr');
diff --git a/tests/Service/Place/Detail/PlaceDetailServiceTest.php b/tests/Service/Place/Detail/PlaceDetailServiceTest.php
index 4306a0ab..e00bd21f 100644
--- a/tests/Service/Place/Detail/PlaceDetailServiceTest.php
+++ b/tests/Service/Place/Detail/PlaceDetailServiceTest.php
@@ -74,6 +74,39 @@ public function testProcessWithLanguage($format)
$this->assertPlaceDetailResponse($response, $request);
}
+ /**
+ * @param string $format
+ *
+ * @dataProvider formatProvider
+ */
+ public function testProcessWithRegion($format)
+ {
+ $request = $this->createRequest();
+ $request->setRegion('uk');
+
+ $this->service->setFormat($format);
+ $response = $this->service->process($request);
+
+ $this->assertPlaceDetailResponse($response, $request);
+ }
+
+ /**
+ * @param string $format
+ *
+ * @dataProvider formatProvider
+ */
+ public function testProcessWithBasicFields($format)
+ {
+ $request = $this->createRequest()->withBasicFields();
+
+ $this->service->setFormat($format);
+ $response = $this->service->process($request);
+
+ $this->assertPlaceDetailResponse($response, $request);
+ $this->assertFalse($response->getResult()->hasReviews());
+ $this->assertTrue($response->getResult()->hasFormattedAddress());
+ }
+
/**
* @param string $format
*
diff --git a/tests/Service/Place/Detail/Request/PlaceDetailRequestTest.php b/tests/Service/Place/Detail/Request/PlaceDetailRequestTest.php
index 46a17b5b..8c82044f 100644
--- a/tests/Service/Place/Detail/Request/PlaceDetailRequestTest.php
+++ b/tests/Service/Place/Detail/Request/PlaceDetailRequestTest.php
@@ -49,6 +49,12 @@ public function testDefaultState()
$this->assertSame($this->placeId, $this->request->getPlaceId());
$this->assertFalse($this->request->hasLanguage());
$this->assertNull($this->request->getLanguage());
+ $this->assertFalse($this->request->hasRegion());
+ $this->assertNull($this->request->getRegion());
+ $this->assertFalse($this->request->hasSessionToken());
+ $this->assertNull($this->request->getSessionToken());
+ $this->assertFalse($this->request->hasSpecificFields());
+ $this->assertNull($this->request->getFields());
}
public function testPlaceId()
@@ -66,6 +72,63 @@ public function testLanguage()
$this->assertSame($language, $this->request->getLanguage());
}
+ public function testRegion()
+ {
+ $this->request->setRegion($region = 'uk');
+
+ $this->assertTrue($this->request->hasRegion());
+ $this->assertSame($region, $this->request->getRegion());
+ }
+
+ public function testSessionToken()
+ {
+ $this->request->setSessionToken($token = 'session_token');
+
+ $this->assertTrue($this->request->hasSessionToken());
+ $this->assertSame($token, $this->request->getSessionToken());
+ }
+
+ public function testFields()
+ {
+ $this->request->withoutAtmosphereFields();
+
+ $expected = array_merge(PlaceDetailRequest::FIELDS_BASIC, PlaceDetailRequest::FIELDS_CONTACT);
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals($expected, $this->request->getFields(), 'Testing withoutAtmosphereFields Function', 0.0, 10, true);
+
+ $this->request->withFields('photo ');
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals($expected, $this->request->getFields(), 'Testing adding a duplicate field', 0.0, 10, true);
+
+ $this->request->withoutFields(' photo');
+ $this->request->withFields('invalid_field');
+ $expected_1 = array_diff($expected, ['photo']);
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals($expected_1, $this->request->getFields(), 'Testing removing a field and adding an invalid field', 0.0, 10, true);
+
+ $this->request->withFields(' photo, review ');
+ $expected_1 = array_merge($expected, ['review']);
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals($expected_1, $this->request->getFields(), 'Testing adding 2 non duplicate fields from 2 different field sets', 0.0, 10, true);
+
+ $this->request->withAllFields();
+ $this->assertFalse($this->request->hasSpecificFields());
+ $this->assertNull($this->request->getFields());
+
+ $this->request->withFields('review');
+ $this->request->withOnlyFields(array_merge(['photo '], $expected = ['name', 'photo', 'website', 'opening_hours']));
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals($expected, $this->request->getFields(), 'Testing withOnlyFields', 0.0, 10, true);
+
+ $this->request->withoutContactFields();
+ $this->assertTrue($this->request->hasSpecificFields());
+ $this->assertEquals(array_diff($expected, PlaceDetailRequest::FIELDS_CONTACT), $this->request->getFields(), 'Testing withoutContactFields', 0.0, 10, true);
+
+ $this->request->withoutBasicFields();
+ $this->assertFalse($this->request->hasSpecificFields());
+ $this->assertNull($this->request->getFields());
+ }
+
public function testBuildQuery()
{
$this->assertSame(['placeid' => $this->placeId], $this->request->buildQuery());
@@ -76,8 +139,48 @@ public function testBuildQueryWithLanguage()
$this->request->setLanguage($language = 'fr');
$this->assertSame([
- 'placeid' => $this->placeId,
+ 'placeid' => $this->placeId,
'language' => $language,
], $this->request->buildQuery());
}
+
+ public function testBuildQueryWithRegion()
+ {
+ $this->request->setRegion($region = 'uk');
+
+ $this->assertSame([
+ 'placeid' => $this->placeId,
+ 'region' => $region,
+ ], $this->request->buildQuery());
+ }
+
+ public function testBuildQueryWithSessionToken()
+ {
+ $this->request->setSessionToken($token = 'token');
+
+ $this->assertSame([
+ 'placeid' => $this->placeId,
+ 'sessiontoken' => $token,
+ ], $this->request->buildQuery());
+ }
+
+ public function testBuildQueryWithBasicFields()
+ {
+ $this->request->withFields($fields = PlaceDetailRequest::FIELDS_BASIC);
+
+ $this->assertSame([
+ 'placeid' => $this->placeId,
+ 'fields' => join(',', $fields),
+ ], $this->request->buildQuery());
+ }
+
+ public function testBuildQueryWithSpecificFields()
+ {
+ $this->request->withFields($fields ='icon,name,url,photo,invalid_field,website, rating , review,user_ratings_total ');
+
+ $this->assertSame([
+ 'placeid' => $this->placeId,
+ 'fields' => 'icon,name,url,photo,website,rating,review,user_ratings_total',
+ ], $this->request->buildQuery());
+ }
}