Soap en PHP

Bonjour,

J’ai un travail qui implique d’écrire un service soap en php. Et certaines taches de ce service sont aussi d’utiliser soap en tant que client vers un autre serveur.

J’ai fais un exemple minimal afin de me mettre sur les rails. C’est un exemple trouvé sur github que j’ai légèrement modifié

serveur.php

<?php
class MySoapServer{
 public function getMessage(){
 return 'Hello,World!';
 }
 public function addNumbers($num1, $num2){
 return $num1+$num2;
 }
}
$options = [
 'uri'=>'http://localhost/test'//je sais pas a quoi ca sert
];
$server = new SoapServer(NULL,$options);
$server->setClass('MySoapServer');
$server->handle();

client.php

<?php
$options = [
 'location' => 'http://localhost:8000',
 'uri' => 'http://localhost/everth',//je sais pas ce que c'est
 'exceptions' => false
];
$client = new SoapClient(NULL,$options);
echo $client->getMessage();
echo $client->addNumbers(3,5);

Pour tester :

php -S 0.0.0.0:8000 serveur.php
php client.php

 

Installer plusieurs version de php

Bonjour,

Introduction

Quand on installe php5 et php7 sur son ordinateur (ou serveur), on ne peut en utiliser qu’un. Malheureusement, des fois, il nous faut tester le même site sous différentes versions de php. Et des fois, on aimerais travailler sur 2 projets qui nécessitent des versions de php différentes.

Je suis passé par plusieurs solutions plus ou moins efficaces, comme les machines virtuelles, 2 serveurs apaches sur 2 ports différents, etc.

La solution en théorie

J’ai fini par trouver la solution idéale :

Php en fast cgi et une variable dans htaccess pour choisir la version à executer. Théoriquement on pourrait avoir au sein du même site, une version différente de php par répertoire (site vitrine, blog…). Dans la pratique, on met un seul htaccess à la racine du site et c’est tout.

On peut aussi paramétrer php-fpm pour que les sites aient l’utilisateur et le groupe d’un compte spécifique, utile pour accorder les droits sur le site et sur ftp et les mettant sur un utilisateur système commun.

On va détailler ici uniquement la partie transformation de php de module vers fast cgi.

Je pars du principe que les 2 versions ont été installées via le repository de la distribution, pour debian :

sudo apt-get install apache2 php7.2 libapache2-mod-php7.2 mysql-server mysql-client php7.2-mysql phpmyadmin php7.2-odbc php7.2-curl php7.2-json php7.2-xml php7.2-mbstring

C’est typiquement ce que j’installe pour avoir un serveur de développement lamp fonctionnel.

On imagine donc que sur le serveur il y ait php5, php7.0 et php7.2 d’installés.

La version exécutée par apache dépend de l’ordre d’installation. Je suppose que dans une utilisation normale, on a installé php7.2 en dernier et c’est donc lui qui anime tous les sites sur lesquels on travail.

La soluton en pratique

Il faut tout d’abord arrêter apache

sudo service apache2 stop

Ensuite on supprime tous les modules php

sudo rm -f /etc/apache2/mods-enabled/php*
sudo apt-get remove --purge 'libapache2-mod-php*'

On installe la version cgi de php

sudo apt-get install libapache2-mod-fastcgi php5-fpm php5-cgi php7.0-fpm php7.0-cgi php7.2-fpm php7.2-cgi

On active le module actions

sudo a2enmod actions

On configure fastcgi

sudo nano -w /etc/apache2/mods-enabled/fastcgi.conf

Mettre le contenu suivant

<IfModule mod_fastcgi.c>
	AddHandler fastcgi-script .fcgi
	#FastCgiWrapper /usr/lib/apache2/suexec
	FastCgiIpcDir /var/lib/apache2/fastcgi
	AddType application/x-httpd-php5 .php5 .php
	AddHandler php5 .php5 .php
	Action php5 /cgi-php5
	Alias /cgi-php5 /usr/lib/cgi-bin/php5
	FastCgiExternalServer /usr/lib/cgi-bin/php5 -socket /var/run/php5-fpm.sock -pass-header Authorization
	AddType application/x-httpd-php7.0 .php7 .php
	AddHandler php7.0 .php7 .php
	Action php7.0 /cgi-php7.0
	Alias /cgi-php7.0 /usr/lib/cgi-bin/php7.0
	FastCgiExternalServer /usr/lib/cgi-bin/php7.0 -socket /var/run/php/php7.0-fpm.sock -pass-header Authorization
	AddType application/x-httpd-php7.2 .php7 .php
	AddHandler php7.2 .php7 .php
	Action php7.2 /cgi-php7.2
	Alias /cgi-php7.2 /usr/lib/cgi-bin/php7.2
	FastCgiExternalServer /usr/lib/cgi-bin/php7.2 -socket /var/run/php/php7.2-fpm.sock -pass-header Authorization
	<Directory /usr/lib/cgi-bin>
		Require all granted
	</Directory>
