{"id":141,"date":"2013-09-11T01:44:59","date_gmt":"2013-09-10T23:44:59","guid":{"rendered":"http:\/\/benoit-alessandroni.fr\/?p=141"},"modified":"2016-01-20T15:07:02","modified_gmt":"2016-01-20T14:07:02","slug":"parlons-technique","status":"publish","type":"post","link":"https:\/\/www.balessan.me\/technique\/parlons-technique\/","title":{"rendered":"Parlons technique !"},"content":{"rendered":"<p>Au (re)lancement de ce site, j&#8217;avais annonc\u00e9 que je ne parlerais pas de technique sur cet espace&#8230; Malheureusement, \u00e9tant passionn\u00e9 par le d\u00e9veloppement WEB et \u00e9tant actuellement en p\u00e9riode de forte productivit\u00e9 sur des projets personnels motivants, l&#8217;envie m&#8217;a repris de d\u00e9tailler un peu mes projets et le travail associ\u00e9 ! Alors, voici un premier post technique !<\/p>\n<h2>Le projet<\/h2>\n<p>Si vous me connaissez, ou si vous avez regard\u00e9 la page <a href=\".\/resume\">C.V.<\/a>, vous avez s\u00fbrement entendu parler de l&#8217;activit\u00e9 de mon p\u00e8re (qui est apiculteur) et du site associ\u00e9 : <a href=\"http:\/\/www.didapi.fr\">Didapi.fr<\/a>. J&#8217;ai d\u00e9velopp\u00e9 ce site en tant que cadeau d&#8217;anniversaire de mon p\u00e8re, pour ses 50 ans. Le temps ayant pass\u00e9 depuis, j&#8217;ai d\u00e9cid\u00e9 lors de mon retour du Canada de me remettre \u00e0 l&#8217;ouvrage, afin de faire de son site un espace d&#8217;application de mes connaissance en d\u00e9veloppement Front-end et de test de technologies.<\/p>\n<p><!--more--><\/p>\n<h2>L&#8217;objectif<\/h2>\n<p>Le r\u00e9sultat souhait\u00e9 est une combinaison d&#8217;un ensemble de pages statiques, constitutives de la partie vitrine du site, et d&#8217;un syst\u00e8me d&#8217;administration personnalis\u00e9, taill\u00e9 en fonction des besoins de mon p\u00e8re, et lui permettant de g\u00e9rer l&#8217;\u00e9volution de son cheptel apicole et de suivre les r\u00e9coltes, les traitements et les transhumances.<\/p>\n<p>Pour la partie vitrine, rien de plus simple: quelques pages HTML, am\u00e9lior\u00e9es par Twitter Bootstrap, saupoudr\u00e9es de PHP et de JavaScript pour quelques fonctionnalit\u00e9s funky.<\/p>\n<p>Pour la partie administration, j&#8217;ai longuement r\u00e9fl\u00e9chis. Mes conclusions ont \u00e9t\u00e9 qu&#8217;il me fallait une combinaison de pages et de webservices \u00e9crit en PHP, manipulant des donn\u00e9es stock\u00e9es dans une base MySQL. Voulant rendre ces pages dynamiques, j&#8217;utiliserais les capacit\u00e9s de JQuery en AJAX pour ce qui est des appels aux webservices, impl\u00e9mentant eux les fonctions de sauvegarde et de r\u00e9cup\u00e9ration de donn\u00e9es.<\/p>\n<h2>Les \u00e9tapes<\/h2>\n<p>La premi\u00e8re \u00e9tape pour moi a \u00e9t\u00e9 d&#8217;explorer la biblioth\u00e8que <a href=\"http:\/\/getbootstrap.com\/\">Twitter Bootstrap<\/a>, afin de proposer \u00e0 mon p\u00e8re un design de base pour les diff\u00e9rentes pages statiques du site.<\/p>\n<p>La seconde \u00e9tape, plus longue et ardue, a \u00e9t\u00e9 de d\u00e9velopper mes comp\u00e9tences en JavaScript, JQuery et \u00e9criture de webservices en PHP pour r\u00e9aliser une premi\u00e8re impl\u00e9mentation des fonctionnalit\u00e9s souhait\u00e9es de gestion de cheptels. Je me suis \u00e0 ce moment l\u00e9g\u00e8rement facilit\u00e9 le travail en utilisant un ORM tr\u00e8s l\u00e9ger et facile d&#8217;utilisation, <a href=\"http:\/\/www.redbeanphp.com\/\">RedBean PHP<\/a>.<\/p>\n<p>Je pr\u00e9senterais dans un prochain billet l&#8217;architecture de l&#8217;information de ce projet via un diagramme de classes<\/p>\n<p>Suite au d\u00e9veloppement d&#8217;un premier prototype, que je dois actuellement tester et faire valider par mon p\u00e8re, j&#8217;ai h\u00e9rit\u00e9 d&#8217;un \u00e9norme tas de &#8220;code spaghetti&#8221; dont je ne suis pas tr\u00e8s fier. Cette \u00e9tape m&#8217;a pourtant permis de d\u00e9finir pr\u00e9cis\u00e9ment mes besoins et les futures orientations techniques du projet.<\/p>\n<h2>Les futures orientations<\/h2>\n<p>Le code produit, ainsi que je l&#8217;ai d\u00e9j\u00e0 dit, pouvait \u00eatre alors qualifi\u00e9 de &#8220;code spaghetti&#8221;. Il violait ainsi quelques bonnes pratiques fondamentales du d\u00e9veloppement informatique, que je pr\u00e9sente dans les paragraphes suivants. Pour ma d\u00e9fense, j&#8217;ai travaill\u00e9 sur cette premi\u00e8re version avec un principe \u00e9nonc\u00e9 par un ancien Team Lead avec lequel j&#8217;ai travaill\u00e9 \u00e0 Montr\u00e9al, qui se r\u00e9sumait ainsi: &#8220;Make it work, make it clean, make it fast&#8221;. Je suis donc actuellement dans la deuxi\u00e8me \u00e9tape de ce processus, le &#8220;make it clean&#8221; !<\/p>\n<h3>Separation Of Concerns<\/h3>\n<p>Ce principe \u00e0 prendre en compte lors de la r\u00e9alisation d&#8217;une architecture logicielle sugg\u00e8re de garder \u00e0 l&#8217;esprit la s\u00e9paration des fonctionnalit\u00e9s au sein d&#8217;une application en modules ne se chevauchant pas et ne cr\u00e9ant ainsi pas de redondances.<\/p>\n<h3>Single Responsibility Principle<\/h3>\n<p>Le principe de responsabilit\u00e9 unique demande, en programmation orient\u00e9 objet, \u00e0 valider que chacune des classes d&#8217;une application n&#8217;impl\u00e9mente qu&#8217;une responsabilit\u00e9 et n&#8217;ex\u00e9cute des op\u00e9rations ne tombant que dans le champ de cette responsabilit\u00e9, qui doit \u00eatre proprement et enti\u00e8rement impl\u00e9ment\u00e9 au sein de la-dite classe.<\/p>\n<p>Le respect de ce principe dans nos d\u00e9veloppements facilite l&#8217;application du principe de Separation of concerns pr\u00e9c\u00e9demment pr\u00e9sent\u00e9.<\/p>\n<h3>DRY: Don&#8217;t Repeat Yourself<\/h3>\n<p>Comme son nom l&#8217;indique, ce principe va \u00e0 l&#8217;encontre d&#8217;une habitude de facilit\u00e9 de d\u00e9veloppeur d\u00e9butant, qui est de copier-coller partout un code impl\u00e9mentant le comportement souhait\u00e9 de mani\u00e8re \u00e0 gagner en vitesse. En POO, la r\u00e9alit\u00e9 est qu&#8217;un coper-coller signifie qu&#8217;une factorisation est possible, et il vaut toujours mieux prendre le temps d&#8217;appliquer cette factorisation que de r\u00e9p\u00e9ter dans n fichiers un bout de code. en OOP, plusieurs concepts nous permettent de respecter le DRY, tels que l&#8217;impl\u00e9mentation de Helpers (classes comme m\u00e9thodes), l&#8217;h\u00e9ritage de classes, ou encore le polymorhpisme.<\/p>\n<h2>Illustrations de ces orientations<\/h2>\n<p>Pour illustrer mes propos, je vais vous pr\u00e9senter un code que j&#8217;ai remis en ordre r\u00e9cemment, ainsi que la mani\u00e8re dont je l&#8217;ai fais \u00e9volu\u00e9.<\/p>\n<h3>Le code de base<\/h3>\n<p>Le code suivant est celui d&#8217;un webservice impl\u00e9ment\u00e9 en PHP (appel\u00e9 par AJAX au sein d&#8217;autres pages de l&#8217;application)<\/p>\n<p>[code language=&#8221;php&#8221;] &lt;?php require_once(&#8216;Mail.php&#8217;); include_once(&#8216;..\/..\/globals.php&#8217;); require(&#8216;..\/..\/library\/RedBeanORM\/rb.php&#8217;); $response = array(); if($_SERVER[&#8216;REQUEST_METHOD&#8217;] == &#8216;POST&#8217;) { \/\/ if form has been posted process data $data = array( &#8216;name&#8217; =&gt; convertAsSafeString($_POST[&#8216;name&#8217;]), &#8216;firstname&#8217; =&gt; convertAsSafeString($_POST[&#8216;firstname&#8217;]), &#8217;email&#8217; =&gt; convertAsSafeString($_POST[&#8217;email&#8217;]), &#8216;phone&#8217; =&gt; convertAsSafeString($_POST[&#8216;phone&#8217;]), &#8216;comment&#8217; =&gt; convertAsSafeString($_POST[&#8216;comment&#8217;]) ); \/\/ always return true if you save the contact data ok or false if it fails $response[&#8216;status&#8217;] = saveContact($data) ? &#8216;success&#8217; : &#8216;error&#8217;; $response[&#8216;message&#8217;] = $response[&#8216;status&#8217;] ? &#8216;Votre message a bien \u00e9t\u00e9 sauvegard\u00e9!&#8217; : &#8216;Il y a eu un probl\u00e8me lors de la sauvegarde du message.&#8217;; header(&#8216;Content-type: application\/json&#8217;); echo json_encode($response); exit; } function convertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } function saveContact($data) { $success = false; R::setup(&#8216;mysql:host=&#8217; . Database::HOST . &#8216;;dbname=&#8217; . Database::NAME, Database::USERNAME, Database::PASSWORD); $contact = R::dispense(&#8216;contact&#8217;); $contact-&gt;name = utf8_encode($data[&#8216;name&#8217;]); $contact-&gt;firstname = utf8_encode($data[&#8216;firstname&#8217;]); $contact-&gt;email = utf8_encode($data[&#8217;email&#8217;]); $contact-&gt;phone = $data[&#8216;phone&#8217;]; $contact-&gt;comment = utf8_encode($data[&#8216;comment&#8217;]); $id = R::store($contact); SendContactMail($contact); if ($id != null) { $success = true; } return $success; } function SendContactMail($contact) { $from = $contact-&gt;email; $to = &#8220;&#8221;; $subject = &#8220;Contact&#8221;; $body = $contact-&gt;comment; $host = &#8220;&#8221;; $username = &#8220;&#8221;; $password = &#8220;&#8221;; $headers = array(&#8216;From&#8217; =&gt; $from, &#8216;To&#8217; =&gt; $to, &#8216;Subject&#8217; =&gt; $subject); $smtp = Mail::factory(&#8216;smtp&#8217;, array( &#8216;host&#8217; =&gt; $host, &#8216;port&#8217; =&gt; &#8217;25&#8217;, &#8216;auth&#8217; =&gt; true, &#8216;username&#8217; =&gt; $username, &#8216;password&#8217; =&gt; $password ) ); $mail = $smtp-&gt;send($to, $headers, $body); if (PEAR::isError($mail)) { echo (&#8220;&lt;p&gt;&#8221; . $mail-&gt;getMessage() . &#8220;&lt;\/p&gt;&#8221;); } else { echo (&#8220;&lt;p&gt;Message successfully sent&lt;\/p&gt;&#8221;); } } ?&gt; [\/code]<\/p>\n<h3>Analyse du code<\/h3>\n<p>Que fait ce code ? Il v\u00e9rifie que la requ\u00eate est de type POST (envoi d&#8217;un formulaire): [code language=&#8221;php&#8221;] if($_SERVER[&#8216;REQUEST_METHOD&#8217;] == &#8216;POST&#8217;) [\/code] Transforme les informations re\u00e7ues en variables safe: [code language=&#8221;php&#8221;] $data = array( &#8216;name&#8217; =&gt; convertAsSafeString($_POST[&#8216;name&#8217;]), &#8216;firstname&#8217; =&gt; convertAsSafeString($_POST[&#8216;firstname&#8217;]), &#8217;email&#8217; =&gt; convertAsSafeString($_POST[&#8217;email&#8217;]), &#8216;phone&#8217; =&gt; convertAsSafeString($_POST[&#8216;phone&#8217;]), &#8216;comment&#8217; =&gt; convertAsSafeString($_POST[&#8216;comment&#8217;]) ); function convertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } [\/code] Se connecte \u00e0 une base de donn\u00e9es: [code language=&#8221;php&#8221;] R::setup(&#8216;mysql:host=&#8217; . Database::HOST . &#8216;;dbname=&#8217; . Database::NAME, Database::USERNAME, Database::PASSWORD); [\/code] Enregistre les informations dans la base de donn\u00e9es via un objet fournis par RedBean PHP: [code language=&#8221;php&#8221;] $contact = R::dispense(&#8216;contact&#8217;); $contact-&gt;name = utf8_encode($data[&#8216;name&#8217;]); $contact-&gt;firstname = utf8_encode($data[&#8216;firstname&#8217;]); $contact-&gt;email = utf8_encode($data[&#8217;email&#8217;]); $contact-&gt;phone = $data[&#8216;phone&#8217;]; $contact-&gt;comment = utf8_encode($data[&#8216;comment&#8217;]); $id = R::store($contact); [\/code] Envoie un mail r\u00e9capitulatif \u00e0 l&#8217;administrateur: [code language=&#8221;php&#8221;] SendContactMail($contact); function SendContactMail($contact) { $from = $contact-&gt;email; $to = &#8220;postmaster&#8221;; $subject = &#8220;Contact&#8221;; $body = $contact-&gt;comment; $host = &#8220;&#8221;; $username = &#8220;&#8221;; $password = &#8220;&#8221;; $headers = array(&#8216;From&#8217; =&gt; $from, &#8216;To&#8217; =&gt; $to, &#8216;Subject&#8217; =&gt; $subject); $smtp = Mail::factory(&#8216;smtp&#8217;, array( &#8216;host&#8217; =&gt; $host, &#8216;port&#8217; =&gt; &#8217;25&#8217;, &#8216;auth&#8217; =&gt; true, &#8216;username&#8217; =&gt; $username, &#8216;password&#8217; =&gt; $password ) ); $mail = $smtp-&gt;send($to, $headers, $body); if (PEAR::isError($mail)) { echo (&#8220;&lt;p&gt;&#8221; . $mail-&gt;getMessage() . &#8220;&lt;\/p&gt;&#8221;); } else { echo (&#8220;&lt;p&gt;Message successfully sent&lt;\/p&gt;&#8221;); } } [\/code] Et renvoie la r\u00e9ponse en format JSON: [code language=&#8221;php&#8221;] \/\/ always return true if you save the contact data ok or false if it fails $response[&#8216;status&#8217;] = saveContact($data) ? &#8216;success&#8217; : &#8216;error&#8217;; $response[&#8216;message&#8217;] = $response[&#8216;status&#8217;] ? &#8216;Votre message a bien \u00e9t\u00e9 sauvegard\u00e9!&#8217; : &#8216;Il y a eu un probl\u00e8me lors de la sauvegarde du message.&#8217;; header(&#8216;Content-type: application\/json&#8217;); echo json_encode($response); exit; [\/code]<\/p>\n<p>Dans ce code PHP, nous voyons bien que le principe de Single Responsibility n&#8217;est pas respect\u00e9. De plus, ce squelette de code avait \u00e9t\u00e9 r\u00e9p\u00e9t\u00e9 \u00e0 outrance pour chacun des objets que mon syst\u00e8me d&#8217;administration n\u00e9cessitait. Donc le DRY n&#8217;\u00e9tait pas non plus respect\u00e9.<\/p>\n<h3>La solution adopt\u00e9e<\/h3>\n<p>Ce simple morceau de code spaghetti m&#8217;a forc\u00e9 \u00e0 revoir une grande partie de ma strat\u00e9gie de sauvegarde. En effet, j&#8217;ai d\u00e9cid\u00e9 de mettre en place une architecture r\u00e9ellement orient\u00e9e objet. Pour cela, j&#8217;ai tout d&#8217;abord impl\u00e9ment\u00e9 une classe de base, que j&#8217;ai nomm\u00e9 Entity: [code language=&#8221;php&#8221;] &lt;?php require_once __DIR__ . &#8216;\/..\/library\/RedBeanORM\/rb.php&#8217;; \/** * * Entity: Base class of all objects stored in the database I will use * in this project. * Mapped to database by RedBeanPHP ORM. * **\/ class Entity { protected $_id; protected $_entity; \/\/ Base Method to be used for saving to Database purpose public function SaveEntity($post, $entityName) { $success = false; R::setup(&#8216;mysql:host=&#8217; . Database::HOST . &#8216;;dbname=&#8217; . Database::NAME, Database::USERNAME, Database::PASSWORD); $this-&gt;_entity = R::dispense($entityName); foreach($post as $key) { if (isset($_POST[$key])) { $this-&gt;_entity-&gt;setAttr($key, Utility::ConvertAsSafeString($_POST[$key])); } } $this-&gt;_id = R::store($this-&gt;_entity); if ($this-&gt;_id != null) { $success = true; } return $success; } } [\/code]<\/p>\n<p>Cette classe impl\u00e9mente une m\u00e9thode de sauvegarde, qui re\u00e7oit par la variable $post un tableau contenant les noms des champs des formulaires \u00e0 utiliser, et le nom de l&#8217;entit\u00e9 \u00e0 sauvegarder.<\/p>\n<p>J&#8217;ai ensuite impl\u00e9ment\u00e9 une classe Contact.php, h\u00e9ritant de la classe Entity, dont le code est le suivant:<\/p>\n<p>[code language=&#8221;php&#8221;] &lt;?php require_once &#8216;Mail.php&#8217;; require_once __DIR__ . &#8216;\/entity.php&#8217;; \/** * * Contact: Class derived from Entity used to manipulate contact information * send through the contact form * **\/ class Contact extends Entity { public function SaveEntity($post) { $success = false; $success = parent::SaveEntity($post, &#8216;contact&#8217;); $this-&gt;SendContactMail(); return $success; } private function SendContactMail() { $from = $this-&gt;_entity-&gt;email; $to = &#8220;&#8221;; $subject = &#8220;Contact&#8221;; $body = $this-&gt;_entity-&gt;comment; $host = &#8220;&#8221;; $username = &#8220;postmaster&#8221;; $password = &#8220;&#8221;; $headers = array(&#8216;From&#8217; =&gt; $from, &#8216;To&#8217; =&gt; $to, &#8216;Subject&#8217; =&gt; $subject); $smtp = Mail::factory(&#8216;smtp&#8217;, array( &#8216;host&#8217; =&gt; $host, &#8216;port&#8217; =&gt; &#8217;25&#8217;, &#8216;auth&#8217; =&gt; true, &#8216;username&#8217; =&gt; $username, &#8216;password&#8217; =&gt; $password ) ); $mail = $smtp-&gt;send($to, $headers, $body); if (PEAR::isError($mail)) { echo (&#8220;&lt;p&gt;&#8221; . $mail-&gt;getMessage() . &#8220;&lt;\/p&gt;&#8221;); } else { echo (&#8220;&lt;p&gt;Message successfully sent&lt;\/p&gt;&#8221;); } } } ?&gt; [\/code]<\/p>\n<p>Cette classe Contact surcharge la m\u00e9thode de sauvegarde de l&#8217;entit\u00e9, en appelant la m\u00e9thode de la classe parente et en ajoutant seulement un appel \u00e0 une fonction private effectuant l&#8217;envoi du mail r\u00e9capitulatif.<\/p>\n<p>J&#8217;ai \u00e9galement impl\u00e9ment\u00e9 une classe statique nomm\u00e9e Utility poss\u00e9dant la m\u00e9thode ConvertAsSafeString() rempla\u00e7ant celle pr\u00e9alablement r\u00e9p\u00e9t\u00e9e dans tous mes webservices.<\/p>\n<p>[code language=&#8221;php&#8221;] &lt;?php \/** * * Utility class used to operate methods on string to ensure * the manipulated data are safe * **\/ class Utility { \/\/ Method to convert as safe string to save in a DB public static function ConvertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } } ?&gt; [\/code]<\/p>\n<p>La derni\u00e8re \u00e9tape \u00e9tait finalement de revenir sur le fichier save_contact.php et de remplacer le code existant par un appel \u00e0 ces diff\u00e9rentes classes, permettant de respecter les bonnes pratiques \u00e9voqu\u00e9es plus haut !<\/p>\n<p>[code language=&#8221;php&#8221;] &lt;?php require_once(&#8216;Mail.php&#8217;); include_once(&#8216;..\/..\/globals.php&#8217;); $response = array(); if($_SERVER[&#8216;REQUEST_METHOD&#8217;] == &#8216;POST&#8217;) { \/\/ if form has been posted process data \/\/Instanciates new Contact entity to be able to use the save capability of this object $post = array(&#8216;name&#8217;, &#8216;firstname&#8217;, &#8217;email&#8217;, &#8216;phone&#8217;, &#8216;comment&#8217;); $contact = new Contact(); \/\/ always return true if you save the contact data ok or false if it fails $response[&#8216;status&#8217;] = $contact-&gt;SaveEntity($post) ? &#8216;success&#8217; : &#8216;error&#8217;; $response[&#8216;message&#8217;] = $response[&#8216;status&#8217;] ? &#8216;Votre message a bien \u00e9t\u00e9 sauvegard\u00e9!&#8217; : &#8216;Il y a eu un probl\u00e8me lors de la sauvegarde du message.&#8217;; header(&#8216;Content-type: application\/json&#8217;); echo json_encode($response); exit; } ?&gt; [\/code]<\/p>\n<p>Comme vous pouvez le voir, un code absolument spaghetti a finalement \u00e9t\u00e9 remplac\u00e9 par un code clair, dont l&#8217;action principale est d&#8217;instancier un objet de type Contact, et de le sauvegarder en base de donn\u00e9es \u00e0 partir des informations envoy\u00e9es par le formulaire.<\/p>\n<p>&nbsp;<\/p>\n<h2>Et maintenant ?<\/h2>\n<p>L&#8217;impl\u00e9mentation de ce code, ainsi que d&#8217;autres fonctions alambiqu\u00e9es que j&#8217;\u00e9voquerais dans de futurs billets (dont la mise en place d&#8217;un autoloading) m&#8217;ont motiv\u00e9es \u00e0 imaginer l&#8217;impl\u00e9mentation de mon propre framework PHP exp\u00e9rimental. Non dans une id\u00e9e de c\u00e9l\u00e9brit\u00e9 ni de contribution exceptionnelle au monde de l&#8217;open-source, mais dans l&#8217;optique de continuer \u00e0 &#8220;diver in&#8221; le fonctionnement de PHP et les possibilit\u00e9s qu&#8217;offrent ces facettes orient\u00e9 objet.<\/p>\n<p>Que pensez-vous de mes avanc\u00e9es, des concepts que je tente d&#8217;appliquer et de ma mani\u00e8re de pr\u00e9senter les choses ?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Au (re)lancement de ce site, j&#8217;avais annonc\u00e9 que je ne parlerais pas de technique sur cet espace&#8230; Malheureusement, \u00e9tant passionn\u00e9 par le d\u00e9veloppement WEB et \u00e9tant actuellement en p\u00e9riode de forte productivit\u00e9 sur des projets personnels motivants, l&#8217;envie m&#8217;a repris de d\u00e9tailler un peu mes projets et le travail associ\u00e9 ! Alors, voici un premier [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coopedia_published":false},"categories":[25],"tags":[18,20,21,17,19,16],"_links":{"self":[{"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/posts\/141"}],"collection":[{"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/comments?post=141"}],"version-history":[{"count":9,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/posts\/141\/revisions"}],"predecessor-version":[{"id":339,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/posts\/141\/revisions\/339"}],"wp:attachment":[{"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/media?parent=141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/categories?post=141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.balessan.me\/api\/wp\/v2\/tags?post=141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}