├── .gitignore ├── HappyrLocationBundle.php ├── Geocoder ├── GeocoderInterface.php └── GeocodedResult.php ├── Entity ├── City.php ├── Location.php ├── Region.php ├── ComponentRepository.php ├── Municipality.php ├── Country.php ├── Component.php └── BaseLocation.php ├── Resources ├── config │ ├── serializer │ │ └── Entity.Location.yml │ └── services.yml ├── translations │ ├── validators.sv.yml │ └── messages.sv.yml └── public │ └── js │ └── google.maps.js ├── .travis.yml ├── composer.json ├── Form ├── DataTransformer │ ├── CountryTransformer.php │ └── ComponentToStringTransformer.php ├── Events │ └── GeocodeLocationString.php └── Type │ └── LocationType.php ├── DependencyInjection ├── Configuration.php └── HappyrLocationExtension.php ├── README.md ├── phpunit.xml.dist ├── Service └── LocationService.php └── Manager └── LocationManager.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | build 4 | phpunit.xml 5 | composer.lock 6 | vendor 7 | -------------------------------------------------------------------------------- /HappyrLocationBundle.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('e') 20 | ->where('e.name LIKE :name') 21 | ->orderBy('e.name', 'DESC') 22 | ->setParameter('name', '%'.$name.'%') 23 | ->getResult(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Resources/translations/messages.sv.yml: -------------------------------------------------------------------------------- 1 | location: 2 | 3 | form: 4 | address: 'Adress' 5 | country: 'Land' 6 | city: 'Stad' 7 | municipality: 'Kommun' 8 | region: 'Postort' 9 | zipCode: 'Postnummer' 10 | location: 'Adress' 11 | 12 | help: 13 | address: 'Ange adressen med gatunamn och gatunummer' 14 | country: 'Ange land från listan' 15 | city: 'Ange en stad' 16 | municipality: 'Ange en kommun' 17 | region: 'Ange en postort' 18 | zipCode: 'Ange vilket postnummer' 19 | location: 'Börja skriv in din adress. Du får du upp ett antal förslag som du kan välja mellan. ' 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - hhvm 9 | 10 | env: 11 | - SYMFONY_VERSION=2.3.* 12 | 13 | before_script: 14 | - composer self-update 15 | - composer require symfony/symfony:${SYMFONY_VERSION} --prefer-source 16 | 17 | script: phpunit --coverage-text 18 | 19 | matrix: 20 | allow_failures: 21 | - env: SYMFONY_VERSION=dev-master 22 | - php: hhvm 23 | include: 24 | - php: 5.5 25 | env: SYMFONY_VERSION=2.4.* 26 | - php: 5.5 27 | env: SYMFONY_VERSION=2.5.* 28 | - php: 5.5 29 | env: SYMFONY_VERSION=dev-master 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happyr/location-bundle", 3 | "type": "symfony-bundle", 4 | "description": "A Symfony2 Bundle to handle geographic location of your entities", 5 | "keywords": ["Location"], 6 | "homepage": "http://developer.happyr.com/symfony2-bundles/location-bundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Tobias Nyholm", 11 | "email": "tobias@happyr.com" 12 | } 13 | ], 14 | "require": { 15 | "php": "^5.4 || ^7.0", 16 | "cocur/slugify": "^1.4" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Happyr\\LocationBundle\\": "" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Entity/Municipality.php: -------------------------------------------------------------------------------- 1 | code = $code; 32 | } 33 | 34 | /** 35 | * Get code. 36 | * 37 | * @return string 38 | */ 39 | public function getCode() 40 | { 41 | return $this->code; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Form/DataTransformer/CountryTransformer.php: -------------------------------------------------------------------------------- 1 | getCode(); 30 | } 31 | 32 | /** 33 | * We don't need any reverse transform. 34 | * 35 | * @param mixed $code 36 | * 37 | * @return mixed 38 | */ 39 | public function reverseTransform($code) 40 | { 41 | return $code; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 21 | } else { 22 | // Symfony < 4.2 code 23 | $rootNode = $treeBuilder->root('happyr_location'); 24 | } 25 | 26 | $rootNode->children() 27 | ->scalarNode('geocoder_service')->isRequired()->cannotBeEmpty()->end() 28 | ->end(); 29 | 30 | return $treeBuilder; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DependencyInjection/HappyrLocationExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 25 | 26 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 27 | $loader->load('services.yml'); 28 | 29 | $container->getDefinition('happyr.location.location_type') 30 | ->replaceArgument(1, new Reference($config['geocoder_service'])); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Resources/public/js/google.maps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Init the location bundle 3 | */ 4 | function locationBundle_initialize() { 5 | var elements = document.getElementsByClassName("google-autocomplete"); 6 | 7 | //for every element with that class 8 | Array.prototype.forEach.call(elements, function (element) { 9 | var autocomplete = new google.maps.places.Autocomplete(element); 10 | autocomplete.setComponentRestrictions({'country': 'se'}); 11 | 12 | if (element.dataset.googleAutocompleteType != undefined) { 13 | autocomplete.setTypes([element.dataset.googleAutocompleteType]); 14 | } 15 | 16 | //make sure we dont have any placeholder 17 | element.setAttribute("placeholder", ""); 18 | 19 | //disable 'enter' in the input. We dont want the form to be submitted when the user is choosing form the dropdown 20 | element.onkeydown = locationBundle_checkEnter; 21 | }); 22 | 23 | } 24 | 25 | //google.maps.event.addDomListener(window, 'load', locationBundle_initialize); 26 | 27 | /** 28 | * Disable enter on a specific input 29 | * @param e 30 | * @returns {boolean} 31 | */ 32 | function locationBundle_checkEnter(e) { 33 | e = e || event; 34 | return (e.keyCode || e.which || e.charCode || 0) !== 13; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Happyr LocationBundle 2 | ===================== 3 | 4 | A Symfony2 Bundle to handle locations. This provided a Locaiton object with differnet parts to clearly identify a location. 5 | 6 | 7 | 8 | ## Installation 9 | 10 | ### 1. Install with composer: 11 | 12 | ``` 13 | php composer.phar require happyr/location-bundle 14 | ``` 15 | 16 | ### 2. Enable the bundle: 17 | 18 | ```php 19 | // app/AppKernel.php 20 | 21 | public function registerBundles() 22 | { 23 | $bundles = array( 24 | // ... 25 | new Happyr\LocationBundle\HappyrLocationBundle(), 26 | ); 27 | } 28 | ``` 29 | 30 | ### 3. Add a geocoder 31 | 32 | You need to specify a geocoder service to in the coniguration. The geoder must inplement the [GeocoderInterface](/Geocoder/GeocoderInterface.php) 33 | 34 | ``` yaml 35 | happyr_location: 36 | geocoder_service: 'acme.geocoder' 37 | ``` 38 | 39 | 40 | ## Usage 41 | 42 | ``` php 43 | //any form 44 | public function buildForm(FormBuilderInterface $builder, array $options) 45 | { 46 | $builder 47 | ->add('location', 'location', array( 48 | 'components'=>array( 49 | 'country'=>true, 50 | 'city'=>true, 51 | ) 52 | )); 53 | } 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /Form/DataTransformer/ComponentToStringTransformer.php: -------------------------------------------------------------------------------- 1 | lm = $lm; 31 | $this->type = $type; 32 | } 33 | 34 | /** 35 | * "app data"=> "norm data". 36 | * 37 | * @param mixed $data 38 | * 39 | * @return mixed|string 40 | */ 41 | public function transform($data) 42 | { 43 | if ($this->type == 'Country') { 44 | return $data; 45 | } 46 | 47 | if (!$data) { 48 | return ''; 49 | } 50 | 51 | return $data->__toString(); 52 | } 53 | 54 | /** 55 | * get the location component object. 56 | * 57 | * @param mixed $data 58 | * 59 | * @return Component 60 | */ 61 | public function reverseTransform($data) 62 | { 63 | return $this->lm->getObject($this->type, $data, null); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | ./Tests 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | ./vendor/ 32 | ./Tests/ 33 | ./DataFixtures/ 34 | ./Resources/ 35 | ./DependencyInjection/ 36 | 37 | 38 | ./ 39 | 40 | 41 | 42 | 43 | 44 | live 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Service/LocationService.php: -------------------------------------------------------------------------------- 1 | lm = $lm; 25 | } 26 | 27 | /** 28 | * Add GeocodedResult to a Location. 29 | * 30 | * @param GeocodedResult $result 31 | * @param BaseLocation $location 32 | */ 33 | public function addResultToLocation(GeocodedResult $result, BaseLocation $location) 34 | { 35 | $countryCode = strtoupper($result->getCountryCode()); 36 | $location->setAddress($result->getAddress()); 37 | $location->setCountry($this->lm->getObject('Country', $countryCode, null)); 38 | $location->setCity($this->lm->getObject('City', $result->getCity(), $countryCode)); 39 | $location->setZipCode($result->getZipcode()); 40 | $location->setRegion($this->lm->getObject('Region', $result->getRegion(), $countryCode)); 41 | $location->setMunicipality($this->lm->getObject('Municipality', $result->getMunicipality(), $countryCode, array( 42 | 'conditions' => array( 43 | 'code' => $result->getMunicipalityCode(), 44 | ), 45 | ))); 46 | 47 | //get the coordinates 48 | $location->setLat($result->getLat()); 49 | $location->setLng($result->getLng()); 50 | 51 | //set a nice looking string 52 | $location->setLocation($result->getFullLocation()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Entity/Country.php: -------------------------------------------------------------------------------- 1 | setCode($code); 44 | } 45 | 46 | /** 47 | * The __toString method allows a class to decide how it will react when it is converted to a string. 48 | * 49 | * @return string 50 | * 51 | * @link http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring 52 | */ 53 | public function __toString() 54 | { 55 | return $this->getName(); 56 | } 57 | 58 | /** 59 | * Returns the name of the country with the current locale. 60 | * 61 | * @return mixed 62 | */ 63 | public function getName($locale = null) 64 | { 65 | return Intl::getRegionBundle()->getCountryName($this->slug, $locale); 66 | } 67 | 68 | /** 69 | * @param $code 70 | * 71 | * @return $this 72 | */ 73 | public function setCode($code) 74 | { 75 | $this->slug = strtoupper($code); 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getCode() 84 | { 85 | return $this->slug; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Form/Events/GeocodeLocationString.php: -------------------------------------------------------------------------------- 1 | geocoder = $geocoder; 32 | $this->ls = $ls; 33 | } 34 | 35 | /** 36 | * Geocode location. Get as much info as we possible can. 37 | * 38 | * @param FormEvent $event 39 | * 40 | * @return bool 41 | */ 42 | public function geocodeLocation(FormEvent $event) 43 | { 44 | /** @var Location $location */ 45 | $location = $event->getData(); 46 | 47 | $submittedData = $location->getLocation(); 48 | $result = $this->geocoder->geocode($submittedData); 49 | 50 | if (!$result || $result->getLng() == null) { 51 | $location->clear(); 52 | $location->setLocation($submittedData); 53 | 54 | return false; 55 | } 56 | 57 | $this->ls->addResultToLocation($result, $location); 58 | 59 | return true; 60 | } 61 | 62 | /** 63 | * Add the coordinates to this location. 64 | * 65 | * @param FormEvent $event 66 | * 67 | * @return Location 68 | */ 69 | public function addCoordinates(FormEvent $event) 70 | { 71 | /** @var Location $location */ 72 | $location = $event->getData(); 73 | 74 | $result = $this->geocoder->geocode(sprintf( 75 | '%s, %s, %s, %s, %s', 76 | $location->getAddress(), 77 | $location->getMunicipality(), 78 | $location->getCity(), 79 | $location->getRegion(), 80 | $location->getCountry() 81 | )); 82 | 83 | $location->setLat($result->getLat()); 84 | $location->setLng($result->getLng()); 85 | 86 | return $location; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Entity/Component.php: -------------------------------------------------------------------------------- 1 | name = $name; 62 | $this->slug = $slug; 63 | $this->setCountry($country); 64 | } 65 | 66 | /** 67 | * A to string method. 68 | * 69 | * 70 | * @return string 71 | */ 72 | public function __toString() 73 | { 74 | return $this->getName(); 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getId() 81 | { 82 | return $this->id; 83 | } 84 | 85 | /** 86 | * @param string $name 87 | * 88 | * @return $this 89 | */ 90 | public function setName($name) 91 | { 92 | $this->name = $name; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getName() 101 | { 102 | return $this->name; 103 | } 104 | 105 | /** 106 | * @param string $slug 107 | * 108 | * @return $this 109 | */ 110 | public function setSlug($slug) 111 | { 112 | $this->slug = $slug; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @return string 119 | */ 120 | public function getSlug() 121 | { 122 | return $this->slug; 123 | } 124 | 125 | /** 126 | * @param string $country 127 | * 128 | * @return $this 129 | */ 130 | public function setCountry($country) 131 | { 132 | $this->country = strtoupper($country); 133 | 134 | return $this; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Manager/LocationManager.php: -------------------------------------------------------------------------------- 1 | em = $em; 34 | } 35 | 36 | /** 37 | * Returns a object of $type. This will always return a object. A new object will be created if it does not exsist. 38 | * 39 | * @param string $entity must be safe. Don't let the user affect this one. Example "City", "Region" 40 | * @param string $name The name of the type. 41 | * @param string $countryCode 2 digit country code, UPPERCASE 42 | * 43 | * @return mixed 44 | */ 45 | public function getObject($entity, $name, $countryCode, $options = array()) 46 | { 47 | $this->isValidEntity($entity); 48 | 49 | if ($name == null) { 50 | return; 51 | } 52 | 53 | $entity = $this->typePrefix.$entity; 54 | $name = $this->beautifyName($name); 55 | 56 | if ($entity == 'HappyrLocationBundle:Country') { 57 | $name = strtoupper($name); 58 | $countryCode = null; 59 | $conditions = array('slug' => $name); 60 | } else { 61 | $conditions = $this->prepareConditions($countryCode, $options, $name); 62 | } 63 | 64 | //fetch object 65 | $object = $this->em->getRepository($entity)->findOneBy($conditions); 66 | 67 | //if object is not found 68 | if (!$object) { 69 | // Assert: this will never be Country 70 | $entityName = explode(':', $entity); 71 | $entityNamespace = 'Happyr\LocationBundle\Entity\\'.$entityName[1]; 72 | 73 | $slug = $this->slugify($name); 74 | 75 | //create 76 | $object = new $entityNamespace($name, $slug, $countryCode); 77 | if (isset($options['conditions'])) { 78 | foreach ($options['conditions'] as $cName => $cValue) { 79 | $setter = 'set'.ucfirst($cName); 80 | $object->$setter($cValue); 81 | } 82 | } 83 | } 84 | 85 | return $object; 86 | } 87 | 88 | /** 89 | * Return an object by slug. 90 | * 91 | * @param string $entity must be safe. Don't let the user affect this one. Example "City", "Region" 92 | * @param string $slug 93 | * @param string $countryCode 2 digit country code, UPPERCASE 94 | * 95 | * @return Component|null 96 | */ 97 | public function findOneObjectBySlug($entity, $slug, $countryCode) 98 | { 99 | $this->isValidEntity($entity); 100 | 101 | if (empty($slug)) { 102 | return; 103 | } 104 | 105 | $entity = $this->typePrefix.$entity; 106 | 107 | return $this->em->getRepository($entity)->findOneBy(array('slug' => $slug, 'country' => $countryCode)); 108 | } 109 | 110 | /** 111 | * Return an object by name. 112 | * 113 | * @param string $entity must be safe. Don't let the user affect this one. Example "City", "Region" 114 | * @param string $name 115 | * @param string $countryCode 2 digit country code, UPPERCASE 116 | * 117 | * @return Object 118 | */ 119 | public function findOneObjectByName($entity, $name, $countryCode) 120 | { 121 | $this->isValidEntity($entity); 122 | if (empty($name)) { 123 | return; 124 | } 125 | 126 | if ($entity == 'Country') { 127 | $conditions = array('slug' => $name); 128 | } else { 129 | $conditions = array('name' => $name, 'country' => $countryCode); 130 | } 131 | 132 | $entity = $this->typePrefix.$entity; 133 | 134 | return $this->em->getRepository($entity)->findOneBy($conditions); 135 | } 136 | 137 | /** 138 | * Remove a Location object. 139 | * 140 | * @param Component $component 141 | */ 142 | public function removeObject(Component $component) 143 | { 144 | $this->em->remove($component); 145 | $this->em->flush(); 146 | } 147 | 148 | /** 149 | * Makes name beautiful before using it. 150 | * 151 | * @param string $name 152 | * 153 | * @return string 154 | */ 155 | protected function beautifyName($name) 156 | { 157 | return mb_convert_case(mb_strtolower(trim($name), 'UTF-8'), MB_CASE_TITLE, 'UTF-8'); 158 | } 159 | 160 | /** 161 | * @param $entity 162 | */ 163 | protected function isValidEntity($entity) 164 | { 165 | $validEntities = array('City', 'Country', 'Municipality', 'Region'); 166 | if (!in_array($entity, $validEntities)) { 167 | throw new \InvalidArgumentException( 168 | sprintf( 169 | '%s is not a valid entity to use with the LocationManager. You should use of of these: %s', 170 | $entity, 171 | implode(', ', $validEntities) 172 | ) 173 | ); 174 | } 175 | } 176 | 177 | /** 178 | * @param $countryCode 179 | * @param $options 180 | * @param $name 181 | * 182 | * @return array 183 | */ 184 | private function prepareConditions($countryCode, $options, $name) 185 | { 186 | $conditions = array('name' => $name,); 187 | 188 | if ($countryCode !== null) { 189 | $conditions['country'] = $countryCode; 190 | } 191 | 192 | if (isset($options['conditions'])) { 193 | $conditions = array_merge($conditions, $options['conditions']); 194 | } 195 | 196 | return $conditions; 197 | } 198 | 199 | /** 200 | * @param string $string 201 | * 202 | * @return string 203 | */ 204 | protected function slugify($string) 205 | { 206 | $slugify = new Slugify(); 207 | 208 | return $slugify->slugify($string); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Geocoder/GeocodedResult.php: -------------------------------------------------------------------------------- 1 | setCountry($array['country']) 76 | ->setCountryCode($array['countryCode']) 77 | ->setCity($array['city']) 78 | ->setMunicipality($array['municipality']) 79 | ->setMunicipalityCode($array['municipalityCode']) 80 | ->setRegion($array['region']) 81 | ->setZipCode($array['zipCode']) 82 | ->setAddress($array['address']) 83 | ->setLat($array['lat']) 84 | ->setLng($array['lng']) 85 | ->setFullLocation($array['fullLocation']); 86 | 87 | return $result; 88 | } 89 | 90 | /** 91 | * @return array 92 | */ 93 | public static function getResultParts() 94 | { 95 | $defaults = [ 96 | 'country' => null, 97 | 'countryCode' => null, 98 | 'city' => null, 99 | 'municipality' => null, 100 | 'municipalityCode' => null, 101 | 'region' => null, 102 | 'zipCode' => null, 103 | 'address' => null, 104 | 'lat' => null, 105 | 'lng' => null, 106 | 'fullLocation' => null, 107 | ]; 108 | 109 | return $defaults; 110 | } 111 | 112 | /** 113 | * @param $name 114 | * 115 | * @return mixed 116 | */ 117 | public function get($name) 118 | { 119 | $parts = array_keys(self::getResultParts()); 120 | if (!in_array($name, $parts)) { 121 | throw new \InvalidArgumentException(sprintf('Could not find property "%s" on %s', $name, get_class($this))); 122 | } 123 | 124 | return $this->$name; 125 | } 126 | 127 | /** 128 | * @return string 129 | */ 130 | public function getCountry() 131 | { 132 | return $this->country; 133 | } 134 | 135 | /** 136 | * @param string $country 137 | * 138 | * @return GeocodedResult 139 | */ 140 | public function setCountry($country) 141 | { 142 | $this->country = $country; 143 | 144 | return $this; 145 | } 146 | 147 | /** 148 | * @return string 149 | */ 150 | public function getCountryCode() 151 | { 152 | return $this->countryCode; 153 | } 154 | 155 | /** 156 | * @param string $countryCode 157 | * 158 | * @return GeocodedResult 159 | */ 160 | public function setCountryCode($countryCode) 161 | { 162 | $this->countryCode = $countryCode; 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * @return string 169 | */ 170 | public function getCity() 171 | { 172 | return $this->city; 173 | } 174 | 175 | /** 176 | * @param string $city 177 | * 178 | * @return GeocodedResult 179 | */ 180 | public function setCity($city) 181 | { 182 | $this->city = $city; 183 | 184 | return $this; 185 | } 186 | 187 | /** 188 | * @return string 189 | */ 190 | public function getMunicipality() 191 | { 192 | return $this->municipality; 193 | } 194 | 195 | /** 196 | * @param string $municipality 197 | * 198 | * @return GeocodedResult 199 | */ 200 | public function setMunicipality($municipality) 201 | { 202 | $this->municipality = $municipality; 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * @return string 209 | */ 210 | public function getMunicipalityCode() 211 | { 212 | return $this->municipalityCode; 213 | } 214 | 215 | /** 216 | * @param string $municipalityCode 217 | * 218 | * @return GeocodedResult 219 | */ 220 | public function setMunicipalityCode($municipalityCode) 221 | { 222 | $this->municipalityCode = $municipalityCode; 223 | 224 | return $this; 225 | } 226 | 227 | /** 228 | * @return string 229 | */ 230 | public function getRegion() 231 | { 232 | return $this->region; 233 | } 234 | 235 | /** 236 | * @param string $region 237 | * 238 | * @return GeocodedResult 239 | */ 240 | public function setRegion($region) 241 | { 242 | $this->region = $region; 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * @return string 249 | */ 250 | public function getZipCode() 251 | { 252 | return $this->zipCode; 253 | } 254 | 255 | /** 256 | * @param string $zipCode 257 | * 258 | * @return GeocodedResult 259 | */ 260 | public function setZipCode($zipCode) 261 | { 262 | $this->zipCode = $zipCode; 263 | 264 | return $this; 265 | } 266 | 267 | /** 268 | * @return string 269 | */ 270 | public function getAddress() 271 | { 272 | return $this->address; 273 | } 274 | 275 | /** 276 | * @param string $address 277 | * 278 | * @return GeocodedResult 279 | */ 280 | public function setAddress($address) 281 | { 282 | $this->address = $address; 283 | 284 | return $this; 285 | } 286 | 287 | /** 288 | * @return float 289 | */ 290 | public function getLat() 291 | { 292 | return $this->lat; 293 | } 294 | 295 | /** 296 | * @param float $lat 297 | * 298 | * @return GeocodedResult 299 | */ 300 | public function setLat($lat) 301 | { 302 | $this->lat = $lat; 303 | 304 | return $this; 305 | } 306 | 307 | /** 308 | * @return float 309 | */ 310 | public function getLng() 311 | { 312 | return $this->lng; 313 | } 314 | 315 | /** 316 | * @param float $lng 317 | * 318 | * @return GeocodedResult 319 | */ 320 | public function setLng($lng) 321 | { 322 | $this->lng = $lng; 323 | 324 | return $this; 325 | } 326 | 327 | /** 328 | * @return string 329 | */ 330 | public function getFullLocation() 331 | { 332 | return $this->fullLocation; 333 | } 334 | 335 | /** 336 | * @param string $fullLocation 337 | * 338 | * @return GeocodedResult 339 | */ 340 | public function setFullLocation($fullLocation) 341 | { 342 | $this->fullLocation = $fullLocation; 343 | 344 | return $this; 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Entity/BaseLocation.php: -------------------------------------------------------------------------------- 1 | location) && $this->city) { 101 | return $this->city->__toString(); 102 | } 103 | 104 | return $this->location; 105 | } 106 | 107 | /** 108 | * Removes all data associated with the location. 109 | */ 110 | public function clear() 111 | { 112 | $this->location = ''; 113 | $this->country = null; 114 | $this->city = null; 115 | $this->municipality = null; 116 | $this->region = null; 117 | $this->zipCode = ''; 118 | $this->address = ''; 119 | $this->lng = 0; 120 | $this->lat = 0; 121 | } 122 | 123 | /** 124 | * Get id. 125 | * 126 | * @return int 127 | */ 128 | public function getId() 129 | { 130 | return $this->id; 131 | } 132 | 133 | /** 134 | * Get the country code or fallback on SE. 135 | * 136 | * @return string 137 | */ 138 | public function getCountryCode() 139 | { 140 | if ($this->getCountry() == null) { 141 | //fallback 142 | return 'SE'; 143 | } 144 | 145 | return $this->getCountry()->getCode(); 146 | } 147 | 148 | /** 149 | * @return array 150 | */ 151 | public function getCoordinates() 152 | { 153 | return array('lng' => $this->lng, 'lat' => $this->lat); 154 | } 155 | 156 | /** 157 | * @return bool 158 | */ 159 | public function hasCoordinates() 160 | { 161 | return $this->lng != 0 && $this->lat != 0; 162 | } 163 | 164 | /** 165 | * alias for getCoordinates. 166 | * 167 | * 168 | * @return array 169 | */ 170 | public function getCoords() 171 | { 172 | return $this->getCoordinates(); 173 | } 174 | 175 | /** 176 | * This is a long string that describes the location. 177 | * 178 | * @param string $str 179 | */ 180 | public function setLocation($str) 181 | { 182 | if ($str != null) { 183 | $this->location = $str; 184 | } else { 185 | $this->location = ''; 186 | } 187 | } 188 | 189 | /** 190 | * @param string $address 191 | * 192 | * @return $this 193 | */ 194 | public function setAddress($address) 195 | { 196 | $this->address = $address; 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * @return string 203 | */ 204 | public function getAddress() 205 | { 206 | return $this->address; 207 | } 208 | 209 | /** 210 | * @param City|null $city 211 | * 212 | * @return $this 213 | */ 214 | public function setCity($city) 215 | { 216 | $this->city = $city; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * @return City|null 223 | */ 224 | public function getCity() 225 | { 226 | return $this->city; 227 | } 228 | 229 | /** 230 | * @param Country|null $country 231 | * 232 | * @return $this 233 | */ 234 | public function setCountry($country) 235 | { 236 | $this->country = $country; 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * @return Country|null 243 | */ 244 | public function getCountry() 245 | { 246 | return $this->country; 247 | } 248 | 249 | /** 250 | * @param float $lat 251 | * 252 | * @return $this 253 | */ 254 | public function setLat($lat) 255 | { 256 | if (empty($lat)) { 257 | $lat = 0; 258 | } 259 | $this->lat = $lat; 260 | 261 | return $this; 262 | } 263 | 264 | /** 265 | * @return float 266 | */ 267 | public function getLat() 268 | { 269 | return $this->lat; 270 | } 271 | 272 | /** 273 | * @param float $lng 274 | * 275 | * @return $this 276 | */ 277 | public function setLng($lng) 278 | { 279 | if (empty($lng)) { 280 | $lng = 0; 281 | } 282 | $this->lng = $lng; 283 | 284 | return $this; 285 | } 286 | 287 | /** 288 | * @return float 289 | */ 290 | public function getLng() 291 | { 292 | return $this->lng; 293 | } 294 | 295 | /** 296 | * @return string 297 | */ 298 | public function getLocation() 299 | { 300 | return $this->location; 301 | } 302 | 303 | /** 304 | * @param Municipality|null $municipality 305 | * 306 | * @return $this 307 | */ 308 | public function setMunicipality($municipality) 309 | { 310 | $this->municipality = $municipality; 311 | 312 | return $this; 313 | } 314 | 315 | /** 316 | * @return Municipality|null 317 | */ 318 | public function getMunicipality() 319 | { 320 | return $this->municipality; 321 | } 322 | 323 | /** 324 | * @param Region|null $region 325 | * 326 | * @return $this 327 | */ 328 | public function setRegion($region) 329 | { 330 | $this->region = $region; 331 | 332 | return $this; 333 | } 334 | 335 | /** 336 | * @return Region|null 337 | */ 338 | public function getRegion() 339 | { 340 | return $this->region; 341 | } 342 | 343 | /** 344 | * @param string $zipCode 345 | * 346 | * @return $this 347 | */ 348 | public function setZipCode($zipCode) 349 | { 350 | $this->zipCode = $zipCode; 351 | 352 | return $this; 353 | } 354 | 355 | /** 356 | * @return string 357 | */ 358 | public function getZipCode() 359 | { 360 | return $this->zipCode; 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /Form/Type/LocationType.php: -------------------------------------------------------------------------------- 1 | lm = $lm; 49 | $this->locationService = $ls; 50 | $this->geocoder = $geocoder; 51 | } 52 | 53 | /** 54 | * @param FormBuilderInterface $builder 55 | * @param array $options 56 | */ 57 | public function buildForm(FormBuilderInterface $builder, array $options) 58 | { 59 | $this->mergeActiveParts($options); 60 | $this->addDefaultAttributes($options); 61 | 62 | $this->addActiveComponents($builder, $options); 63 | } 64 | 65 | /** 66 | * Configures the options for this type. 67 | * 68 | * @param OptionsResolver $resolver The resolver for the options. 69 | */ 70 | public function configureOptions(OptionsResolver $resolver) 71 | { 72 | $resolver->setDefaults( 73 | array( 74 | 'data_class' => 'Happyr\LocationBundle\Entity\Location', 75 | 'error_bubbling' => true, 76 | 'components' => array(), 77 | 'geocodeLocationString' => true, 78 | //use this one if you want to set an attr on a field 79 | 'field' => array(), 80 | ) 81 | ); 82 | } 83 | 84 | /** 85 | * Set all parts to default false. 86 | * 87 | * @param array &$options 88 | */ 89 | protected function mergeActiveParts(array &$options) 90 | { 91 | $validComponents = array( 92 | 'address' => false, 93 | 'country' => false, 94 | 'city' => false, 95 | 'municipality' => false, 96 | 'region' => false, 97 | 'zipCode' => false, 98 | 'location' => false, 99 | ); 100 | 101 | $options['components'] = array_merge($validComponents, $options['components']); 102 | } 103 | 104 | /** 105 | * Here is the default configuration for each field. 106 | * 107 | * @param array &$options 108 | */ 109 | protected function addDefaultAttributes(array &$options) 110 | { 111 | $defaults = array( 112 | 'all' => array(), 113 | 'address' => array( 114 | 'trim' => true, 115 | 'label' => 'location.form.address', 116 | ), 117 | 'country' => array( 118 | 'preferred_choices' => array('SE'), 119 | 'label' => 'location.form.country', 120 | ), 121 | 'city' => array( 122 | 'trim' => true, 123 | 'label' => 'location.form.city', 124 | 'attr' => array( 125 | 'class' => 'google-autocomplete', 126 | 'data-google-autocomplete-type' => '(cities)', 127 | ), 128 | ), 129 | 'municipality' => array( 130 | 'trim' => true, 131 | 'label' => 'location.form.municipality', 132 | ), 133 | 'region' => array( 134 | 'trim' => true, 135 | 'label' => 'location.form.region', 136 | ), 137 | 'zipCode' => array( 138 | 'trim' => true, 139 | 'label' => 'location.form.zipCode', 140 | ), 141 | 'location' => array( 142 | 'label' => 'location.form.location', 143 | 'attr' => array( 144 | 'data-placeholder' => 'location.form.location', 145 | 'class' => 'google-autocomplete', 146 | 'data-google-autocomplete-type' => 'geocode', 147 | ), 148 | ), 149 | ); 150 | 151 | //merge defaults with the user options 152 | $options['field'] = array_replace_recursive($defaults, $options['field']); 153 | 154 | /* 155 | * Merge the default values 156 | */ 157 | if (count($options['field']['all']) > 0) { 158 | foreach ($options['components'] as $component => $active) { 159 | if (!$active) { 160 | continue; 161 | } 162 | 163 | $options['field'][$component] = array_replace_recursive( 164 | $options['field']['all'], 165 | $options['field'][$component] 166 | ); 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * Add all the active parts for this instance. 173 | * 174 | * @param FormBuilderInterface &$builder 175 | * @param array &$options 176 | */ 177 | protected function addActiveComponents(FormBuilderInterface &$builder, array &$options) 178 | { 179 | if ($options['components']['location']) { 180 | $this->addLocation($builder, $options); 181 | } else { 182 | $eventListener = new GeocodeLocationString($this->locationService, $this->geocoder); 183 | $builder->addEventListener(FormEvents::SUBMIT, array($eventListener, 'addCoordinates')); 184 | } 185 | 186 | if ($options['components']['country']) { 187 | $builder->add( 188 | $builder->create('country', CountryType::class, $options['field']['country']) 189 | ->addModelTransformer(new CountryTransformer()) 190 | ->addModelTransformer(new ComponentToStringTransformer($this->lm, 'Country')) 191 | ); 192 | } 193 | 194 | if ($options['components']['city']) { 195 | $builder->add( 196 | $builder->create('city', TextType::class, $options['field']['city']) 197 | ->addModelTransformer(new ComponentToStringTransformer($this->lm, 'City')) 198 | ); 199 | } 200 | 201 | if ($options['components']['municipality']) { 202 | $builder->add( 203 | $builder->create('municipality', TextType::class, $options['field']['municipality']) 204 | ->addModelTransformer(new ComponentToStringTransformer($this->lm, 'Municipality')) 205 | ); 206 | } 207 | 208 | if ($options['components']['address']) { 209 | $builder->add('address', TextType::class, $options['field']['address']); 210 | } 211 | 212 | if ($options['components']['zipCode']) { 213 | $builder->add('zipCode', TextType::class, $options['field']['zipCode']); 214 | } 215 | 216 | if ($options['components']['region']) { 217 | $builder->add( 218 | $builder->create('region', TextType::class, $options['field']['region']) 219 | ->addModelTransformer(new ComponentToStringTransformer($this->lm, 'Region')) 220 | ); 221 | } 222 | } 223 | 224 | /** 225 | * @param FormBuilderInterface $builder 226 | * @param array &$options 227 | */ 228 | protected function addLocation(FormBuilderInterface &$builder, array &$options) 229 | { 230 | $locationForm = $builder->create('location', TextType::class, $options['field']['location']); 231 | 232 | /* 233 | * if we should geocode the location string 234 | */ 235 | if ($options['geocodeLocationString'] == true && $this->geocoder != null) { 236 | 237 | // Geocode the input 238 | $eventListener = new GeocodeLocationString($this->locationService, $this->geocoder); 239 | $builder->addEventListener(FormEvents::SUBMIT, array($eventListener, 'geocodeLocation')); 240 | 241 | //Make sure we got a valid geocode 242 | $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { 243 | /** @var $data Location */ 244 | $data = $event->getData(); 245 | $form = $event->getForm(); 246 | 247 | if ($data === null) { 248 | return; 249 | } 250 | 251 | if (!empty($data->getLocation()) && $data->getLat() == 0 && $data->getLng() == 0) { 252 | $form->get('location')->addError(new FormError('happyr.location.geocode.failed')); 253 | } 254 | }); 255 | } 256 | 257 | $builder->add($locationForm); 258 | } 259 | 260 | /** 261 | * @return string 262 | */ 263 | public function getName() 264 | { 265 | return 'location'; 266 | } 267 | } 268 | --------------------------------------------------------------------------------