</IfModule>

Le bloc précédent est un peu indigest mais en le lisant, on voit qu’il y a 3 blocs, 1 pour chaque version de php.

Il ne reste plus qu’a activer apache

sudo service apache2 start

Tout devrait fonctionner comme avant. Y compris, la version php7.2 par defaut.

Pour changer cela, dans le htaccess, on mettra le code suivant

<IfModule mod_fastcgi.c>
 AddHandler php5 .php
 #AddHandler php7.0 .php
 #AddHandler php7.2 .php
</IfModule>

Il suffit de décommenter la ligne qui nous intéresse et de commenter les autres. On peut aussi supprimer celles qui ne nous intéressent plus.

Conclusion

Voilà, c’est la configuration que j’utilise sur les serveurs de développement. Cela permet d’avoir un seul serveur qui pourra s’adapter à toutes les versions de php.

Je ne sais pas comment c’est fait chez free.fr, mais je suppose que c’est très similaire, on peut donc supposer que cette méthode (avec un réglage de fpm plus approfondi pour les droits) peut convenir à un serveur de prod.

 

Closure en php

Bonjour,

Les closures qu’est-ce que c’est ?

Eh bien, ça dépend du langage. Dans un langage fonctionnel, c’est une fonction et son environnement. En PHP, c’est une fonction anonyme.

Dans la version 7.1 de PHP, on se rapproche d’une vraie closure grâce à « use ».

Ce qui m’intéresse dans les fonctions anonymes en PHP, c’est leur côté first class functions. C’est à dire qu’on peut les utiliser comme des objets et les affecter à des variables, les passer en paramètre à d’autres fonctions, etc.

Si vous vous êtes demandé comment les routeurs (réécriture d’url) font leur travail, la réponse est ici.

Par exemple comment je peux écrire la fonction get pour que le code ci-dessous marche ?

$routeur->get('/un/chemin', controleur());
$routeur->get('/un/autre/chemin', function(){return "coucou";});

Dans le premier cas, c’est facile, la fonction controleur s’exécute et retournera sûrement une chaîne de caractère qui sera reproduite à l’écran.

L’implémentation de get pourrait être la suivante :

function get($chemin, $resultat){
   //quelques traitements
   if(is_callable($resultat)){
      echo $resultat();
   }else{
      echo $resultat;
   }
   //d'autres traitements
}

On peut facilement étoffer l’exemple avec de nouvelles fonctionnalités. Mais je vais m’arrêter là pour l’instant.

Idée sur les sites multilingues

Bonjour,

La traduction d’un site est un casse tête. Différents frameworks proposent différentes solutions.

Et si je devais construire un module de traduction sans me soucier de ce qui existe déjà ? Comment je verrais la chose ?

Voici mon idée :

Je mettrais en place une structure de base de données qui permet d’avoir autant de langues que l’on veut. Aucune langue n’est obligatoire ni aucune par défaut. C’est l’application qui fera un choix en « requêtant » la base de données.

La clé pour un texte ressemble a une variable et non a un texte.

Voici un exemple de chaque :

echo trad("client.lister");
echo trad("Lister les clients");

La fonction trad fait la traduction et rapporte les manques :

Si une clé n’existe pas dans la base de données, on la crée et on ne lui donne aucune traduction.

Si une clé existe mais pas de traduction dans la langue courante, l’application décidera quoi faire : Afficher la clé, une erreur ou le texte dans une autre langue. De plus, un flag dans la base de données est positionné afin de savoir qu’une traduction manque.

Pour accélérer la traduction, on peut exporter la base de données dans plusieurs fichiers PHP (un par langue) contenant un tableau des traductions.

Enfin, un portail admin permet de voir les clés demandées sans traduction et de les traiter. Quand on valide toutes les traductions, les fichiers PHP (si l’accélération est activée) sont re-générés.

