PHP SoapClient port bug workaround

PHP’s SoapClient has a bug (PHP 5.2.10 here, still not fixed apparently) when the SOAP service must be accessed on a different port other than 80. The WSDL file is fetched correctly, but all subsequent requests are made without any port in the Host field. This causes a SoapFault exception when trying to call any of the service’s methods.

So if the WSDL location is:

http://example.com:33080/soap/server/path?WSDL

All requests after fetching the WSDL file will be made to:

http://example.com/soap/server/path

The simplest way i could work around this was to extend SoapClient and intercept the constructor and the __doRequest method to inject the port in the location on each request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
class My_SoapClient extends SoapClient {
 
    public function __construct($wsdl, $options) {
        $url = parse_url($wsdl);
        if ($url['port']) {
            $this->_port = $url['port'];
        }
        return parent::__construct($wsdl, $options);
    }
 
    public function __doRequest($request, $location, $action, $version) {
        $parts = parse_url($location);
        if ($this->_port) {
            $parts['port'] = $this->_port;
        }
        $location = $this->buildLocation($parts);
 
        $return = parent::__doRequest($request, $location, $action, $version);
        return $return;
    }
 
    public function buildLocation($parts = array()) {
        $location = '';
 
        if (isset($parts['scheme'])) {
            $location .= $parts['scheme'].'://';
        }
        if (isset($parts['user']) || isset($parts['pass'])) {
            $location .= $parts['user'].':'.$parts['pass'].'@';
        }
        $location .= $parts['host'];
        if (isset($parts['port'])) {
            $location .= ':'.$parts['port'];
        }
        $location .= $parts['path'];
        if (isset($parts['query'])) {
            $location .= '?'.$parts['query'];
        }
 
        return $location;
    }
}

It works for me, and I would like to know if it doesn’t cover your particular case :)

Detectarea platformelor mobile in PHP

Folosind diverse resurse online (inclusiv lista de User-Agents folositi de dispozitivele mobile de aici), am scris o mica clasa PHP ce detecteaza platforma mobila a utilizatorului din cele mai cunoscute (fara nici o pretentie de exhaustivitate):

Modul de utilizare este foarte simplu, descris in detaliu pe Google Code

Nu am avut foarte multe device-uri la indemana pentru teste (decat cateva standard, plus un iPhone si un Blackberry), asa ca as aprecia daca m-ati atentiona daca nu functioneaza cum ar trebui.

FTL jump … now!

Fiecare fisier JavaScript, CSS, imagine, Flash, etc. afisat utilizatorului reprezinta un request catre server. Pentru fiecare asemenea fisier browserul trimite cererea, serverul o preia, proceseaza, serveste fisierul cerut, si tot asa. La site-urile “mari” se simte. In load-ul serverului, in latimea de banda consumata, in experienta utilizatorilor.

Urmand recomandarile echipei de la Yahoo!, o sa va arat la ce solutie am ajuns.

Dar mai intai … cifre. Am ales Okazii.ro drept studiu de caz (toate masuratorile sunt facute cu YSlow, folosind o versiune salvata a primei pagini):

Fara FTL (pagina salvata aici)

Cu FTL (pagina salvata aici)

Calculat la traficul lor (aprox. 82.000 vizitatori unici pe zi), netinand cont de cache in nici unul din cazuri, asta se traduce in o diferenta de 902.000 de request-uri si 14.1 GB trafic lunar.

Cum se foloseste

Presupunand urmatoarea structura de directoare:

/public
    /css
    /js
index.php

Descarcati arhiva si extrageti fisierele in root asa incat noua structura va fi:

/ftl
    /cache
    /lib
    CSS.php
    File.php
    JS.php
    config.php
    index_css.php
    index_js.php
/public
    /css
    /js
index.php

Mutati fisierele index_css.php si index_js.php din directorul /ftl in directorul /public/css, respectiv /public/js, si redenumiti-le in index.php:

/ftl
    /cache
    /lib
    CSS.php
    File.php
    JS.php
    config.php
/public
    /css
        index.php
    /js
        index.php
index.php

Adaugati liniile urmatoare in .htaccess:

1
2
3
4
5
6
7
8
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType application/x-javascript A315360000
    ExpiresByType text/css A315360000
</IfModule>
 
RewriteEngine On
RewriteRule ^public/(js|css)/([a-z0-9\.]+)\.(js|css)$    public/$1/index.php?hash=$2

Includeti fisierele necesare in index.php:

