diff --git a/Command/CleanCacheCommand.php b/Command/CleanCacheCommand.php index 9f7a453..33c2284 100644 --- a/Command/CleanCacheCommand.php +++ b/Command/CleanCacheCommand.php @@ -28,11 +28,15 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $cleanCount = $geolocationApi->cleanCache(); + if(is_array($cleanCount)) + $cleanCount = $cleanCount['n']; $output->writeln($cleanCount . " cache entries were removed"); } catch (\Exception $e) { - $output->writeln("Cache not available. Cannot clean!"); + $output->writeln(sprintf('%s', $e->getMessage())); + + return; } } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 97b67b5..48a60a5 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -20,9 +20,23 @@ public function getConfigTreeBuilder() $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('google_geolocation'); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. + $supportedDrivers = array('orm', 'mongodb'); + + $rootNode + ->children() + ->scalarNode('db_driver') + ->validate() + ->ifNotInArray($supportedDrivers) + ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($supportedDrivers)) + ->end() + ->cannotBeOverwritten() + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('daily_limit')->defaultValue(2500)->end() + ->scalarNode('cache_lifetime')->defaultValue(24)->end() + ->end() + ; return $treeBuilder; } diff --git a/DependencyInjection/GoogleGeolocationExtension.php b/DependencyInjection/GoogleGeolocationExtension.php index f71958b..0fb721e 100644 --- a/DependencyInjection/GoogleGeolocationExtension.php +++ b/DependencyInjection/GoogleGeolocationExtension.php @@ -23,6 +23,9 @@ public function load(array $configs, ContainerBuilder $container) $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yml'); + $loader->load($config['db_driver'].'.yml'); + + $container->setParameter('google_geolocation.geolocation_api.daily_limit', $config['daily_limit']); + $container->setParameter('google_geolocation.geolocation_api.cache_lifetime', $config['cache_lifetime']); } } diff --git a/Document/ApiLog.php b/Document/ApiLog.php new file mode 100755 index 0000000..85170b3 --- /dev/null +++ b/Document/ApiLog.php @@ -0,0 +1,144 @@ +setRequests(0); + } + + public function incrementRequests() + { + $this->setRequests($this->getRequests() + 1); + } + + public function getId() + { + return $this->id; + } + + /** + * Set lastStatus + * + * @param string $lastStatus + */ + public function setLastStatus($lastStatus) + { + $this->lastStatus = $lastStatus; + return $this; + } + + /** + * Get lastStatus + * + * @return string + */ + public function getLastStatus() + { + return $this->lastStatus; + } + + /** + * Set requests + * + * @param integer $requests + */ + public function setRequests($requests) + { + $this->requests = $requests; + return $this; + } + + /** + * Get requests + * + * @return integer + */ + public function getRequests() + { + return $this->requests; + } + + /** + * Set created + * + * @param date $created + */ + public function setCreated($created) + { + $this->created = $created; + return $this; + } + + /** + * Get created + * + * @return date + */ + public function getCreated() + { + return $this->created; + } + + /** + * Set updated + * + * @param datetime $updated + */ + public function setUpdated($updated) + { + $this->updated = $updated; + return $this; + } + + /** + * Get updated + * + * @return datetime + */ + public function getUpdated() + { + return $this->updated; + } +} diff --git a/Document/ApiLogRepository.php b/Document/ApiLogRepository.php new file mode 100755 index 0000000..aef921f --- /dev/null +++ b/Document/ApiLogRepository.php @@ -0,0 +1,39 @@ +createQueryBuilder('GoogleGeolocationBundle:ApiLog') + ->field('created')->equals($date) + ; + + try { + return $qb->getQuery() + ->getSingleResult(); + } catch (Doctrine\ODM\MongoDB\MongoDBException $e) { + return null; + } + } + + public function cleanCache() + { + $from = new \DateTime('today'); + + return $this->createQueryBuilder('GoogleGeolocationBundle:ApiLog') + ->remove() + ->field('created')->lt($from) + ->getQuery() + ->execute(); + } + +} \ No newline at end of file diff --git a/Document/Location.php b/Document/Location.php new file mode 100755 index 0000000..fcd9693 --- /dev/null +++ b/Document/Location.php @@ -0,0 +1,225 @@ +setMatches(false); + $this->setHits(0); + } + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Set search + * + * @param string $search + */ + public function setSearch($search) + { + $this->search = $search; + return $this; + } + + /** + * Get search + * + * @return string + */ + public function getSearch() + { + return $this->search; + } + + /** + * Set matches + * + * @param boolean $matches + */ + public function setMatches($matches) + { + $this->matches = $matches; + return $this; + } + + /** + * Get matches + * + * @return boolean + */ + public function getMatches() + { + return $this->matches; + } + + /** + * Set result + * + * @param string $result + */ + public function setResult($result) + { + $this->result = $result; + return $this; + } + + /** + * Get result + * + * @return string + */ + public function getResult($raw = true) + { + return $this->result; + } + + /** + * Set hits + * + * @param integer $hits + */ + public function setHits($hits) + { + $this->hits = $hits; + return $this; + } + + /** + * Get hits + * + * @return integer + */ + public function getHits() + { + return $this->hits; + } + + /** + * Set created + * + * @param datetime $created + */ + public function setCreated($created) + { + $this->created = $created; + return $this; + } + + /** + * Get created + * + * @return datetime + */ + public function getCreated() + { + return $this->created; + } + + /** + * Set updated + * + * @param datetime $updated + */ + public function setUpdated($updated) + { + $this->updated = $updated; + return $this; + } + + /** + * Get updated + * + * @return datetime + */ + public function getUpdated() + { + return $this->updated; + } + + /** + * Set status + * + * @param string $status + */ + public function setStatus($status) + { + $this->status = $status; + return $this; + } + + /** + * Get status + * + * @return string + */ + public function getStatus() + { + return $this->status; + } +} diff --git a/Document/LocationRepository.php b/Document/LocationRepository.php new file mode 100755 index 0000000..2b2170b --- /dev/null +++ b/Document/LocationRepository.php @@ -0,0 +1,31 @@ +createQueryBuilder('GoogleGeolocationBundle:Location') + ->field('search')->equals($search); + + try { + return $qb->getQuery() + ->getSingleResult(); + } catch (Doctrine\ODM\MongoDB\MongoDBException $e) { + return null; + } + } + + public function cleanCache($expiresAt) + { + return $this->createQueryBuilder('GoogleGeolocationBundle:Location') + ->remove() + ->field('created')->lt($expiresAt) + ->getQuery() + ->execute(); + } + +} \ No newline at end of file diff --git a/Entity/ApiLog.php b/Entity/ApiLog.php index 8b7b509..ab4c663 100644 --- a/Entity/ApiLog.php +++ b/Entity/ApiLog.php @@ -5,7 +5,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass="Google\GeolocationBundle\Repository\ApiLog") + * @ORM\Entity(repositoryClass="Google\GeolocationBundle\ApiLogRepository") * @ORM\Table(name="google_geolocation_api_log") * @ORM\HasLifecycleCallbacks() */ diff --git a/Repository/ApiLog.php b/Entity/ApiLogRepository.php old mode 100644 new mode 100755 similarity index 63% rename from Repository/ApiLog.php rename to Entity/ApiLogRepository.php index eee452a..c95fffa --- a/Repository/ApiLog.php +++ b/Entity/ApiLogRepository.php @@ -1,6 +1,6 @@ createQueryBuilder('l') + ->delete() + ->where('l.created < :expires') + ->setParameter('expires', $from) + ->getQuery() + ->execute(); + } } diff --git a/Entity/Location.php b/Entity/Location.php index a83c1f8..4222c68 100644 --- a/Entity/Location.php +++ b/Entity/Location.php @@ -2,14 +2,16 @@ namespace Google\GeolocationBundle\Entity; +use Webinfopro\Bundle\GoogleGeolocationBundle\Model\BaseLocation; + use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass="Google\GeolocationBundle\Repository\LocationRepository") + * @ORM\Entity(repositoryClass="Google\GeolocationBundle\LocationRepository") * @ORM\Table(name="google_geolocation_location") * @ORM\HasLifecycleCallbacks() */ -class Location +class Location extends BaseLocation { /** * @ORM\Id @@ -69,63 +71,6 @@ public function setUpdatedValue() $this->setUpdated(new \DateTime()); } - public function incrementHits() - { - $this->setHits($this->getHits() + 1); - } - - /** - * Get the address components for a Geocoded result. Geocoding service - * may return more than 1 match for a search. The $match param can - * be used to specify the result to return - * - * @param int $match Result match to return (indexes start at 0) - * @return array Key/Value address components - */ - public function getAddressComponents($match = 0) - { - $matches = json_decode($this->getResult(), true); - - if (false === isset($matches[$match])) - { - return false; - } - - $components = array(); - foreach ($matches[$match]['address_components'] as $component) - { - $type = $component['types'][0]; - $components[$type] = $component['long_name']; - } - - return $components; - } - - /** - * Get the latlng components for a Geocoded result. Geocoding service - * may return more than 1 match for a search. The $match param can - * be used to specify the result to return - * - * @param int $match Result match to return (indexes start at 0) - * @return array Key/Value latlng component - */ - public function getLatLng($match = 0) - { - $matches = json_decode($this->getResult(), true); - - if (false === isset($matches[$match])) - { - return false; - } - - $components = array(); - $components['lat'] = $matches[$match]['geometry']['location']['lat']; - $components['lng'] = $matches[$match]['geometry']['location']['lng']; - - return $components; - - } - /** * Get id * @@ -196,22 +141,6 @@ public function getResult($raw = true) return $this->result; } - /** - * Get result as array - * - * @return string - */ - public function getResultArray() - { - $results = array(); - if ($this->getMatches()) - { - // Retrieve the result. - $results = json_decode($this->getResult()); - } - return $results; - } - /** * Set hits * diff --git a/Repository/LocationRepository.php b/Entity/LocationRepository.php old mode 100644 new mode 100755 similarity index 94% rename from Repository/LocationRepository.php rename to Entity/LocationRepository.php index 1b9c6bc..76ae3fd --- a/Repository/LocationRepository.php +++ b/Entity/LocationRepository.php @@ -1,6 +1,6 @@ browser = $browser ?: new Browser(); - + $this->em = null; $this->dailyLimit = 0; // No restriction $this->cacheLifetime = 0; @@ -82,6 +87,21 @@ public function setEntityManager(\Doctrine\ORM\EntityManager $em = null) $this->setCacheEnabled(); } + /** + * Set the Document Manager. This enables the availability of the cache + * and API limiting + * + * @param \Doctrine\ODM\DocumentManager $em DocumentManager + */ + public function setDocumentManager(\Doctrine\ODM\MongoDB\DocumentManager $em = null) + { + $this->em = $em; + // Cache become available now + $this->setCacheEnabled(); + + $this->isORM = false; + } + /** * Set daily limit for API. The Entity Manager must be set first with * setEntityManager() @@ -113,7 +133,7 @@ public function setBrowser(Browser $browser) { $this->browser = $browser; } - + /** * Enable the cache */ @@ -121,12 +141,12 @@ public function setCacheEnabled() { if (true === is_null($this->em)) { - throw new \Exception("Cannot enable cache. EntityManager must be set via setEntityManager()"); + throw new \Exception("Cannot enable cache. Manager must be set via setEntityManager() OR setDocumentManager()"); } - + $this->cacheAvailable = true; } - + /** * Disable the cache */ @@ -134,7 +154,7 @@ public function setCacheDisabled() { $this->cacheAvailable = false; } - + public function locateAddress($search) { $location = null; @@ -148,8 +168,9 @@ public function locateAddress($search) if (true === is_null($location)) { - // No cache, Use Google Geolocation API - $location = new Location(); + // No cache, Use Google Geolocation API + $location = $this->getLocation(); + $location->setSearch($search); $location = $this->geolocate($location); @@ -193,7 +214,16 @@ public function cleanCache() throw new \Exception("Unable to clean cache. There is no cache available"); } + if($this->cacheLifetime == 0) + { + throw new \Exception("Unable to clean cache. Lifetime=infinite"); + } + $expiresAt = date("Y-m-d H:i:s", mktime(date("H")-$this->cacheLifetime)); + + $this->em + ->getRepository('GoogleGeolocationBundle:ApiLog')->cleanCache(); + // Clean cache return $this->em ->getRepository('GoogleGeolocationBundle:Location') @@ -203,10 +233,8 @@ public function cleanCache() /** * Geolocate and populate Location entity with result * - * @param Google\GeolocationBundle\Entity\Location $location - * @return Google\GeolocationBundle\Entity\Location */ - protected function geolocate(\Google\GeolocationBundle\Entity\Location $location) + protected function geolocate($location) { // Check limiting if ($this->apiAttemptsAllowed()) @@ -307,7 +335,7 @@ protected function logResponse($response) if (true === is_null($apiLog)) { - $apiLog = new ApiLog(); + $apiLog = $this->getLog(); } $apiLog->setLastStatus($response['status']); @@ -318,4 +346,20 @@ protected function logResponse($response) return $apiLog; } + + protected function getLocation() + { + if($this->isORM) + return new Location; + + return new MongoLocation; + } + + protected function getLog() + { + if($this->isORM) + return new ApiLog; + + return new MongoApiLog; + } } diff --git a/Model/BaseLocation.php b/Model/BaseLocation.php new file mode 100755 index 0000000..cf57de8 --- /dev/null +++ b/Model/BaseLocation.php @@ -0,0 +1,80 @@ +setHits($this->getHits() + 1); + } + + /** + * Get the address components for a Geocoded result. Geocoding service + * may return more than 1 match for a search. The $match param can + * be used to specify the result to return + * + * @param int $match Result match to return (indexes start at 0) + * @return array Key/Value address components + */ + public function getAddressComponents($match = 0) + { + $matches = json_decode($this->getResult(), true); + + if (false === isset($matches[$match])) + { + return false; + } + + $components = array(); + foreach ($matches[$match]['address_components'] as $component) + { + $type = $component['types'][0]; + $components[$type] = $component['long_name']; + } + + return $components; + } + + /** + * Get the latlng components for a Geocoded result. Geocoding service + * may return more than 1 match for a search. The $match param can + * be used to specify the result to return + * + * @param int $match Result match to return (indexes start at 0) + * @return array Key/Value latlng component + */ + public function getLatLng($match = 0) + { + $matches = json_decode($this->getResult(), true); + + if (false === isset($matches[$match])) + { + return false; + } + + $components = array(); + $components['lat'] = $matches[$match]['geometry']['location']['lat']; + $components['lng'] = $matches[$match]['geometry']['location']['lng']; + + return $components; + + } + + /** + * Get result as array + * + * @return string + */ + public function getResultArray() + { + $results = array(); + if ($this->getMatches()) + { + // Retrieve the result. + $results = json_decode($this->getResult()); + } + return $results; + } +} \ No newline at end of file diff --git a/Resources/config/mongodb.yml b/Resources/config/mongodb.yml new file mode 100755 index 0000000..759b36e --- /dev/null +++ b/Resources/config/mongodb.yml @@ -0,0 +1,14 @@ +parameters: + google_geolocation.geolocation_api.class: Webinfopro\Bundle\GoogleGeolocationBundle\Geolocation\GeolocationApi + # Max attempts to Google Geocoding API per day. Google's restriction is 2,500 + google_geolocation.geolocation_api.daily_limit: 2500 + # Max lifetime of geocoding result (hours). Set to 0 to disable caching + google_geolocation.geolocation_api.cache_lifetime: 24 + +services: + google_geolocation.geolocation_api: + class: %google_geolocation.geolocation_api.class% + calls: + - [ setDocumentManager, [ @doctrine.odm.mongodb.document_manager ] ] + - [ setDailyLimit, [ %google_geolocation.geolocation_api.daily_limit% ] ] + - [ setCacheLifetime, [ %google_geolocation.geolocation_api.cache_lifetime% ] ] \ No newline at end of file diff --git a/Resources/config/orm.yml b/Resources/config/orm.yml new file mode 100755 index 0000000..bdf020c --- /dev/null +++ b/Resources/config/orm.yml @@ -0,0 +1,14 @@ +parameters: + google_geolocation.geolocation_api.class: Webinfopro\Bundle\GoogleGeolocationBundle\Geolocation\GeolocationApi + # Max attempts to Google Geocoding API per day. Google's restriction is 2,500 + google_geolocation.geolocation_api.daily_limit: 2500 + # Max lifetime of geocoding result (hours). Set to 0 to disable caching + google_geolocation.geolocation_api.cache_lifetime: 24 + +services: + google_geolocation.geolocation_api: + class: %google_geolocation.geolocation_api.class% + calls: + - [ setEntityManager, [ @doctrine.orm.entity_manager ] ] + - [ setDailyLimit, [ %google_geolocation.geolocation_api.daily_limit% ] ] + - [ setCacheLifetime, [ %google_geolocation.geolocation_api.cache_lifetime% ] ] \ No newline at end of file diff --git a/Resources/config/services.yml b/Resources/config/services.yml deleted file mode 100644 index 6fd854e..0000000 --- a/Resources/config/services.yml +++ /dev/null @@ -1,14 +0,0 @@ -parameters: - google_geolocation.geolocation_api.class: Google\GeolocationBundle\Geolocation\GeolocationApi - # Max attempts to Google Geocoding API per day. Google's restriction is 2,500 - google_geolocation.geolocation_api.daily_limit: 2500 - # Max lifetime of geocoding result (hours). Set to 0 to disable caching - google_geolocation.geolocation_api.cache_lifetime: 24 - -services: - google_geolocation.geolocation_api: - class: %google_geolocation.geolocation_api.class% - #calls: - # - [ setEntityManager, [ @doctrine.orm.entity_manager ] ] - # - [ setDailyLimit, [ %google_geolocation.geolocation_api.daily_limit% ] ] - # - [ setCacheLifetime, [ %google_geolocation.geolocation_api.cache_lifetime% ] ] diff --git a/composer.json b/composer.json index 041cc6b..b163c07 100644 --- a/composer.json +++ b/composer.json @@ -1,19 +1,32 @@ { - "name": "dsyph3r/google-geolocation-bundle", - "type": "symfony-bundle", - "description": "Google Geolocaton integration for your Symfony2 project", - "keywords": ["google", "geolocation"], - "homepage": "https://github.com/dsyph3r/GoogleGeolocationBundle", - "license": "MIT", - "authors": [ - { "name": "Darren Rees", "email": "d.syph.3r@gmail.com", "homepage": "http://blog.dsyph3r.com/" } - ], - "require": { - "php": ">=5.3.2", - "kriswallsmith/buzz": ">=0.9" - }, - "autoload": { - "psr-0": { "Google\\GeolocationBundle": "" } - }, - "target-dir": "Google/GeolocationBundle" -} + "name" : "dsyph3r/google-geolocation-bundle", + "description" : "Google Geolocaton integration for your Symfony2 project", + "type" : "symfony-bundle", + "authors" : [{ + "name" : "Darren Rees", + "email" : "d.syph.3r@gmail.com", + "homepage" : "http://blog.dsyph3r.com/" + } + ], + "keywords" : [ + "google", + "geolocation" + ], + "homepage" : "https://github.com/dsyph3r/GoogleGeolocationBundle", + "license" : [ + "MIT" + ], + "require" : { + "kriswallsmith/buzz" : ">=0.9", + "php" : ">=5.3.2" + }, + "autoload" : { + "psr-0" : { + "Google\\GeolocationBundle" : "" + } + }, + "target-dir" : "Google/GeolocationBundle", + "suggest" : { + "gedmo/doctrine-extensions" : "Allows automatique update of created and updated with db_driver=mongodb" + } +} \ No newline at end of file