Voilà pour les grandes lignes. Si vous avez aussi des idées sur une implémentation plus aboutie, mettez les en commentaire.

Les chemins vers les fichiers en php

Bonjour,

Voici un sujet qui me donne pas mal de soucis quand je veux faire une application web.

Comment écrire ses chemins ?

Il y a plusieurs façons de procéder et je vais donner mon avis sur chacune d’elles.

Contexte

Il faut d’abord savoir dans quel contexte on est. Le contexte dans ce cas est l’environnement qui va résoudre le chemin.

On peut distinguer 3 contextes différents :

Le contexte php : C’est PHP sur le serveur qui va résoudre le chemin et consommer le fichier.

Le contexte http de la page : C’est le navigateur qui va demander le fichier et il connaît l’url en cours.

Le contexte externe : On ne connaît rien sur l’adresse du fichier à atteindre. Quand on est dans un mail par exemple.

Invocation

Dans le contexte de PHP

On peut invoquer un fichier de plusieurs façons :

! J’ai choisis la commande include, mais toutes les autres invocations marchent de la même façon pour les autres types d’inclusion

Chemin aléatoire
include "../chemin/fichier.php";

C’est une inclusion relative au chemin courant.

Cette méthode marche mais on ne sais pas toujours quel fichier est utilisé à cause de sa méthode de résolution cf doc.

On se met à dupliquer les fichiers pour être sûr de l’inclure sans se prendre la tête à comprendre la résolution de PHP.

Chemin relatif
include __DIR__."/../chemin/fichier.php";

On donne ici le chemin absolu dans l’arborescence du serveur, mais dans notre tête, c’est un chemin relatif au fichier courant.

C’est la méthode que je privilégie car il devient simple de retrouver son chemin 🙂

Chemin absolu
define("_ROOT", __DIR__."/../cheminVersRoot");
include _ROOT."/cheminDepuisRoot/fichier.php";

! Les 2 lignes ci-dessus sont dans 2 fichiers différents, généralement config.php pour la 1ère.

Ici aussi on donne un chemin absolu sur le serveur à PHP, mais dans notre tête, on donne un chemin absolu depuis la racine du projet.

C’est une solution qui fonctionne bien et qui avait son avantage à l’époque où on avait l’url qui reflétait la structure des fichiers.

Dans le contexte HTTP de la page

Pour illustrer ce contexte, j’utiliserais une balise img (et sans les attributs obligatoires).

Chemin relatif
<img src="../chemin/fichier.png">

C’est un chemin relatif, ça marchait à l’époque où le fichier php avait le même chemin que l’url pour y accéder. Depuis la réécriture d’url, c’est une technique qui ne fonctionnera pas sans y laisser ses cheveux.

Chemin absolu
<img src="/chemin/fichier.png">

C’est la méthode que j’utilise, elle a l’avantage de donner un chemin absolu par rapport à la racine du projet.

Chemin externe
<img src="http://www.domaine.com/chemin/fichier.png">

Le travail et la maintenance deviennent un vrai casse tête, il faut être certain d’avoir modifié les chemins du serveur local avant l’envoi etc.

Chemin externe aidé par PHP
define("_HTTP_ROOT", "http://www.domaine.com");
ou (pire) define("_HTTP_ROOT", $_SERVER["HTTP_HOST"]);
<img src="<?= _HTTP_ROOT ?>/chemin/fichier.png">

! Les 2 lignes ci-dessus sont censées être dans 2 fichiers différents
! J’ai choisi HTTP_HOST mais SERVER_NAME marche tout aussi bien, mais il faut faire attention avec ces variables

On essaie de pallier au problème ci-dessus en n’ayant qu’une constante à modifier, mais il faut quand même penser à la modifier avant l’envoi.

Dans le contexte externe

La meilleure solution est le chemin externe décrit ci-dessus.

On peut néanmoins avec quelques précautions utiliser les variables dans $_SERVER.

Conclusion

Les chemins sont toujours un casse tête en PHP. Avec un peu de rigueur et en utilisant la bonne forme, on peut s’en sortir.

Je n’ai pas mis tous les dérivés de chemins que j’ai pu utiliser mais seulement un échantillon. Si vous avez une suggestion intéressante que j’aurais oublié n’hésitez pas à la mettre en commentaire.

Détecter l’environnement de php

Bonjour,