1
2
3
4
<?php
    include("ftl/JS.php");
    include("ftl/CSS.php");
?>

Totul e pregatit, tot ce mai trebuie sa facem e sa incarcam in pagina fisierele. Spre exemplu, ca sa incarc in <head> cateva fisiere CSS si JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
    $css = new FTL_CSS();
    $js  = new FTL_JS();
 
    $css->add('reset')
        ->add('typo')
        ->add('layout')
        ->add('error')
        ->add('ie', 'lte IE 7');
 
    $js->add('prototype')
       ->add('sizzle')
       ->add('effects')
       ->add('lightbox');
 
    echo $css->render();
    echo $js->render();
?>

Nu uitati sa setati drepturi de scriere pe subdirectoarele din directorul /ftl/cache.

Codul HTML generat va arata in felul urmator:

1
2
3
<link rel="stylesheet" type="text/css" media="screen" href="public/css/1e9dd525d1fbe94f7b8aede22cc141f5.1242750618.css" />
<!--[if lte IE 7]><link rel="stylesheet" type="text/css" media="screen" href="public/css/25400724d7370b0b29c9369d9af3dd21.1242750626.css" /><![endif]-->
<script type="text/javascript" src="public/js/56c728a5a7689ab1663359a7edff160a.1242751025.js"></script>

Doua fisiere .css (unul pentru cele standard, unul incarcat doar pentru IE), si un fisier .js. 3 request-uri in loc de 9, si o dimensiune totala de aprox. 6.5 ori mai mica. Fisierele sunt atat minified cat si gzipped, folosind librariile CSSTidy, respectiv Packer

Suna bine, dar ce se intampla cand …

PHP Class: AutoLoader

Oricine a adoptat metodologia OOP in lucrul cu PHP cu siguranta s-a bucurat la implementarea functiei __autoload in PHP 5, ce permite incarcarea automata a fisierului corespunzator unei clase la momentul invocarii acesteia.

Probleme

In ciuda potentialului imens adus de aceasta functionalitate, majoritatea cartilor de specialitate prezinta doar cel mai elementar exemplu pentru exploatarea acestei functii, si anume simpla incarcare a fisierului cu numele identic sau asemanator cu cel al clasei necesare:

1
2
3
function __autoload($class_name) {
    require_once $class_name . '.php';
    }

Imediat dupa trecerea entuziasmului, incepem sa observam minusurile acestei abordari:

Cerinte

Una din marile frumuseti ale programarii este ca permite transpunerea logicii gandirii umane intr-un limbaj “inteles” de catre tehnologiile disponibile. La fel este si cazul procesului decizional ce urmeaza:

Solutie

Dupa ce a trecut prin mai multe iteratii si versiuni (si probabil va mai trece), va invit sa descarcati si folositi versiunea actuala a clasei AutoLoader. Modul de utilizare este simplu, doar incarcati fisierul in care se afla aceasta, si folositi urmatoarea varianta a functiei __autoload:

1
2
3
4
5
6
7
8
9
function __autoload($class) {
	$class_dirs = array("./"); // array ce va contine directoarele in care vom cauta (inclusiv subdirectoarele acestora, recursiv)
	$autoloader = new AutoLoader($class_dirs, 'autoloader-cache.php');
	try {
		$autoloader->loadClass($class);
		} catch (Exception $e) {
			exit($e->getMessage());
			}
	}

Puteti vedea output-ul unui scurt exemplu aici, ce contine urmatorul cod PHP:

1
2
3
include("AutoLoader.class.php");
include("autoload.func.php");
new Test;

Avantaje

Dezavantaje

Unul din dezavantajele minore ale acestui sistem este imposibilitatea existentei a doua clase cu acelasi nume. In cazul in care numele a doua clase coincid, locatia primei intalnite la scanarea fisierelor va fi suprascrisa in fisierul de cache de catre cea de-a doua.

Cu toate ca in mod normal nu ar trebui sa existe doua clase denumite la fel, se impune in acest fel o atentie sporita in momentul utilizarilor de clase / plugin-uri “3rd party”, deoarece numele uneia din acestea poate coincide cu cel al unei clase deja existente.

Download

Puteti downloada atat clasa AutoLoader, cat si toate fisierele corespunzatoare exemplului anterior:

Download AutoLoader.class.php
Download exemplu

Ca de obicei, sunt foarte interesat de eventualele imbunatatiri / modificari pe care le-ati propune. De asemenea, daca depistati bug-uri, as fi recunoscator daca mi le-ati indica.

  • Arhiva