One place for hosting & domains

      Comment installer la pile Linux, Apache, MySQL, PHP (LAMP) sur un serveur Ubuntu 18.04


      Introduction

      Une pile “LAMP” est un groupe de logiciels libres qui sont généralement installés ensemble afin de permettre à un serveur d’héberger des sites internet dynamiques ainsi que des applications web. Le terme constitue généralement un acronyme qui représente le système d’exploitation Linux, le serveur web Apache. Les données du site sont hébergées sur une base de données MySQL, puis le contenu dynamique est traité par PHP.

      Dans ce guide, nous installerons une pile LAMP sur un serveur Ubuntu 18.04.

      Préalable

      Afin de compléter ce tutoriel, vous aurez besoin d’un serveur Ubuntu 18.04, un compte d’utilisateur non-root «sudo» activé, ainsi qu’un pare-feu de base. Cela peut être configuré en se référant à notre guide de configuration initial pour Ubuntu 18.04.

      Étape 1 — Installer Apache et mettre à jour le pare-feu.

      Le serveur Apache est parmi les serveurs web les plus populaires au monde. Il est bien documenté et a été utilisé abondamment pour la majeure partie de l’histoire de l’internet, ce qui en fait un bon choix par défaut pour héberger un site internet.

      Installer Apache à l’aide du gestionnaire de paquets d’Ubuntu, apt:

      • sudo apt update
      • sudo apt install apache2

      Puisqu’il s’agit d’une commande sudo, ces opérations sont exécutées avec les privilèges root. On vous demandera votre mot de passe d’utilisateur régulier afin de connaître vos intentions.

      Dès que vous aurez entré votre mot de passe, apt vous dira quels paquets il prévoit installer et combien d’espace il prendra sur votre disque dur. Entrez la touche Y et appuyer sur ENTER afin de continuer, et l’installation poursuivra.

      Ajuster votre pare-feu afin d’autoriser le trafic web.

      Ensuite, en présumant que vous avez suivi les instructions de configuration initiale du serveur et autorisé le pare-feu UFW, assurez-vous que votre pare-feu autorise le trafic HTTP et HTTPS. Vous pouvez vérifier que UFW possède un profil d’application pour Apache de la manière suivante :

      SortieOutput

      Available applications: Apache Apache Full Apache Secure OpenSSH

      Si vous regardez sur le profil Apache Full, il devrait y être indiqué qu’il permet le trafic aux ports 80 et 443 :

      • sudo ufw app info "Apache Full"

      SortieOutput

      Profile: Apache Full Title: Web Server (HTTP,HTTPS) Description: Apache v2 is the next generation of the omnipresent Apache web server. Ports: 80,443/tcp

      Autoriser le trafic HTTP et HTTPS entrant pour ce profil :

      • sudo ufw allow in "Apache Full"

      Vous pouvez immédiatement effectuer une vérification afin de valider que tout se soit déroulé comme prévu en visitant l’adresse IP de votre serveur public sur votre navigateur web (voir la note sous la rubrique suivante afin de voir quel est votre adresse IP, si vous ne disposez pas déjà de cette information) :

      http://your_server_ip
      

      Vous allez voir la page web par défaut du serveur Ubuntu 18.04 Apache qui s’affiche à titre d’information et à des fins d’essai. La page devrait ressembler à ceci :

      Ubuntu 18.04 Apache default

      Si vous voyez cette page, cela veut dire que votre serveur web est maintenant bien installé et qu’il est accessible à travers votre pare-feu.

      Si vous ne connaissez pas l’adresse IP publique de votre serveur, il existe différentes façons de la trouver. Normalement, il s’agit de l’adresse que vous utilisez afin de vous connecter à votre serveur via SSH.

      Il y a plusieurs façons d’effectuer cela à partir de la ligne de commande. D’abord, vous pouvez utiliser les outils iproute2 afin d’obtenir votre adresse IP en écrivant ceci :

      • ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's//.*$//'

      Vous devriez voir apparaître deux ou trois lignes de résultats. Ce sont tous des adresses correctes, par contre votre ordinateur ne sera peut-être qu’en mesure d’utiliser une de celles-ci, alors libre à vous d’essayer chacune d’entre elles.

      Une autre méthode consiste à utiliser l’outil curl pour contacter un correspondant externe afin qu’il vous informe comment « il » perçoit votre serveur. Cela s’effectue en demandant à un serveur spécifique quelle est votre adresse IP :

      • sudo apt install curl
      • curl http://icanhazip.com

      Indépendamment de la méthode que vous choisissez pour obtenir votre adresse IP, inscrivez-la sur la barre d’adresse de votre navigateur afin de voir la page par défaut d’Apache.

      Étape 2 — Installer MySQL

      Maintenant que votre serveur web est opérationnel, il est temps d’installer MySQL. MySQL est un système de gestion de base de données. Il sert essentiellement à organiser et donner l’accès aux bases de données au sein desquelles votre site pourra emmagasiner de l’information.

      Encore une fois, utiliser apt pour obtenir et installer ce logiciel.

      • sudo apt install mysql-server

      Note: Dans ce cas, vous n’avez pas besoin d’activer sudo apt update avant d’effectuer la commande. Cela est dû au fait que l’avez récemment activé dans les commandes ci-dessus pour installer Apache. Le paquet d’index sur votre ordinateur devrait déjà être à jour.

      Cette commande affichera également une liste des paquets qui seront installés, de même que l’espace qu’ils occuperont sur votre disque dur. Entrez la touche Y pour continuer.

      Lorsque l’installation est complétée, exécuter un script de sécurité simple qui est préinstallé avec MySQL et qui permettra de supprimer des défaillances dangereuses et puis de verrouiller l’accès à votre système de base de données. Démarrer le script interactif en exécutant la commande :

      • sudo mysql_secure_installation

      On vous demandera si vous désirez configurer le VALIDATE PASSWORD PLUGIN.

      Note: Activer cette fonctionnalité demeure une question de jugement. Lorsqu’activés, les mots de passe qui ne correspondent pas au critère spécifique seront refusés par MySQL avec un message d’erreur. Ceci engendrera des problèmes si vous utilisez un mot de passe faible conjointement à l’application qui configure automatiquement les identifiants d’utilisateurs MySQL, tels que les paquets d’Ubuntu pour phpMyAdmin. Il est sécuritaire de laisser la validation désactivée, mais vous devriez toujours utiliser un mot de passe robuste et unique pour les authentifications de base de données.

      Répondre Y pour oui, ou n’importe quelle autre commande pour continuer sans l’activer.

      VALIDATE PASSWORD PLUGIN peut être utilisé pour tester les mots de passe
      et améliorer la sécurité. Le système vérifie la sécurité du mot de passe
      et permet aux utilisateurs de définir uniquement les mots de passe qui sont
      assez bien sécurisés en demandant : Voulez-vous configurer le plug-in  - VALIDATE PASSWORD?
      Press y|Y for Yes, any other key for No:
      

      Si vous répondez “oui”, on vous demandera de choisir un niveau de validation de mot de passe. Gardez à l’esprit que si vous choisissez 2, pour le niveau le plus élevé, vous recevrez des messages d’erreur lorsque vous tenterez de définir un mot de passe qui ne contient pas de chiffre, de majuscule et de minuscule, de caractères spéciaux, ou qui s’inspire de mots communs du dictionnaire.

      Il existe trois niveaux de politique de validation du mot de passe:
      
      LOW    Length >= 8
      MEDIUM Length >= 8, numeric, mixed case, and special characters
      STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file
      
      Veuillez saisir 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
      

      Indépendamment de votre décision de configurer ou non le VALIDATE PASSWORD PLUGIN, votre serveur vous demandera de choisir et de confirmer un mot de passe pour l’utilisateur root MySQL. Il s’agit d’un compte administratif au sein de MySQL qui possède des privilèges accrus. Voyez-le comme étant similaire au compte root pour le serveur lui-même (bien que celui que vous êtes en train de configurer est un compte spécifique au sein de MySQL). Assurez-vous que vous de détenir un mot de passe robuste, unique, et de ne pas laisser l’espace vide.

      Si vous activez la validation du mot de passe, on vous indiquera la robustesse du mot de passe root que vous venez d’inscrire et votre serveur vous demandera si vous voulez le modifier. Si vous êtes satisfait de votre mot de passe, entrez N pour « non » au moment de faire le choix :

      Utiliser le mot de passe existant pour root.
      
      Force estimée du mot de passe : 100
      Changer le mot de passe pour root ? ((Press y|Y for Yes, any other key for No) : n
      

      Pour le reste des questions, entrez la touche Y et appuyer sur le bouton ENTER au moment de faire le choix. Cela supprimera certains utilisateurs anonymes ainsi que la base de données d’essai, désactivera les identifications root à distance et chargera les nouvelles règles afin que MySQL applique automatiquement les changements que vous venez d’apporter.

      Veuillez noter que pour les systèmes Ubuntu fonctionnant avec MySQL 5.7 (et les versions ultérieures), l’utilisateur root MySQL est configuré par défaut pour authentifier en utilisant le plugin auth_socket, plutôt qu’avec un mot de passe. Cela permet d’avoir une meilleure sécurité et ergonomie dans de nombreux cas, mais il peut également compliquer les choses lorsque vous devez autoriser l’ouverture d’un programme externe (ex : phpMyAdmin) afin d’accéder au serveur.

      Si vous préférez utiliser un mot de passe lorsque vous vous connectez au MySQL en tant que root, vous aurez besoin de changer le mode d’authentification de auth_socket à mysql_native_password. Pour y parvenir, ouvrez le prompt MySQL à partir de votre terminal :

      Ensuite, vérifier quel mode d’authentification chacun de vos comptes d’utilisateurs MySQL fait appel avec la commande suivante :

      • SELECT user,authentication_string,plugin,host FROM mysql.user;

      SortieOutput

      +------------------+-------------------------------------------+-----------------------+-----------+ | user | authentication_string | plugin | host | +------------------+-------------------------------------------+-----------------------+-----------+ | root | | auth_socket | localhost | | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost | +------------------+-------------------------------------------+-----------------------+-----------+ 4 rows in set (0.00 sec)

      Dans cet exemple, vous pouvez voir que l’utilisateur root s’authentifie effectivement en utilisant le plugin auth_socket. Afin de configurer le compte root pour l’identification avec mot de passe, exécuter la commande ALTER USER ci-dessous. Assurez-vous de modifier password pour un mot de passe robuste de votre choix :

      • ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

      Ensuite, exécuter FLUSH PRIVILEGES, qui envoie un message au serveur de renouveler les tableaux d’autorisations et de mettre en application vos nouvelles modifications :

      Vérifier encore les modes d’authentifications utilisées par chacun de vos utilisateurs afin de confirmer que le root ne s’authentifie plus en utilisant le plugin auth_socket :

      • SELECT user,authentication_string,plugin,host FROM mysql.user;

      SortieOutput

      +------------------+-------------------------------------------+-----------------------+-----------+ | user | authentication_string | plugin | host | +------------------+-------------------------------------------+-----------------------+-----------+ | root | *3636DACC8616D997782ADD0839F92C1571D6D78F | mysql_native_password | localhost | | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost | +------------------+-------------------------------------------+-----------------------+-----------+ 4 rows in set (0.00 sec)

      Vous pouvez voir dans cet exemple que l’utilisateur root de MySQL s’authentifie actuellement en utilisant un mot de passe. Une fois que vous aurez confirmé cela sur votre propre serveur, vous pouvez sortir du shell MySQL :

      À ce stade, votre système de base de données est maintenant programmé et vous pouvez poursuivre avec l’installation PHP, le dernier composant de la pile LAMP.

      Étape 3 — Installer PHP

      PHP est le composant de votre configuration qui sert de code de traitement pour afficher le contenu dynamique. Il peut exécuter des scripts, se connecter à vos bases de données MySQL afin d’obtenir de l’information et acheminer le contenu traité vers votre serveur web pour affichage.

      Encore une fois, utiliser le système apt pour installer PHP. De plus, inclure des paquets d’assistance cette fois-ci afin de permettre au code PHP de s’exécuter sous le serveur Apache et communiquer avec votre base de données MySQL :

      • sudo apt install php libapache2-mod-php php-mysql

      Cela devrait permettre d’installer PHP sans problème. Nous le mettrons à l’essai dans un moment.

      Dans la plupart des cas, vous allez vouloir modifier la façon dont Apache dessert les fichiers lorsqu’un répertoire est demandé. Actuellement, si un utilisateur demande un répertoire du serveur, Apache recherchera d’abord pour un fichier nommé index.html. Nous voulons dire au serveur web de donner priorité aux fichiers PHP, ainsi il faut exiger à Apache de regarder pour un fichier index.php en premier.

      Afin d’effectuer cela, entrez cette commande pour ouvrir le fichier dir.conf dans un éditeur de texte avec des privilèges root :

      • sudo nano /etc/apache2/mods-enabled/dir.conf

      Cela va ressembler à cela :

      /etc/apache2/mods-enabled/dir.conf

      <IfModule mod_dir.c>
          DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
      </IfModule>
      

      Déplacer le fichier d’index PHP (surligner ci-dessous) à la première position après la spécification DirectoryIndex, de la manière suivante :

      /etc/apache2/mods-enabled/dir.conf

      <IfModule mod_dir.c>
          DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
      </IfModule>
      

      Lorsque vous avez terminé, sauvegarder et fermer le fichier en appuyant sur CTRL+X. Confirmer la sauvegarde en entrant la touche Y et en appuyant sur ENTER afin de vérifier la localisation du fichier de sauvegarde.

      Ensuite, redémarrer le serveur web Apache afin que vos modifications prennent effet. Cela s’effectuera en inscrivant ceci :

      • sudo systemctl restart apache2

      Vous pouvez également vérifier le statut du service apache2 en utilisant la commande systemctl :

      • sudo systemctl status apache2

      Sample SortieOutput

      ● apache2.service - LSB: Apache2 web server Loaded: loaded (/etc/init.d/apache2; bad; vendor preset: enabled) Drop-In: /lib/systemd/system/apache2.service.d └─apache2-systemd.conf Active: active (running) since Tue 2018-04-23 14:28:43 EDT; 45s ago Docs: man:systemd-sysv-generator(8) Process: 13581 ExecStop=/etc/init.d/apache2 stop (code=exited, status=0/SUCCESS) Process: 13605 ExecStart=/etc/init.d/apache2 start (code=exited, status=0/SUCCESS) Tasks: 6 (limit: 512) CGroup: /system.slice/apache2.service ├─13623 /usr/sbin/apache2 -k start ├─13626 /usr/sbin/apache2 -k start ├─13627 /usr/sbin/apache2 -k start ├─13628 /usr/sbin/apache2 -k start ├─13629 /usr/sbin/apache2 -k start └─13630 /usr/sbin/apache2 -k start

      Afin d’améliorer le fonctionnement de PHP, vous avez l’option d’installer de modules supplémentaires. Pour voir les options disponibles de modules PHP et de bibliothèques, mener les résultats de apt search vers less, un récepteur qui vous laissera défiler à travers les résultats d’autres commandes :

      Utiliser les flèches afin de défiler de haut en bas, et appuyer sur Q pour quitter.

      Les résultats sont tous des composants optionnels que vous pouvez installer. Une courte description de chacun d’entre eux sera affichée :

      bandwidthd-pgsql/bionic 2.0.1+cvs20090917-10ubuntu1 amd64
        Tracks usage of TCP/IP and builds html files with graphs
      
      bluefish/bionic 2.2.10-1 amd64
        advanced Gtk+ text editor for web and software development
      
      cacti/bionic 1.1.38+ds1-1 all
        web interface for graphing of monitoring systems
      
      ganglia-webfrontend/bionic 3.6.1-3 all
        cluster monitoring toolkit - web front-end
      
      golang-github-unknwon-cae-dev/bionic 0.0~git20160715.0.c6aac99-4 all
        PHP-like Compression and Archive Extensions in Go
      
      haserl/bionic 0.9.35-2 amd64
        CGI scripting program for embedded environments
      
      kdevelop-php-docs/bionic 5.2.1-1ubuntu2 all
        transitional package for kdevelop-php
      
      kdevelop-php-docs-l10n/bionic 5.2.1-1ubuntu2 all
        transitional package for kdevelop-php-l10n
      …
      :
      

      Pour en savoir plus sur la fonctionnalité de chaque module, vous pouvez chercher sur internet pour plus d’informations à leur sujet. Une autre solution est de lire la longue description du paquet en tapant :

      Il y aura plusieurs résultats, incluant un champ intitulé Description qui présentera une explication plus détaillée de la fonctionnalité du module en question.

      Par exemple, afin de découvrir en quoi le module php-cli consiste, vous pouvez taper :

      En plus de la grande quantité d’autres informations, vous obtiendrez quelque chose qui ressemble à ceci :

      SortieOutput

      … Description: command-line interpreter for the PHP scripting language (default) This package provides the /usr/bin/php command interpreter, useful for testing PHP scripts from a shell or performing general shell scripting tasks. . PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML. . Ce paquet est un forfait de dépendances, qui dépend du défaut d'Ubuntu PHP version (currently 7.2). …

      Si, après votre recherche, vous décidez que vous voulez installer un paquet, vous pouvez le faire en utilisation la commande apt install, de la même manière que vous avez procédé pour l’autre logiciel.

      Si vous décidez que le php-cli est quelque chose dont vous avez besoin, vous pouvez taper cette commande :

      Si vous désirez installer plus d’un module, vous pouvez le faire en énumérant chacun d’entre eux, séparé d’un espace, suivant la commande apt install, comme ceci :

      • sudo apt install package1 package2 ...

      À ce stade, votre pile LAMP est installée et configurée. Cependant, avant de procéder à toute modification ou de déployer une application, il serait préférable de tester votre configuration PHP de manière proactive au cas où il y aurait un problème à traiter.

      Étape 4 — Tester le processus PHP sur votre serveur web

      Afin de tester si votre système est configuré correctement pour PHP, créer un script PHP de base appelé info.php. Afin qu’Apache puisse localiser ce fichier et le desservir correctement, il devra être sauvegardé dans un répertoire bien spécifique, qui se nomme le "web root".

      Sur Ubuntu 18.04, ce répertoire est situé au /var/www/html/. Créer le fichier à cet emplacement en exécutant :

      • sudo nano /var/www/html/info.php

      Cela ouvrira un fichier vierge. Ajouter le texte suivant, qui s’agit d’un code PHP valide, à l’intérieur du fichier :

      info.php

      <?php
      phpinfo();
      ?>
      

      Lorsque vous aurez terminé, sauvegarder et fermer le fichier.

      Vous pouvez maintenant tester si votre serveur web affiche correctement le contenu généré par ce script PHP. Pour le tester, visiter la page suivante dans votre navigateur web. Vous aurez encore besoin de votre adresse IP publique.

      L’adresse que vous devrez consulter est la suivante :

      http://your_server_ip/info.php
      

      La page que vous allez accéder devrait ressembler à ceci :
      Ubuntu 18.04 default PHP info

      Cette page présente de l’information de base sur votre serveur du point de vue de PHP. Elle est pratique pour le débogage et afin d’assurer que vos réglages sont appliqués correctement.

      Si vous voyez cette page sur votre navigateur, alors votre PHP fonctionne correctement.

      Vous devriez supprimer ce fichier après la mise en essai parce qu’il pourrait en fait donner de l’information sur votre serveur à des utilisateurs non autorisés. Pour ce faire, exécuter la commande suivante :

      • sudo rm /var/www/html/info.php

      Vous pourrez toujours recréer cette page si vous avez besoin d’accéder à cette information plus tard.

      Conclusion

      Maintenant que votre pile LAMP est installée, vous avez plusieurs choix quant à ce que vous pouvez faire par la suite. Essentiellement, vous venez d’installer une plateforme qui vous permettra d’installer la plupart des types de site internet et de logiciels web sur votre serveur.

      Dans l’immédiat, vous devriez vous assurer que les connexions à votre serveur web sont sécurisées, en les faisant fonctionner via HTTPS. L’option la plus simple dans ce cas est de utiliser Let's Encrypt afin de sécuriser votre site avec un certificat TLS/SSL gratuit.

      D’autres options populaires demeurent (notez que pour le moment ces tutoriels sont seulement disponibles en anglais) :



      Source link

      How To Deploy a PHP Application with Kubernetes on Ubuntu 16.04


      The author selected the Open Internet/Free Speech to receive a donation as part of the Write for DOnations program.

      Introduction

      Kubernetes is an open source container orchestration system. It allows you to create, update, and scale containers without worrying about downtime.

      To run a PHP application, Nginx acts as a proxy to PHP-FPM. Containerizing this setup in a single container can be a cumbersome process, but Kubernetes will help manage both services in separate containers. Using Kubernetes will allow you to keep your containers reusable and swappable, and you will not have to rebuild your container image every time there’s a new version of Nginx or PHP.

      In this tutorial, you will deploy a PHP 7 application on a Kubernetes cluster with Nginx and PHP-FPM running in separate containers. You will also learn how to keep your configuration files and application code outside the container image using DigitalOcean’s Block Storage system. This approach will allow you to reuse the Nginx image for any application that needs a web/proxy server by passing a configuration volume, rather than rebuilding the image.

      Prerequisites

      Step 1 — Creating the PHP-FPM and Nginx Services

      In this step, you will create the PHP-FPM and Nginx services. A service allows access to a set of pods from within the cluster. Services within a cluster can communicate directly through their names, without the need for IP addresses. The PHP-FPM service will allow access to the PHP-FPM pods, while the Nginx service will allow access to the Nginx pods.

      Since Nginx pods will proxy the PHP-FPM pods, you will need to tell the service how to find them. Instead of using IP addresses, you will take advantage of Kubernetes’ automatic service discovery to use human-readable names to route requests to the appropriate service.

      To create the service, you will create an object definition file. Every Kubernetes object definition is a YAML file that contains at least the following items:

      • apiVersion: The version of the Kubernetes API that the definition belongs to.
      • kind: The Kubernetes object this file represents. For example, a pod or service.
      • metadata: This contains the name of the object along with any labels that you may wish to apply to it.
      • spec: This contains a specific configuration depending on the kind of object you are creating, such as the container image or the ports on which the container will be accessible from.

      First you will create a directory to hold your Kubernetes object definitions.

      SSH to your master node and create the definitions directory that will hold your Kubernetes object definitions.

      Navigate to the newly created definitions directory:

      Make your PHP-FPM service by creating a php_service.yaml file:

      Set kind as Service to specify that this object is a service:

      php_service.yaml

      ...
      apiVersion: v1
      kind: Service
      

      Name the service php since it will provide access to PHP-FPM:

      php_service.yaml

      ...
      metadata:
        name: php
      

      You will logically group different objects with labels. In this tutorial, you will use labels to group the objects into "tiers", such as frontend or backend. The PHP pods will run behind this service, so you will label it as tier: backend.

      php_service.yaml

      ...
        labels:
          tier: backend
      

      A service determines which pods to access by using selector labels. A pod that matches these labels will be serviced, independent of whether the pod was created before or after the service. You will add labels for your pods later in the tutorial.

      Use the tier: backend label to assign the pod into the backend tier. You will also add the app: php label to specify that this pod runs PHP. Add these two labels after the metadata section.

      php_service.yaml

      ...
      spec:
        selector:
          app: php
          tier: backend
      

      Next, specify the port used to access this service. You will use port 9000 in this tutorial. Add it to the php_service.yaml file under spec:

      php_service.yaml

      ...
        ports:
          - protocol: TCP
            port: 9000
      

      Your completed php_service.yaml file will look like this:

      php_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: php
        labels:
          tier: backend
      spec:
        selector:
          app: php
          tier: backend
        ports:
        - protocol: TCP
          port: 9000
      

      Hit CTRL + o to save the file, and then CTRL + x to exit nano.

      Now that you've created the object definition for your service, to run the service you will use the kubectl apply command along with the -f argument and specify your php_service.yaml file.

      Create your service:

      • kubectl apply -f php_service.yaml

      This output confirms the service creation:

      Output

      service/php created

      Verify that your service is running:

      You will see your PHP-FPM service running:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10m php ClusterIP 10.100.59.238 <none> 9000/TCP 5m

      There are various service types that Kubernetes supports. Your php service uses the default service type, ClusterIP. This service type assigns an internal IP and makes the service reachable only from within the cluster.

      Now that the PHP-FPM service is ready, you will create the Nginx service. Create and open a new file called nginx_service.yaml with the editor:

      This service will target Nginx pods, so you will name it nginx. You will also add a tier: backend label as it belongs in the backend tier:

      nginx_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: nginx
        labels:
          tier: backend
      

      Similar to the php service, target the pods with the selector labels app: nginx and tier: backend. Make this service accessible on port 80, the default HTTP port.

      nginx_service.yaml

      ...
      spec:
        selector:
          app: nginx
          tier: backend
        ports:
        - protocol: TCP
          port: 80
      

      The Nginx service will be publicly accessible to the internet from your Droplet's public IP address. your_public_ip can be found from your DigitalOcean Cloud Panel. Under spec.externalIPs, add:

      nginx_service.yaml

      ...
      spec:
        externalIPs:
        - your_public_ip
      

      Your nginx_service.yaml file will look like this:

      nginx_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: nginx
        labels:
          tier: backend
      spec:
        selector:
          app: nginx
          tier: backend
        ports:
        - protocol: TCP
          port: 80
        externalIPs:
        - your_public_ip    
      

      Save and close the file. Create the Nginx service:

      • kubectl apply -f nginx_service.yaml

      You will see the following output when the service is running:

      Output

      service/nginx created

      You can view all running services by executing:

      You will see both the PHP-FPM and Nginx services listed in the output:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13m nginx ClusterIP 10.102.160.47 your_public_ip 80/TCP 50s php ClusterIP 10.100.59.238 <none> 9000/TCP 8m

      Please note, if you want to delete a service you can run:

      • kubectl delete svc/service_name

      Now that you've created your PHP-FPM and Nginx services, you will need to specify where to store your application code and configuration files.

      Step 2 — Installing the DigitalOcean Storage Plug-In

      Kubernetes provides different storage plug-ins that can create the storage space for your environment. In this step, you will install the DigitalOcean storage plug-in to create block storage on DigitalOcean. Once the installation is complete, it will add a storage class named do-block-storage that you will use to create your block storage.

      You will first configure a Kubernetes Secret object to store your DigitalOcean API token. Secret objects are used to share sensitive information, like SSH keys and passwords, with other Kubernetes objects within the same namespace. Namespaces provide a way to logically separate your Kubernetes objects.

      Open a file named secret.yaml with the editor:

      You will name your Secret object digitalocean and add it to the kube-system namespace. The kube-system namespace is the default namespace for Kubernetes’ internal services and is also used by the DigitalOcean storage plug-in to launch various components.

      secret.yaml

      apiVersion: v1
      kind: Secret
      metadata:
        name: digitalocean
        namespace: kube-system
      

      Instead of a spec key, a Secret uses a data or stringData key to hold the required information. The data parameter holds base64 encoded data that is automatically decoded when retrieved. The stringData parameter holds non-encoded data that is automatically encoded during creation or updates, and does not output the data when retrieving Secrets. You will use stringData in this tutorial for convenience.

      Add the access-token as stringData:

      secret.yaml

      ...
      stringData:
        access-token: your-api-token
      

      Save and exit the file.

      Your secret.yaml file will look like this:

      secret.yaml

      apiVersion: v1
      kind: Secret
      metadata:
        name: digitalocean
        namespace: kube-system
      stringData:
        access-token: your-api-token
      

      Create the secret:

      • kubectl apply -f secret.yaml

      You will see this output upon Secret creation:

      Output

      secret/digitalocean created

      You can view the secret with the following command:

      • kubectl -n kube-system get secret digitalocean

      The output will look similar to this:

      Output

      NAME TYPE DATA AGE digitalocean Opaque 1 41s

      The Opaque type means that this Secret is read-only, which is standard for stringData Secrets. You can read more about it on the Secret design spec. The DATA field shows the number of items stored in this Secret. In this case, it shows 1 because you have a single key stored.

      Now that your Secret is in place, install the DigitalOcean block storage plug-in:

      • kubectl apply -f https://raw.githubusercontent.com/digitalocean/csi-digitalocean/master/deploy/kubernetes/releases/csi-digitalocean-v0.3.0.yaml

      You will see output similar to the following:

      Output

      storageclass.storage.k8s.io/do-block-storage created serviceaccount/csi-attacher created clusterrole.rbac.authorization.k8s.io/external-attacher-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-attacher-role created service/csi-attacher-doplug-in created statefulset.apps/csi-attacher-doplug-in created serviceaccount/csi-provisioner created clusterrole.rbac.authorization.k8s.io/external-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-provisioner-role created service/csi-provisioner-doplug-in created statefulset.apps/csi-provisioner-doplug-in created serviceaccount/csi-doplug-in created clusterrole.rbac.authorization.k8s.io/csi-doplug-in created clusterrolebinding.rbac.authorization.k8s.io/csi-doplug-in created daemonset.apps/csi-doplug-in created

      Now that you have installed the DigitalOcean storage plug-in, you can create block storage to hold your application code and configuration files.

      Step 3 — Creating the Persistent Volume

      With your Secret in place and the block storage plug-in installed, you are now ready to create your Persistent Volume. A Persistent Volume, or PV, is block storage of a specified size that lives independently of a pod’s life cycle. Using a Persistent Volume will allow you to manage or update your pods without worrying about losing your application code. A Persistent Volume is accessed by using a PersistentVolumeClaim, or PVC, which mounts the PV at the required path.

      Open a file named code_volume.yaml with your editor:

      Name the PVC code by adding the following parameters and values to your file:

      code_volume.yaml

      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: code
      

      The spec for a PVC contains the following items:

      • accessModes which vary by the use case. These are:
        • ReadWriteOnce – mounts the volume as read-write by a single node
        • ReadOnlyMany – mounts the volume as read-only by many nodes
        • ReadWriteMany – mounts the volume as read-write by many nodes
      • resources – the storage space that you require

      DigitalOcean block storage is only mounted to a single node, so you will set the accessModes to ReadWriteOnce. This tutorial will guide you through adding a small amount of application code, so 1GB will be plenty in this use case. If you plan on storing a larger amount of code or data on the volume, you can modify the storage parameter to fit your requirements. You can increase the amount of storage after volume creation, but shrinking the disk is not supported.

      code_volume.yaml

      ...
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
      

      Next, specify the storage class that Kubernetes will use to provision the volumes. You will use the do-block-storage class created by the DigitalOcean block storage plug-in.

      code_volume.yaml

      ...
        storageClassName: do-block-storage
      

      Your code_volume.yaml file will look like this:

      code_volume.yaml

      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: code
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: do-block-storage
      

      Save and exit the file.

      Create the code PersistentVolumeClaim using kubectl:

      • kubectl apply -f code_volume.yaml

      The following output tells you that the object was successfully created, and you are ready to mount your 1GB PVC as a volume.

      Output

      persistentvolumeclaim/code created

      To view available Persistent Volumes (PV):

      You will see your PV listed:

      Output

      NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-ca4df10f-ab8c-11e8-b89d-12331aa95b13 1Gi RWO Delete Bound default/code do-block-storage 2m

      The fields above are an overview of your configuration file, except for Reclaim Policy and Status. The Reclaim Policy defines what is done with the PV after the PVC accessing it is deleted. Delete removes the PV from Kubernetes as well as the DigitalOcean infrastructure. You can learn more about the Reclaim Policy and Status from the Kubernetes PV documentation.

      You've successfully created a Persistent Volume using the DigitalOcean block storage plug-in. Now that your Persistent Volume is ready, you will create your pods using a Deployment.

      Step 4 — Creating a PHP-FPM Deployment

      In this step, you will learn how to use a Deployment to create your PHP-FPM pod. Deployments provide a uniform way to create, update, and manage pods by using ReplicaSets. If an update does not work as expected, a Deployment will automatically rollback its pods to a previous image.

      The Deployment spec.selector key will list the labels of the pods it will manage. It will also use the template key to create the required pods.

      This step will also introduce the use of Init Containers. Init Containers run one or more commands before the regular containers specified under the pod's template key. In this tutorial, your Init Container will fetch a sample index.php file from GitHub Gist using wget. These are the contents of the sample file:

      index.php

      <?php
      echo phpinfo(); 
      

      To create your Deployment, open a new file called php_deployment.yaml with your editor:

      This Deployment will manage your PHP-FPM pods, so you will name the Deployment object php. The pods belong to the backend tier, so you will group the Deployment into this group by using the tier: backend label:

      php_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: php
        labels:
          tier: backend
      

      For the Deployment spec, you will specify how many copies of this pod to create by using the replicas parameter. The number of replicas will vary depending on your needs and available resources. You will create one replica in this tutorial:

      php_deployment.yaml

      ...
      spec:
        replicas: 1
      

      This Deployment will manage pods that match the app: php and tier: backend labels. Under selector key add:

      php_deployment.yaml

      ...
        selector:
          matchLabels:
            app: php
            tier: backend
      

      Next, the Deployment spec requires the template for your pod's object definition. This template will define specifications to create the pod from. First, you will add the labels that were specified for the php service selectors and the Deployment’s matchLabels. Add app: php and tier: backend under template.metadata.labels:

      php_deployment.yaml

      ...
        template:
          metadata:
            labels:
              app: php
              tier: backend
      

      A pod can have multiple containers and volumes, but each will need a name. You can selectively mount volumes to a container by specifying a mount path for each volume.

      First, specify the volumes that your containers will access. You created a PVC named code to hold your application code, so name this volume code as well. Under spec.template.spec.volumes, add the following:

      php_deployment.yaml

      ...
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
      

      Next, specify the container you want to run in this pod. You can find various images on the Docker store, but in this tutorial you will use the php:7-fpm image.

      Under spec.template.spec.containers, add the following:

      php_deployment.yaml

      ...
            containers:
            - name: php
              image: php:7-fpm
      

      Next, you will mount the volumes that the container requires access to. This container will run your PHP code, so it will need access to the code volume. You will also use mountPath to specify /code as the mount point.

      Under spec.template.spec.containers.volumeMounts, add:

      php_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      Now that you have mounted your volume, you need to get your application code on the volume. You may have previously used FTP/SFTP or cloned the code over an SSH connection to accomplish this, but this step will show you how to copy the code using an Init Container.

      Depending on the complexity of your setup process, you can either use a single initContainer to run a script that builds your application, or you can use one initContainer per command. Make sure that the volumes are mounted to the initContainer.

      In this tutorial, you will use a single Init Container with busybox to download the code. busybox is a small image that contains the wget utility that you will use to accomplish this.

      Under spec.template.spec, add your initContainer and specify the busybox image:

      php_deployment.yaml

      ...
            initContainers:
            - name: install
              image: busybox
      

      Your Init Container will need access to the code volume so that it can download the code in that location. Under spec.template.spec.initContainers, mount the volume code at the /code path:

      php_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      Each Init Container needs to run a command. Your Init Container will use wget to download the code from Github into the /code working directory. The -O option gives the downloaded file a name, and you will name this file index.php.

      Note: Be sure to trust the code you’re pulling. Before pulling it to your server, inspect the source code to ensure you are comfortable with what the code does.

      Under the install container in spec.template.spec.initContainers, add these lines:

      php_deployment.yaml

      ...
              command:
              - wget
              - "-O"
              - "/code/index.php"
              - https://raw.githubusercontent.com/do-community/php-kubernetes/master/index.php
      

      Your completed php_deployment.yaml file will look like this:

      php_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: php
        labels:
          tier: backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: php
            tier: backend
        template:
          metadata:
            labels:
              app: php
              tier: backend
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
            containers:
            - name: php
              image: php:7-fpm
              volumeMounts:
              - name: code
                mountPath: /code
            initContainers:
            - name: install
              image: busybox
              volumeMounts:
              - name: code
                mountPath: /code
              command:
              - wget
              - "-O"
              - "/code/index.php"
              - https://raw.githubusercontent.com/do-community/php-kubernetes/master/index.php
      

      Save the file and exit the editor.

      Create the PHP-FPM Deployment with kubectl:

      • kubectl apply -f php_deployment.yaml

      You will see the following output upon Deployment creation:

      Output

      deployment.apps/php created

      To summarize, this Deployment will start by downloading the specified images. It will then request the PersistentVolume from your PersistentVolumeClaim and serially run your initContainers. Once complete, the containers will run and mount the volumes to the specified mount point. Once all of these steps are complete, your pod will be up and running.

      You can view your Deployment by running:

      You will see the output:

      Output

      NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE php 1 1 1 0 19s

      This output can help you understand the current state of the Deployment. A Deployment is one of the controllers that maintains a desired state. The template you created specifies that the DESIRED state will have 1 replicas of the pod named php. The CURRENT field indicates how many replicas are running, so this should match the DESIRED state. You can read about the remaining fields in the Kubernetes Deployments documentation.

      You can view the pods that this Deployment started with the following command:

      The output of this command varies depending on how much time has passed since creating the Deployment. If you run it shortly after creation, the output will likely look like this:

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-bf8zd 0/1 Init:0/1 0 9s

      The columns represent the following information:

      • Ready: The number of replicas running this pod.
      • Status: The status of the pod. Init indicates that the Init Containers are running. In this output, 0 out of 1 Init Containers have finished running.
      • Restarts: How many times this process has restarted to start the pod. This number will increase if any of your Init Containers fail. The Deployment will restart it until it reaches a desired state.

      Depending on the complexity of your startup scripts, it can take a couple of minutes for the status to change to podInitializing:

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-lkwgn 0/1 podInitializing 0 39s

      This means the Init Containers have finished and the containers are initializing. If you run the command when all of the containers are running, you will see the pod status change to Running.

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-lkwgn 1/1 Running 0 1m

      You now see that your pod is running successfully. If your pod doesn't start, you can debug with the following commands:

      • View detailed information of a pod:
      • kubectl describe pods pod-name
      • View logs generated by a pod:
      • View logs for a specific container in a pod:
      • kubectl logs pod-name container-name

      Your application code is mounted and the PHP-FPM service is now ready to handle connections. You can now create your Nginx Deployment.

      Step 5 — Creating the Nginx Deployment

      In this step, you will use a ConfigMap to configure Nginx. A ConfigMap holds your configuration in a key-value format that you can reference in other Kubernetes object definitions. This approach will grant you the flexibility to reuse or swap the image with a different Nginx version if needed. Updating the ConfigMap will automatically replicate the changes to any pod mounting it.

      Create a nginx_configMap.yaml file for your ConfigMap with your editor:

      • nano nginx_configMap.yaml

      Name the ConfigMap nginx-config and group it into the tier: backend micro-service:

      nginx_configMap.yaml

      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: nginx-config
        labels:
          tier: backend
      

      Next, you will add the data for the ConfigMap. Name the key config and add the contents of your Nginx configuration file as the value. You can use the example Nginx configuration from this tutorial.

      Because Kubernetes can route requests to the appropriate host for a service, you can enter the name of your PHP-FPM service in the fastcgi_pass parameter instead of its IP address. Add the following to your nginx_configMap.yaml file:

      nginx_configMap.yaml

      ...
      data:
        config : |
          server {
            index index.php index.html;
            error_log  /var/log/nginx/error.log;
            access_log /var/log/nginx/access.log;
            root ^/code^;
      
            location / {
                try_files $uri $uri/ /index.php?$query_string;
            }
      
            location ~ .php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_pass php:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
              }
          }
      

      Your nginx_configMap.yaml file will look like this:

      nginx_configMap.yaml

      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: nginx-config
        labels:
          tier: backend
      data:
        config : |
          server {
            index index.php index.html;
            error_log  /var/log/nginx/error.log;
            access_log /var/log/nginx/access.log;
            root /code;
      
            location / {
                try_files $uri $uri/ /index.php?$query_string;
            }
      
            location ~ .php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_pass php:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
              }
          }
      

      Save the file and exit the editor.

      Create the ConfigMap:

      • kubectl apply -f nginx_configMap.yaml

      You will see the following output:

      Output

      configmap/nginx-config created

      You've finished creating your ConfigMap and can now build your Nginx Deployment.

      Start by opening a new nginx_deployment.yaml file in the editor:

      • nano nginx_deployment.yaml

      Name the Deployment nginx and add the label tier: backend:

      nginx_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx
        labels:
          tier: backend
      

      Specify that you want one replicas in the Deployment spec. This Deployment will manage pods with labels app: nginx and tier: backend. Add the following parameters and values:

      nginx_deployment.yaml

      ...
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nginx
            tier: backend
      

      Next, add the pod template. You need to use the same labels that you added for the Deployment selector.matchLabels. Add the following:

      nginx_deployment.yaml

      ...
        template:
          metadata:
            labels:
              app: nginx
              tier: backend
      

      Give Nginx access to the code PVC that you created earlier. Under spec.template.spec.volumes, add:

      nginx_deployment.yaml

      ...
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
      

      Pods can mount a ConfigMap as a volume. Specifying a file name and key will create a file with its value as the content. To use the ConfigMap, set path to name of the file that will hold the contents of the key. You want to create a file site.conf from the key config. Under spec.template.spec.volumes, add the following:

      nginx_deployment.yaml

      ...
            - name: config
              configMap:
                name: nginx-config
                items:
                - key: config
                  path: site.conf
      

      Warning: If a file is not specified, the contents of the key will replace the mountPath of the volume. This means that if a path is not explicitly specified, you will lose all content in the destination folder.

      Next, you will specify the image to create your pod from. This tutorial will use the nginx:1.7.9 image for stability, but you can find other Nginx images on the Docker store. Also, make Nginx available on the port 80. Under spec.template.spec add:

      nginx_deployment.yaml

      ...
            containers:
            - name: nginx
              image: nginx:1.7.9
              ports:
              - containerPort: 80
      

      Nginx and PHP-FPM need to access the file at the same path, so mount the code volume at /code:

      nginx_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      The nginx:1.7.9 image will automatically load any configuration files under the /etc/nginx/conf.d directory. Mounting the config volume in this directory will create the file /etc/nginx/conf.d/site.conf. Under volumeMounts add the following:

      nginx_deployment.yaml

      ...
              - name: config
                mountPath: /etc/nginx/conf.d
      

      Your nginx_deployment.yaml file will look like this:

      nginx_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx
        labels:
          tier: backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nginx
            tier: backend
        template:
          metadata:
            labels:
              app: nginx
              tier: backend
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
            - name: config
              configMap:
                name: nginx-config
                items:
                - key: config
                  path: site.conf
            containers:
            - name: nginx
              image: nginx:1.7.9
              ports:
              - containerPort: 80
              volumeMounts:
              - name: code
                mountPath: /code
              - name: config
                mountPath: /etc/nginx/conf.d
      

      Save the file and exit the editor.

      Create the Nginx Deployment:

      • kubectl apply -f nginx_deployment.yaml

      The following output indicates that your Deployment is now created:

      Output

      deployment.apps/nginx created

      List your Deployments with this command:

      You will see the Nginx and PHP-FPM Deployments:

      Output

      NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 1 1 1 0 16s php 1 1 1 1 7m

      List the pods managed by both of the Deployments:

      You will see the pods that are running:

      Output

      NAME READY STATUS RESTARTS AGE nginx-7bf5476b6f-zppml 1/1 Running 0 32s php-86d59fd666-lkwgn 1/1 Running 0 7m

      Now that all of the Kubernetes objects are active, you can visit the Nginx service on your browser.

      List the running services:

      • kubectl get services -o wide

      Get the External IP for your Nginx service:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 39m <none> nginx ClusterIP 10.102.160.47 your_public_ip 80/TCP 27m app=nginx,tier=backend php ClusterIP 10.100.59.238 <none> 9000/TCP 34m app=php,tier=backend

      On your browser, visit your server by typing in http://your_public_ip. You will see the output of php_info() and have confirmed that your Kubernetes services are up and running.

      Conclusion

      In this guide, you containerized the PHP-FPM and Nginx services so that you can manage them independently. This approach will not only improve the scalability of your project as you grow, but will also allow you to efficiently use resources as well. You also stored your application code on a volume so that you can easily update your services in the future.



      Source link

      How to Generate a Short and Unique Digital Address for Any Location Using AngularJS and PHP


      Introduction

      Postal addresses are usually lengthy and sometimes difficult to remember. There are a number of scenarios where a shorter address would be desirable. For example, having the ability to send a short address consisting of only a couple of characters could ensure faster delivery of emergency ambulance services. Pieter Geelen and Harold Goddijn developed the Mapcode system in 2001 to make it easy to create a short-form address for any physical address in the world.

      In this tutorial, you will develop a web app that uses the Google Maps API to generate a short digital address for any address of your choice. You will do this by cloning the base code for this app from GitHub and then adding code to it that will make it fully functional. This app will also be able to retrieve the original physical address from a given mapcode.

      Prerequisites

      In order to complete this tutorial, you will need the following:

      Step 1 — Getting a Google API Key

      In this tutorial, you will use JavaScript to create an interface to Google Maps. Google assigns API keys to enable developers to use the JavaScript API on Google Maps, which you will need to obtain and add to your web app’s code.

      To get your own API key, head to Google’s “Get API Key” page. Click on the GET STARTED button in Step 1, and a pop-up will open as shown in the following image:

      Select Maps by clicking the check box and hit CONTINUE. If you aren’t already logged into a Google account, you will be asked to do so. Then, the window will ask you to provide a name for the project, which can be anything you’d like:

      Following this, it will ask you to enter your billing information. Note that Google provides API keys as part of a free trial, but it requires you to set up and enable billing in order retrieve them.

      After entering this information, your API key will appear on the screen. Copy and store it in a location where you can easily retrieve it, as you will need to add it to your project code later on.

      After obtaining your API key, you can begin building the foundation of your application by creating a MySQL database.

      Step 2 — Creating the Database

      The web application described in this tutorial accepts an address from the user and generates a mapcode for it along with the latitude and longitude of the specified location. You will store this data in a MySQL database so that you can retrieve it later on just by entering the respective digital address.

      Begin by opening the MySQL shell and authenticating with your password:

      At the prompt, create a database called digitaladdress using the following command:

      • CREATE DATABASE IF NOT EXISTS `digitaladdress`;

      Next, select this new database so that you can create a table within it:

      After selecting the digitaladdress database, create a table called locations within it to store the physical address, its longitude, latitude, and the mapcode that your application will create from this data. Run the following CREATE TABLE statement to create the locations table within the database:

      • CREATE TABLE `locations` (
      • `digitaladdress` varchar(50) DEFAULT NULL,
      • `state` varchar(30) DEFAULT NULL,
      • `zip` varchar(30) DEFAULT NULL,
      • `street` varchar(30) DEFAULT NULL,
      • `town` varchar(30) DEFAULT NULL,
      • `house` varchar(30) DEFAULT NULL,
      • `latitude` varchar(30) DEFAULT NULL,
      • `longitude` varchar(30) DEFAULT NULL,
      • KEY `digitaladdress` (`digitaladdress`)
      • );

      This table has eight columns: digitaladdress, state, zip, street, town, house, latitude, and longitude. The first column, digitaladdress, is indexed using the KEY command. Indexes in MySQL function similarly to how they work in an encyclopedia or other reference work. Any time you or your application issue a query containing a WHERE statement, MySQL reads every entry in each column, row-by-row, which can become an extremely resource-intensive process as your table accumulates more and more entries. Indexing a column like this takes the data from the column and stores it alphabetically in a separate location, which means that MySQL will not have to look through every row in the table. It only has to find the data you're looking for in the index and then jump to the corresponding row in the table.

      After adding this table, exit the MySQL prompt:

      With your database and table set up and your Google Maps API key in hand, you're ready to create the project itself.

      Step 3 — Creating the Project

      As mentioned in the introduction, we will clone the base code for this project from GitHub and then add some extra code to make the application functional. The reason for this, rather than walking you through the process of creating each file and adding all the code yourself, is to speed up the process of getting the app running. It will also allow us to focus on adding and understanding the code that allows the app to communicate with both the Google Maps and Mapcode APIs.

      You can find the skeleton code for the full project on this GitHub project page. Use the following git command to clone the project to your server:

      • git clone https://github.com/do-community/digiaddress.git

      This will create a new folder called digiaddress in your home directory. Move this directory to your server's web root. If you followed the LAMP stack tutorial linked in the prerequisites, this will be the /var/www/html directory:

      • sudo mv digiaddress/ /var/www/html/

      This project contains several PHP and JS files to which you'll add some code later on in this tutorial. To view the directory structure, first install the tree package using apt:

      Then run the tree command with the digiaddress directory given as an argument:

      • tree /var/www/html/digiaddress/

      Output

      digiaddress/ ├── README.md ├── db.php ├── fetchaddress.php ├── findaddress.php ├── generateDigitalAddress.php ├── geoimplement.php ├── index.php └── js ├── createDigitialAddressApp.js └── findAddressApp.js

      You can see from this output that the project consists of six PHP files and two JavaScript files. Together, these files create the application's two main functionalities: creating a mapcode from a physical address, and decoding a mapcode to retrieve the original physical address. The following files enable the first functionality:

      • index.php
      • geoimplement.php
      • generateDigitialAddress.php
      • db.php
      • createDigitialAddressApp.js

      The index.php file contains the code for the application's user interface (UI), which consists of a form where users can enter a physical address. The index.php file calls the geoimplement.php file any time a user submits the form. geoimplement.php makes a call to the Google Maps API and passes the address along to it. The Google server then responds with a JSON containing the specified address's information, including its latitude and longitude. This information is then passed to the generateDigitalAddress.php file which calls the Mapcode API to obtain a mapcode for the given location, as specified by its latitude and longitude. The resulting mapcode, along with the latitude, longitude, and the physical address, are then stored in the database that you created in Step 2. db.php acts as a helper for this operation. The createDigitalAddressApp.js file performs a number of operations that control the UX elements seen in the app, including setting a marker and boundary rectangle on the Google Maps interface.

      The remaining three files enable the second function of the application — that is, retrieving a physical address from a given mapcode:

      • findaddress.php
      • fetchaddress.php
      • findAddressApp.js

      The findaddress.php file defines the application UI, which is distinct from the one defined in index.php. The application accepts a previously-generated mapcode as an input and displays the corresponding physical address stored in the database. Whenever a user submits this form, findaddress.php sends a call to fetchaddress.php which then retrieves the respective mapcode from the database. The findAddressApp.js file contains the helper code for setting a marker and a boundary rectangle on the Google Maps interface.

      Test the installation by visiting http://your_server_ip/digiaddress in your browser, making sure to change your_server_ip to reflect your server's IP address.

      Note: If you don't know your server's IP address, you can run the following curl command. This command will print the page content of icanhazip.com, a website that shows the IP address of the machine accessing it:

      • curl http://icanhazip.com

      Once there, you will see this heading at the top of your browser window:

      Generate Digital Address
      

      This confirms that you have correctly downloaded the project files. With that, let us proceed with the development of the app's primary function: generating a mapcode.

      Step 4 — Developing the Application's UI

      While the boilerplate code for the application interface is included in the files you downloaded in the previous step, you still need to make a few changes and additions to some of these files to make the application functional and engaging for users. We will get started with updating the code to develop the application's UI.

      Open the index.php file using your preferred editor. Here, we'll use nano:

      • nano /var/www/html/digiaddress/index.php

      Look for the following line of code:

      /var/www/html/digiaddress/index.php

      . . .
      <script async defer src="https://maps.googleapis.com/maps/api/js?key=<YOUR KEY>"></script>
      . . .
      

      Replace <YOUR KEY> with the Google API key you obtained in Step 1. After adding your API key, the line should look similar to this:

      /var/www/html/digiaddress/index.php

      . . .
      <script async defer src="https://maps.googleapis.com/maps/api/js?key=ExampleAPIKeyH2vITfv1eIHbfka9ym634Esw7u"></script>
      . . .
      

      Next, find the following comment in the index.php file:

      /var/www/html/digiaddress/index.php

      . . .
                  <!-- add form code here -->
      . . .
      

      We'll add a few dozen lines of code below this comment which will create a form where users can enter the address of a physical location which the application will use to generate a mapcode. Under this comment, add the following highlighted code which creates a title called Enter Address at the top of the form:

      /var/www/html/digiaddress/index.php

      . . .
                  <!-- add form code here -->
      
                  <div class="form-border spacing-top">
                      <div class="card-header" style="background:#cc0001; color:#ffff">
                          <h5>Enter Address</h5>
                      </div>
                      <div class="extra-padding">
      . . .
      

      Below this, add the following HTML code. This creates a form with five text fields (along with their appropriate labels) where users will input their information:

      /var/www/html/digiaddress/index.php

                      . . .
                      <form>
                              <div class="form-group input-group-sm">
                                  <label for="state">State</label>
                                  <input type="text" class="form-control rounded-0 textbox-border" id="state"
                                         placeholder="" ng-model="address.state"/>
                              </div>
                              <div class="form-group input-group-sm">
                                  <label for="zip" class="animated-label">Zip</label>
                                  <input type="text" class="form-control rounded-0 textbox-depth textbox-border"
                                         id="zip" ng-model="address.zip" disabled="disabled"/>
                              </div>
                              <div class="form-group input-group-sm">
                                  <label for="town">Town</label>
                                  <input type="text" class="form-control rounded-0 textbox-border"
                                         id="town" ng-model="address.town" disabled="disabled"/>
                              </div>
                              <div class="form-group input-group-sm">
                                  <label for="street">Street</label>
                                  <input type="text" class="form-control rounded-0 textbox-border" id="street"
                                         placeholder="" ng-model="address.street" disabled="disabled"/>
                              </div>
                              <div class="form-group input-group-sm">
                                  <label for="house">House</label>
                                  <input type="text" class="form-control rounded-0 textbox-border" id="house"
                                         placeholder="" ng-model="address.house" disabled="disabled"/>
                              </div>
                       . . .
      

      Below the form code, add the following lines. These create two hidden controls which pass along the latitude and longitude information derived from any address submitted through the form:

      /var/www/html/digiaddress/index.php

                                  . . .
                                  <div class="form-group input-group-sm">
                                      <input type="hidden" ng-model="address.lat"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <input type="hidden" ng-model="address.long"/>
                                  </div>
                                  . . .
      

      Lastly, close out this section by adding the following code. This creates a Generate button which will allow users to submit the form:

      /var/www/html/digiaddress/index.php

                                  . . .
                                  <button type="submit" disabled="disabled" class="btn btn-color btn-block rounded-0" id="generate"
                                          style="color:#ffff;background-color: #cc0001;">Generate
                                  </button>
                          </form>
                      </div>
                  </div>
              . . .
      

      After adding these elements, this section of the file should match this:

      /var/www/html/digiaddress/index.php

      . . .
                  <!-- add form code here -->
      
                  <div class="form-border spacing-top">
                      <div class="card-header" style="background:#cc0001; color:#ffff">
                          <h5>Enter Address</h5>
                      </div>
                      <div class="extra-padding">
                          <form>    
                                  <div class="form-group input-group-sm">
                                      <label for="state">State</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="state"
                                             placeholder="" ng-model="address.state"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="zip" class="animated-label">Zip</label>
                                      <input type="text" class="form-control rounded-0 textbox-depth textbox-border"
                                             id="zip" ng-model="address.zip" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="town">Town</label>
                                      <input type="text" class="form-control rounded-0 textbox-border "
                                             id="town" ng-model="address.town" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="street">Street</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="street"
                                             placeholder="" ng-model="address.street" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="house">House</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="house"
                                             placeholder="" ng-model="address.house" disabled="disabled"/>
                                  </div>
      
                                  <div class="form-group input-group-sm">
                                      <input type="hidden" ng-model="address.lat"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <input type="hidden" ng-model="address.long"/>
                                  </div>
                                  <button type="submit" disabled="disabled" class="btn btn-color btn-block rounded-0" id="generate"
                                          style="color:#ffff;background-color: #cc0001;">Generate
                                  </button>
                          </form>
                      </div>
                  </div>
                  <br>
              </div>
      
              <!-- add google map control -->
                          . . .
      

      Save the file by pressing CTRL+O then ENTER, and then visit the application in your browser again:

      http://your_server_ip/digiaddress
      

      You will see the newly-added form fields and Generate button, and the application should look like this:

      At this point, if you enter address information into the form and try clicking the Generate button, nothing will happen. We will add the mapcode generation functionality later on, but let's first focus on making this page more visually engaging by adding a map which users can interact with.

      Step 5 — Adding Google Maps Controls

      When maps are displayed on a website through the Google Maps JavaScript API, they contain user interface features that allow visitors to interact with the map they see. These features are known as controls. We will continue editing the index.php file to add Google Maps controls to this app and, when finished, users will be able to view a map next to the input form, drag it around to view different locations, zoom in and out, and switch between Google's map, satellite, and street views.

      Find the following comment within the index.php file:

      /var/www/html/digiaddress/index.php

      . . .
      <!-- add google map control -->
      . . .
      

      Add the following highlighted code below this comment:

      /var/www/html/digiaddress/index.php

      . . .
              <!-- add google map control -->
      
              <div class="col-sm-8 map-align" ng-init="initMap()">
                  <div id="map" class="extra-padding" style="height: 100%;
                  margin-bottom: 15px;"></div>
                  <label id="geocoordinates" ng-show="latlng" ng-model="lt"></label><br/>
                  <label id="geoaddress" ng-show="address" ng-model="padd"></label>
                  </div>
              </div>
      . . .
      

      Save the file, then visit the application in your browser again. You will see the following:

      As you can see, we've successfully added a map to the application. You can drag the map around to focus on different locations, zoom in and out, and switch between the map, satellite, and street views. Looking back at the code you just added, notice that we've also added two label controls that will display the geocoordinates and the physical address that were entered on the form:

      /var/www/html/digiaddress/index.php

                  . . .
                  <label id="geocoordinates" ng-show="latlng" ng-model="lt"></label><br/>
                  <label id="geoaddress" ng-show="address" ng-model="padd"></label>
                  . . .
      

      Visit the application again in your browser and enter the name of a state in the first field. When you move your text cursor to the next field, the latitude and longitude labels don't appear, nor does the location shown on the map change to reflect the information you've entered. Let's enable these behaviors.

      Step 6 — Adding Event Listeners

      Adding interactive elements to an application can help to keep its users engaged. We will implement a few interactive behaviors in this application through the use of event listeners.

      An event is any action that takes place on a web page. Events can be something done by a user or by the browser itself. Examples of common events are:

      • Clicking an HTML button
      • Changing the content of an input field
      • Changing the focus from one page element to another

      An event listener is a directive that tells a program to take a certain action when a specific event takes place. In AngularJS, event listeners are defined with directives that generally follow this format:

      ng-event_type=expression
      

      In this step, we will add an event listener that helps to process the information entered by users into a mapcode whenever they submit the form. We will also add a couple more event listeners that will make the application more interactive. Specifically, we'll use these listeners to change the location shown in the application map, place a marker, and draw a rectangle around the location as users enter information into the form. We'll add these event listeners to index.php, so open that file up again if you've closed it:

      • nano /var/www/html/digiaddress/index.php

      Scroll down to the first batch of code we added, and find the block that begins with <form>. It will look like this:

      /var/www/html/digiaddress/index.php

                      . . .
                          <form>
                                  <div class="form-group input-group-sm">
                                      <label for="state">State</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="state"
                                             placeholder="" ng-model="address.state"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="zip" class="animated-label">Zip</label>
                                      <input type="text" class="form-control rounded-0 textbox-depth textbox-border"
                                             id="zip" ng-model="address.zip" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="town">Town</label>
                                      <input type="text" class="form-control rounded-0 textbox-border"
                                             id="town" ng-model="address.town" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="street">Street</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="street"
                                             placeholder="" ng-model="address.street" disabled="disabled"/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="house">House</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="house"
                                             placeholder="" ng-model="address.house" disabled="disabled"/>
                                  </div>
                          </form>
      . . .
      

      To begin, add the following highlighted event listener to the opening <form> tag. This code tells the app to call the processForm function whenever a user submits information through the form. processForm is defined in the createDigitalAddressApp.js file, and serves as a helper function that sends the information submitted by users to the appropriate files which then process it into a mapcode. We will take a closer look at this function in Step 7:

      /var/www/html/digiaddress/index.php

                      . . .
                          <form ng-submit="processForm()" class="custom-form">
                                  <div class="form-group input-group-sm">
                                      <label for="state">State</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="state"
                                             placeholder="" ng-model="address.state"
                                  </div>
                      . . .
      

      Next, continue editing this block by adding a couple blur event listeners. A blur event occurs when a given page element loses focus. Add the following highlighted lines to the form block's input tags. These lines tell the application to call the geocodeAddress function when a user's focus shifts away from the respective form fields we created in Step 4. Note that you must also delete the slashes and greater-than signs (/>) that close out each input tag. Failing to do so will prevent the app from registering the blur events correctly:

      /var/www/html/digiaddress/index.php

                      . . .
                      <form ng-submit="processForm()" class="custom-form">
                                  <div class="form-group input-group-sm">
                                      <label for="state">State</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="state"
                                             placeholder="" ng-model="address.state"
                                             ng-blur="geocodeAddress(address,'state')" required=""/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="zip" class="animated-label">Zip</label>
                                      <input type="text" class="form-control rounded-0 textbox-depth textbox-border"
                                             id="zip" ng-model="address.zip" disabled="disabled"
                                             ng-blur="geocodeAddress(address,'zip')" required=""/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="town">Town</label>
                                      <input type="text" class="form-control rounded-0 textbox-border"
                                             id="town" ng-model="address.town" disabled="disabled"
                                             ng-blur="geocodeAddress(address,'town')" required=""/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="street">Street</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="street"
                                             placeholder="" ng-model="address.street" disabled="disabled"
                                             ng-blur="geocodeAddress(address,'street')" required=""/>
                                  </div>
                                  <div class="form-group input-group-sm">
                                      <label for="house">House</label>
                                      <input type="text" class="form-control rounded-0 textbox-border" id="house"
                                             placeholder="" ng-model="address.house" disabled="disabled"
                                             ng-blur="geocodeAddress(address,'house')" required=""/>
                                  </div>
      . . .
      

      The first of these new lines — ng-blur="geocodeAddress(address,'state')" required=""/> — translates to "When the user's focus shifts away from the 'state' field, call the geocodeAddress function." The other new lines also call geocodeAddress, albeit when the user's focus shifts away from their respective fields.

      As with the processForm function, geocodeAddress is declared in the createDigitalAddressApp.js file, but there isn't yet any code in that file that defines it. We will complete this function so that it places a marker and draws a rectangle on the application map after these blur events occur to reflect the information entered into the form. We'll also add some code that takes the address information and processes it into a mapcode.

      Save and close the index.php file (press CTRL+X, Y, then ENTER) and then open thecreateDigitalAddressApp.js file:

      • nano /var/www/html/digiaddress/js/createDigitalAddressApp.js

      In this file, find the following line:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      $scope.geocodeAddress = function (address, field) {
      . . .
      

      This line is where we declare the geocodeAddress function. A few lines below this, we declare a variable named fullAddress which constructs a human-readable mailing address from the information entered by a user into the application's form fields. This is done through a series of if statements:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      var fullAddress = "";
      
          if (address ['house']) {
              angular.element(document.getElementById('generate'))[0].disabled = false;
                  fullAddress = address ['house'] + ",";
                      }
          if (address ['town']) {
              angular.element(document.getElementById('street'))[0].disabled = false;
                  fullAddress = fullAddress + address ['town'] + ",";
          }
          if (address ['street']) {
              angular.element(document.getElementById('house'))[0].disabled = false;
                  fullAddress = fullAddress + address ['street'] + ",";
          }
          if (address ['state']) {
              angular.element(document.getElementById('zip'))[0].disabled = false;
                  fullAddress = fullAddress + address ['state'] + " ";
          }
          if (address ['zip']) {
              angular.element(document.getElementById('town'))[0].disabled = false;
                  fullAddress = fullAddress + address ['zip'];
          }
      . . .
      

      Directly after these lines is the following comment:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      // add code for locating the address on Google maps
      . . .
      

      Underneath this comment, add the following line which checks whether fullAddress is any value other than null:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                      . . .
                      if (fullAddress !== "") {
                      . . .
      

      Add the following code below this line. This code submits the information entered into the form to the geoimplement.php file using the HTTP POST method if fullAddress is not null:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                          . . .
                          $http({
                              method: 'POST',
                              url: 'geoimplement.php',
                              data: {address: fullAddress},
                              headers: {'Content-Type': 'application/x-www-form-urlencoded'}
      
                          }).then(function successCallback(results) {
                          . . .
      

      Next, add the following line which checks whether the PHP call was returned successfully:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                              . . .
                              if (results.data !== "false") {
                              . . .
      

      If the PHP call was successfully returned, we'll be able to process the result. Add the following line, which removes any boundary rectangle that may have been previously drawn on the map by calling the removeRectangle function, which is defined at the top of the createDigitalAddressApp.js file:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                                  . . .
                                  removeRectangle();
                                  . . .
      

      Under the removeRectangle(); line, add the following four lines which will create a marker pointing to the new location on the map control:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                                  . . .
                                  new google.maps.Marker({
                                      map: locationMap,
                                      position: results.data.geometry.location
                                  });
                                  . . .
      

      Then add the following code, which obtains the latitude and longitude information from the result and displays it with the two HTML labels we created in the index.php file in Step 5:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                                  . . .
                                  lat = results.data.geometry.location.lat;
                                  lng = results.data.geometry.location.lng;
      
                                  $scope.address.lat = lat;
                                  $scope.address.lng = lng;
      
                                  geoCoordLabel = angular.element(document.querySelector('#geocoordinates'));
                                  geoCoordLabel.html("Geo Coordinate: " + lat + "," + lng);
      
                                  geoAddressLabel = angular.element(document.querySelector('#geoaddress'));
                                  geoAddressLabel.html("Geo Address: " + fullAddress);
      
                                  $scope.latlng = true;
                                  . . .
      

      Lastly, below these lines, add the following content. This code creates a viewport which marks a new boundary rectangle on the map:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                                  . . .
                                  if (results.data.geometry.viewport) {
      
                                      rectangle = new google.maps.Rectangle({
                                          strokeColor: '#FF0000',
                                          strokeOpacity: 0.8,
                                          strokeWeight: 0.5,
                                          fillColor: '#FF0000',
                                          fillOpacity: 0.35,
                                          map: locationMap,
                                          bounds: {
                                              north: results.data.geometry.viewport.northeast.lat,
                                              south: results.data.geometry.viewport.southwest.lat,
                                              east: results.data.geometry.viewport.northeast.lng,
                                              west: results.data.geometry.viewport.southwest.lng
                                          }
                                      });
      
                                      var googleBounds = new google.maps.LatLngBounds(results.data.geometry.viewport.southwest, results.data.geometry.viewport.northeast);
      
                                      locationMap.setCenter(new google.maps.LatLng(lat, lng));
                                      locationMap.fitBounds(googleBounds);
                                  }
                              } else {
                                  errorLabel = angular.element(document.querySelector('#lt'));
                                  errorLabel.html("Place not found.");
                                  $scope.latlng = true;
                                  removeRectangle();
                              }
      
                          }, function errorCallback(results) {
                             console.log(results);
                          });
                      }
                      . . .
      

      After adding this content, this section of the file will look like this:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

                      . . .
                      // add code for locating the address on Google maps
                      if (fullAddress !== "") {
                          $http({
                              method: 'POST',
                              url: 'geoimplement.php',
                              data: {address: fullAddress},
                              headers: {'Content-Type': 'application/x-www-form-urlencoded'}
      
                          }).then(function successCallback(results) {
      
                              if (results.data !== "false") {
                                  removeRectangle();
      
                                  new google.maps.Marker({
                                      map: locationMap,
                                      position: results.data.geometry.location
                                  });
      
                                  lat = results.data.geometry.location.lat;
                                  lng = results.data.geometry.location.lng;
      
                                  $scope.address.lat = lat;
                                  $scope.address.lng = lng;
      
                                  geoCoordLabel = angular.element(document.querySelector('#geocoordinates'));
                                  geoCoordLabel.html("Geo Coordinate: " + lat + "," + lng);
      
                                  geoAddressLabel = angular.element(document.querySelector('#geoaddress'));
                                  geoAddressLabel.html("Geo Address: " + fullAddress);
      
                                  $scope.latlng = true;
      
                                  if (results.data.geometry.viewport) {
      
                                      rectangle = new google.maps.Rectangle({
                                          strokeColor: '#FF0000',
                                          strokeOpacity: 0.8,
                                          strokeWeight: 0.5,
                                          fillColor: '#FF0000',
                                          fillOpacity: 0.35,
                                          map: locationMap,
                                          bounds: {
                                              north: results.data.geometry.viewport.northeast.lat,
                                              south: results.data.geometry.viewport.southwest.lat,
                                              east: results.data.geometry.viewport.northeast.lng,
                                              west: results.data.geometry.viewport.southwest.lng
                                          }
                                      });
      
                                      var googleBounds = new google.maps.LatLngBounds(results.data.geometry.viewport.southwest, results.data.geometry.viewport.northeast);
      
                                      locationMap.setCenter(new google.maps.LatLng(lat, lng));
                                      locationMap.fitBounds(googleBounds);
                                  }
                              } else {
                                  errorLabel = angular.element(document.querySelector('#lt'));
                                  errorLabel.html("Place not found.");
                                  $scope.latlng = true;
                                  removeRectangle();
                              }
      
                          }, function errorCallback(results) {
                             console.log(results);
                          });
                      }
                      . . .
      

      Save the file, but keep it open for now. If you were to visit the application in your browser again, you wouldn't see any new changes to its appearance or behavior. Likewise, if you were to enter an address and click on the Generate button, the application still would not generate or display a mapcode. This is because we must still edit a few files before the mapcode functionality will work. Let's continue to make these changes, and also take a closer look at how these mapcodes are generated.

      Step 7 — Understanding Mapcode Generation

      While still looking at the createDigitalAddressApp.js file, scroll past the section of code that you added in the previous step to find the code that takes the information submitted through the form and process it into a unique mapcode. Whenever a user clicks the Generate button, the code within the index.php file submits the form and calls the processForm function, which is defined here in createDigitalAddressApp.js:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      $scope.processForm = function () {
      . . .
      

      processForm then makes an HTTP POST to the generateDigitalAddress.php file:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      $http({
          method: 'POST',
          url: 'generateDigitalAddress.php',
          data: $scope.address,
          headers: {'Content-Type': 'application/x-www-form-urlencoded'}
      }).then(function (response) {
      . . .
      

      The Stichting Mapcode Foundation provides the API that generates mapcodes from physical addresses as a free web service. To understand how this call to the Mapcode web service works, close createDigitalAddressApp.js and open the generateDigitialAddress.php file:

      • nano /var/www/html/digiaddress/generateDigitalAddress.php

      At the top of the file, you'll see the following:

      /var/www/html/digiaddress/generateDigitalAddress.php

      <?php
      include("db.php");
      . . .
      

      The line reading include("db.php"); tells PHP to include all the text, code, and markup from the db.php file within the generateDigitalAddress.php file. db.php holds the login credentials for the MySQL database you created in Step 2, and by including it within generateDigitalAddress.php, we can add any address information submitted through the form to the database.

      Below this include statement are a few more lines that obtain the latitude and longitude information based on the request submitted by createDigitalAddressApp.js:

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      $data = json_decode(file_get_contents("php://input"));
      $lat = $data->lat;
      $long = $data->lng;
      . . .
      

      Look for the following comment in generateDigitalAddress.php file.

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      // call to mapcode web service
      . . .
      

      Add the following line of code below this comment. This code makes a call the Mapcode API, sending lat and long as parameters.

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      // call to mapcode web service
      $digitaldata = file_get_contents("https://api.mapcode.com/mapcode/codes/".$lat.",".$long."?include=territory,alphabet&allowLog=true&client=web");
      . . .
      

      The web service returns the JSON data which was assigned to digitaldata, and the following statement decodes that JSON:

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      $digitalAddress["status"] = json_decode($digitaldata, TRUE)['local']['territory']." ".json_decode($digitaldata, TRUE)['local']['mapcode'];
      . . .
      

      This returns a mapcode for the user-specified location. The following lines then store this information in the database:

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      $obj = new databaseConnection();
      
      $conn = $obj->dbConnect();
      
      $obj->insertLocation($conn, $digitalAddress["status"],$data->state,$data->zip,$data->street,$data->town,$data->house,$lat,$long);
      . . .
      

      Then, the final line echoes the mapcode back to the caller function:

      /var/www/html/digiaddress/generateDigitalAddress.php

      . . .
      echo json_encode($digitalAddress);
      

      Save and close this file, then reopen createDigitalAddressApp.js again:

      • nano /var/www/html/digiaddress/js/createDigitalAddressApp.js

      When a mapcode has been retrieved successfully, the following lines in the createDigitalAddressApp.js file displays it to the user in a dialog box:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      . . .
      digiAddress = response.data.status;
      . . .
      $('#digitalAddressDialog').modal('show');
      . . .
      

      Although you did add a new line of code to generateDigitalAddress.php, you still won't see any functional changes when you visit and interact with the app in your browser. This is because you've not yet added your Google API key to the geoimplement.php file, which makes the actual call to the Google Maps API.

      Step 8 — Enabling Calls to the Google Maps API

      This application depends on the Google Maps API to translate a physical address into the appropriate latitude and longitude coordinates. These are then passed on to the Mapcode API which uses them to generate a mapcode. Consequently, if the application is unable to communicate with the Google Maps API to generate the location's latitude and longitude, any attempt to generate a mapcode will fail.

      Recall from Step 6 where, after constructing the address data, we passed the result along via an HTTP POST request in the createDigitalAddressApp.js file:

      /var/www/html/digiaddress/js/createDigitalAddressApp.js

      $http({
          method: 'POST',
          url: 'geoimplement.php',
          data: {address: fullAddress},
          headers: {'Content-Type': 'application/x-www-form-urlencoded'}
      }).then(function successCallback(results) {
      

      This code block sends the address data entered by a user to the geoimplement.php file which contains the code that calls the Google Maps API. Go ahead and open this file:

      • nano /var/www/html/digiaddress/geoimplement.php

      You'll see that it first decodes the address that was received through the POST request:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      $data=json_decode(file_get_contents("php://input"));
      . . .
      

      It then passes the address field of the input data to a geocode function which returns the geographic information on the address:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      $result = geocode($data->address);
      . . .
      

      The result is then echoed back to the caller:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      echo json_encode($result);
      . . .
      

      The geocode function encodes the address and passes it on to the Google Maps API, along with your application key:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      // url encode the address
      $address = urlencode($address);
      
      // google map geocode api url
      $url = "https://maps.googleapis.com/maps/api/geocode/json?address={$address}&key=<YOUR KEY>";
      . . .
      

      Before scrolling on, go ahead and add your API key to the line under the // google map geocode api url comment:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      // google map geocode api url
      $url = "https://maps.googleapis.com/maps/api/geocode/json?address={$address}&key=ExampleAPIKeyH2vITfv1eIHbfka9ym634Esw7u";
      . . .
      

      After sending the call to the Google Maps API, the response is decoded and its value is returned by the function:

      /var/www/html/digiaddress/geoimplement.php

      . . .
      // get the json response
      $resp_json = file_get_contents($url);
      
      // decode the json
      $resp = json_decode($resp_json, true);
      
      if ($resp['status'] == 'OK') {
          return $resp['results'][0];
      } else {
          return false;
      }
      . . .
      

      Save this file, and visit your application once again. Input US-NY in the state field and then hit TAB to change the input focus to the next field. You will see the following output:

      Notice that the geocoordinates and physical address that you entered in the form appear underneath the map. This makes the application feel much more engaging and interactive.

      Note: When it comes to abbreviations for place names, Mapcode uses the ISO 3166 standard. This means that it may not interpret some commonly-used abbreviations as expected. For example, if you'd like to generate a Mapcode for an address in Louisiana and you enter LA, the map will jump to Los Angeles, California (rather than the state of Louisiana).

      You can avoid confusion with US postal abbreviations by preceding them with US-. In the context of this Louisiana example, you would enter US-LA.

      To learn more about how Mapcode uses this standard, check out the Territories and standard codes reference page.

      Despite this improvement to how the application displays locations on the map, the app still isn't fully functional. The last step you need to take before you can generate a mapcode is to edit the db.php file to allow the application to access your database.

      Step 9 — Adding Database Credentials and Testing Mapcode Generation

      Recall that this application stores every address entered into the form — along with its latitude, longitude, and mapcode — in the database you created in Step 2. This is made possible by the code within the db.php file, which stores your database credentials and allows the application to access the locations table within it.

      As a final step to enable the mapcode generation functionality, open the db.php file for editing:

      • nano /var/www/html/digiaddress/db.php

      Near the top of this file, find the line that begins with $pass. This line submits your MySQL login credentials in order to allow the application to access your database. Replace your_password with your root MySQL user's password:

      /var/www/html/digiaddress/db.php

      . . .
              $username = "root";
              $pass = "your_password";
      . . .
      

      That is the last change you need to make in order to generate a mapcode from a physical address. Save and close the file, then go ahead and refresh the application in your browser once again. Enter in an address of your choice and click the Generate button. The output will look similar to this:

      At this stage, you have completed your application and you can now generate a short digital address for any physical location in the world. Feel free to experiment with different addresses, and note that the address you enter does not necessarily need to be within the United States.

      Your final task is to enable this app's second functionality: retrieving an address from the database using its respective mapcode.

      Step 10 — Retrieving a Physical Address

      Now that you're able to generate a mapcode from a given physical address, your final step is to retrieve the original physical address, as derived from the mapcode. To accomplish this, we will develop a PHP user interface, shown here:

      The code for this UI is available in the findaddress.php file. As the UI defined within this file is fairly similar to the UI we covered earlier in Step 4, we will not look too closely at all the details of how it works. We will, however, go through these three files to explain generally how they function.

      In order to enable the address retrieval functionality, you'll need to add your Google API key to the findaddress.php file, so open it up with your preferred editor:

      • nano /var/www/html/digiaddress/findaddress.php

      Near the bottom of the file, find the line that begins with <script async defer src=. It will look like this:

      /var/www/html/digiaddress/findaddress.php

      <script async defer src="https://maps.googleapis.com/maps/api/js?key=<YOUR KEY>"></script>
      

      Replace <YOUR KEY> with your Google API key as you've done in the previous steps, then save the file. Before closing it, though, let's take a quick look to see how these files work together.

      When a user submits the form it triggers a submit event, and an event listener calls the fetchadd function:

      /var/www/html/digiaddress/findaddress.php

      . . .
      <form ng-submit="fetchadd()" class="custom-form">
      . . .
      

      The fetchadd function sends the digital address to fetchaddress.php with a POST request:

      /var/www/html/digiaddress/js/findAddressApp.js

      . . .
      $http({
          method : 'POST',
          url : 'fetchaddress.php',
          data : {digiaddress: $scope.digiaddress}
      }).then(function(response){
      . . .
      

      If the POST is successful, the function returns a JSON response. The following line parses this response:

      /var/www/html/digiaddress/js/findAddressApp.js

      . . .
      var jsonlatlng = JSON.parse(response.data.latlng);
      . . .
      

      The next lines set the marker on the map:

      /var/www/html/digiaddress/js/findAddressApp.js

      . . .
      marker = new google.maps.Marker({
          position: new google.maps.LatLng(jsonlatlng.latitude, jsonlatlng.longitude),
              map: locationMap
      });
      . . .
      

      And the following prints the geocoordinates and the physical address:

      /var/www/html/digiaddress/js/findAddressApp.js

      . . .
      geoCoordLabel = angular.element(document.querySelector('#geocoordinates'));
      geoCoordLabel.html("Geo Coordinate: "+ jsonlatlng.latitude +","+ jsonlatlng.longitude);
      
      geoAddressLabel = angular.element(document.querySelector('#geoaddress'));
      geoAddressLabel.html("Geo Address: " + jsonlatlng.house +","+ jsonlatlng.town +","+ jsonlatlng.street +","+ jsonlatlng.state + " " + jsonlatlng.zip );
      . . .
      

      Visit this application in your browser by going to the following link:

      http://your_server_ip/digiaddress/findaddress.php
      

      Test it out by entering in the mapcode you obtained earlier. The following figure shows a typical output:

      With that, your application is finished. You can now create a unique mapcode for any location in the world, and then use that mapcode to retrieve the location's physical address.

      Conclusion

      In this tutorial you used the Google Maps API to pin a location and gets its longitude, latitude information. This information is used to generate a unique and short digital address using Mapcode API. There are a number of practical use cases for mapcodes, ranging from emergency services to archaeological surveying. The Stichting Mapcode Foundation lists several such use cases.

      Acknowledgements

      Many thanks to Dinesh Karpe and Sayli Patil for developing the entire project code.



      Source link