J’ai eu besoin de connaître l’environnement de Php. C’est à dire savoir si c’est en environnement web, en ligne de commande directe, ssh ou via cron.

Je n’avais pas besoin de plus de détail, mais il fallait que ce soit fiable.

Comme d’habitude, une recherche sur stackoverflow donne plusieurs réponses. Je fais le tri et ne trouve rien qui marche bien pour mon cas. Mais une bonne piste que j’ai creusé.

Je me retrouve donc avec ceci :

php_sapi_name()=="cli"?(isset($_SERVER["TERM"])?"cli":(isset($_SERVER["SSH_CLIENT"])?"ssh":"cron")):"web";

Ce one-liner renvoie :

  • « web » en environnement web
  • « cli » en ligne de commande directe
  • « ssh » via ssh
  • « cron » quand il est lancé via cron

Ce script m’a été très utile et marche bien dans un environnement linux. Si vous avez l’occasion de le tester sur un autre système, n’hésitez pas à commenter sur votre expérience.

Envoi d’emails en php depuis une boite office365

Bonjour,

Des fois, un problème qui est censé être simple, devient un casse tête chronophage, en plus je DOIS le faire car c’est pour un client, donc pas de remise au lendemain (du moins pas sans se battre jusqu’à épuisement le jour même).

Voici les données de départ :

  • Un nom de domaine relié à un hébergement
  • Une boite e-mail chez office365

Le but est simple : pouvoir envoyer des e-mails depuis php grâce à office365. Et si on a choisi de payer une boite e-mail c’est pour avoir notre nom de domaine dessus.

C’est simple et en théorie facile. Il faut d’abord enregistrer les champs MX auprès de notre registrar pour avoir le nom de domaine qui colle à notre boite mail, c’est facile, ça marche un peu, on règle les DNS et ça roule.

Deuxième étape, on vérifie le smtp et pop/imap. C’est tout aussi facile. On prend icedove (oui, j’ai une Debian, si vous vous souvenez) et on configure les paramètres qu’office365 nous a donné. Après des dizaines d’années de configuration sur outlook express, thunderbird, et autres smartphones du s60 à android, le test se fait en deux coups de cuiller à pot.

Troisième étape, tester manuellement (avec openssl) sur le serveur en ssh. Ca fonctionne bien.

Quatrième étape, on fait un test en php avec la fonction mail(). Bien entendu, il faut une connexion StartTls avec authentification qui n’est pas prise en charge par mail().

Cinquième étape, installer et configurer un MTA qui pourra ne rien demander à mail() et envoyer le courrier quand même. On regarde du côté de sendmail, ç’est très compliqué. Postfix, c’est moins compliqué mais ça reste compliqué quand même. Le problème n’est pas de faire marcher cette solution, c’est de la sécuriser afin de ne pas devenir un zombie pour spammers.

Cinquième étape bis, chercher une alternative à mail(). Une solution vite trouvée : phpmailer. C’est facile et ça marche sur la machine de test (mon ordinateur) et un compte hotmail fait pour l’occasion. J’ajuste les paramètres pour le compte office365 et comme prévu, ça fonctionne toujours sur ma machine.

Sixième étape, on envoie la page de test sur le serveur. Ca ne marche pas ! Le syndrome « Ca marche sur ma machine » a encore été diagnostiqué. Je passe une journée entière à déboguer le code source de phpmailer afin de savoir où se situe le problème. C’est la fonction stream_socket_client qui fait un timeout, il y a donc un firewall qui empêche la connexion. Je règle le timeout de connexion à 10s pour que php donne une erreur de connexion plutôt qu’un timeout de la page (entre 2 timeouts, choisir le moindre). Une nouvelle journée passée à parler anglais (à la louche) pour se faire comprendre du staff de chez Microsoft (très professionnel d’ailleurs). Ils veulent m’aider grâce à un logiciel de partage d’écran, qui est compatible Windows et Mac… J’ai aussi un problème avec l’adsl en ce moment, j’utilise donc la 3G de mon téléphone, et donc pas d’internet quand je parle… Décidément, cette journée n’est pas la plus « jouasse ». Après quelque coups de fil et des mails échangés, on arrive à la conclusion que ç’est pas chez eux que ça coince.

Septième étape, on récapitule parce que ça commence à ressembler à du vaudou cette histoire.

  • Ca marche sur ma machine
  • Le même code ne marche pas sur le serveur
  • Le même code marche sur le serveur quand c’est hotmail qui est configuré
  • Le firewall est désactivé pour pas nous prendre la tête
  • Ca ne bloque pas du côté d’office365
  • Openssl nous permet d’envoyer les mails sur le serveur manuellement
  • Sendmail n’est pas à blamer car phpmailer le court-circuite

Les hypothèses qui restent :

  • phpmailer à une bogue qui fait que hotmail fonctionne et pas office365
  • Le serveur malgré le firewall désactivé fait une distinction entre office365 et hotmail

La deuxième hypothèse me parait plus plausible, je décide de faire un telnet tout bête, et ça ne passe pas, contrairement à openssl qui se connecte.
Un indice qui ne m’as pas échappé est que telnet coince devant une ipv6, je test derechef sur hotmail, ça passe, et bingo, une ipv4.
J’ai donc une piste sérieuse, je désactive l’ipv6 sur le serveur et telnet marche de partout sur toutes les adresses.

Enfin, je lance le script de test php et… Encore un timeout. Je consulte les logs, je constate qu’il y a un fait étrange dans phpmailer : La variable timeout est utilisée dans stream_socket_client(), ce qui est normal, mais aussi dans stream_set_timeout(), ce qui est bizarre pour moi. J’aurais aimé choisir une valeur différente pour chacun.
Et si j’ai remarqué cela c’est que le timeout n’est plus à la connexion mais lors de l’authentification. Le serveur met environ 12 secondes pour confirmer l’existence du compte, oui, j’ai mis 10s et ça me retombre dessus.
Je fais un calcul simple, la page php timeout à 30s, l’authentification met 15s, il reste donc 15s pour la connexion vers la boite mail.
Je met donc 15s pour timeout sans patcher phpmailer (afin de prendre 2 valeurs différentes, si vous suivez toujours).

Je croise les doigts et lance le test et… Ca marche \o/. On fais quelques tests supplémentaires pour voir si c’est tout le temps vrai, et oui.

J’ai quelques questions qui me viennent à l’esprit :

  • Pourquoi le test manuel est très rapide (connexion et authentification en 3s, le reste est quasi instantané) et phpmailer si lent.
  • Pourquoi ipv6 ne marche pas alors que le firewall est désactivé. J’ai une réponse qui vient de google, Digital Ocean, qui est notre hébergeur, désactive ipv6 sur les ports smtp, donc même si le firewall du serveur laisse passer ces ports, celui de l’hébergeur bloque quand même.

Je n’ai plus de temps pour résoudre ces questions. Et je me contente donc de cette petite victoire.

 

forcer un code retour http 200 en php

Bonjour,

J’avais l’habitude de donner des codes de retour http en php pour signaler des erreurs, c’est devenu de plus en plus vrai quand la réécriture d’url est devenue la norme.

Voici les codes de retour que j’utilise le plus :

  • 404 pour un lien invalide
  • 403 pour les gens inconnus dans une zone protégée
  • 401 pour les gens connus dans une zone dont ils n’ont pas accès
  • 501 pour un lien valide non configuré dans la base de données
  • 503 pour une erreur de service (par exemple mod_rewrite pas fonctionnel)

Il y en a d’autres qui sont implicitements envoyés par apache ou php.

En allant chez free.fr, htaccess n’accepte quasi rien.
La réécriture n’est pas possible directement et on a recours à la redirection de l’erreur 404 d’apache pour aller sur notre point d’entrée.

Jusqu’ici tout va bien, mais quand j’ai servi ma page, je ne veux pas qu’elle renvoie 404. Ce n’est pas un souci car j’ai l’habitude d’envoyer des codes de retour, donc si aucune erreur n’est détectée, je rajoute avant tout envoi le code suivant :

header("HTTP/1.1 200 Ok");

Sauf que le code retour 200 n’est pas honoré par php, j’ai donc cherché dans la doc de php pour trouver une alternative qui puisse donner un code 200 afin que les bots ne soient pas induits en erreur.

http_response_code(200);

Malheureusement tout aussi inefficace.

J’allais abandonner quand je suis tombé sur une réponse dans stackoverflow.

header("Status: 200");

J’ai vite testé et ça marche. J’ai aussi testé pour les autres codes et ça marche aussi.

Voilà donc ma nouvelle façon d’envoyer un code d’erreur (et de réussite) http via php.