oriolrius.cat

Des del 2000 compartiendo sobre…

Tag: php5

PHP5: passant i tornant objectes com a paràmetre en una crida SOAP (Part II)

Reading time: 5 – 8 minutes

El mes de març vaig escriure un article titulat gairebé igual que aquest, en aquell article explicava com s’ho feia el PHP5 per rebre i retornar tipus complexes (p.e. objectes) com a paràmetres de crides SOAP. Doncs bé havia trobat una cosa que em tenia una mica mosca i és que quan enviava una objecte del client cap al servidor o alrevés en perdia el tipus. Doncs bé aquesta tarda he descobert com aconseguir fer això sense perdren el tipus. Ja sabeu que quan se’m posa una cosa al cap…

El tema no és senzill i el generador de WSDL del ZendStudio és una mica tiquis-miquis fent aquestes coses així doncs s’ha de fer tot molt maco perquè ho entengui el generador WSDL. De fet, la gràcia esta en definir el tipus complexe dins del fitxer WSDL, per això el generador necessita que li donem tota la informació com ell vol sinó no és capaç de generar bé el fitxer.

En l’exemple que us poso a continuació he creat la classe persona amb tres atributs dos de públics i un de privat. El privat al contrari del que comentava en l’article anterior no perd el seu valor. Així doncs, en principi tenim l’objecte 100% replicat amb el mètode que ara us explicaré.

La classe persona:

<?php
/**
 * classe persona
 *
 */
class persona {
	/**
	 * propietat nom
	 *
	 * @var string $nom
	 * @var string $cognom
	 * @var string $dni
	 */
	public $nom;
	private $cognom;
	public $dni;
	/**
	 * setter function
	 *
	 * @param string $nom
	 */
	function set($nom) {
		$this-?>nom=$nom;
	}
	/**
	 * setter cognom
	 *
	 *  @param string $cognom
	 */
	function setCognom($cognom)
	{
		$this-?>cognom=$cognom;
	}
	/**
	 * getter function
	 *
	 * @return string valor a tornar
	 */
	function get()
	{
		return $this-?>nom;
	}
	/**
	 * getter cognom
	 *
	 * @return string valor a tornar
	 */
	function getCognom()
	{
		return $this-?>cognom;
	}
}
?>

Fixeu-vos que comentem el codi segons la sintaxis del phpDoc això es fa perquè és d’aquests comentaris d’on el generador WSDL extreurà els tipus de les variables per poder-les exportar sense perdre informació. La propietat dni no té ni getter ni setter perquè hi accedirem directament. Per la resta de coses és una classe completament normal i senzilla.

El servidor és força simple:

<?php
require_once("persona.class.php");
/**
 * servidor soap
 *
 */
class soapservice {
	/**
	 * funcio soap publicada
	 *
	 * @param persona $per
	 * @return persona
	 */
	function peticion($per) {
		$per->dni="38147000x";
		return $per;
  }
}
	$server = new SoapServer("server.wsdl",array("classmap"=>array("persona"=>"persona")));
$server->setClass("soapservice");
$server->handle();
?>

Comentar que la classe soapservice rep un objecte de tipus persona afegeix un valor a la propietat dni i retorna el mateix objecte. Aquí també hem de documentar el codi segons la sintaxis del phpDoc així el model WSDL ens dirà explicitament el tipus dels objectes que rep i torna el mètode peticion.

Pel que fa a la creació de l’objecte server del tipus SoapServer fixeu-vos que mapegem amb l’opció classmap l’objecte de tipus persona que es passa com a paràmetre amb la classe local també anomenada persona. Gràcies a aquest mapeig quan rebem l’objecte per al soapservice aquest ja és del tipus persona.

Ara toca veure el codi del client, aquest codi també té un mapeig igual que el servidor de SOAP, però en aquest cas serveix per capturar la sortida del servidor i com que l’objecte que es torna a la sortida també és del tipus persona el classmap és igual que el del servidor.

<?php
require_once("persona.class.php");
// creem objecte tipus persona
$myPersona=new persona;
$myPersona->set("el_nom");
$myPersona->setCognom("el_cognom");
	$client = new SoapClient("server.wsdl",array("classmap"=>array("persona"=>"persona")));
	$localPersona=$client->peticion($myPersona);
	var_dump($client->__getFunctions());
print_r($localPersona);
echo $localPersona->getCognom();
?>

Una de les coses interssants que fem és preguntar quines funcions s’estan publicant al servidor SOAP. Aquesta informació esta descrita al WSDL i la funció __getFunctions() ens la formateja per humans. Així doncs podem veure que hi ha disponible la funció peticion que té com a paràmetre un objecte del tipus persona i que retorna un altre objecte de tipus.

array(1) {
  [0]=>
  string(30) "persona peticion(persona $per)"
}

Després mostrem (print_r) l’objecte localPersona que ens ha tornat la crida al mètode peticion ($localPersona=$client->peticion($myPersona);):

persona Object
(
    [nom] => el_nom
)

Finalment fem una prova de concepte i obtenim el valor de la propietat nom a través del getter, o sigui, echo $localPersona->getCognom();:

el_nom 

Com podeu veure ha quedat ben demostrat que és relativament senzill enviar i rebre tipus complexes a través dels serveis SOAP. Tota la gràcia esta en el fitxer WSDL, a continuació us enganxo el que m’ha generat el Zend Studio per fer aquestes proves fixeu-vos com al prinicipi de tot el primer que fa és declarar el tipus complexe de dades que ha de traspassar, en el nostre cas la classe persona.

<?xml version='1.0' encoding='UTF-8'?>
	<!-- WSDL file generated by Zend Studio. -->
	<definitions name="server" targetNamespace="urn:server" xmlns:typens="urn:server" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:typens0="http://oriol.joor.net/fotoprix/web/cu.php/soap/server">
	<types>
		<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:server">
			<xsd:complexType name="persona">
				<xsd:all>
					<xsd:element name="nom" type="xsd:string"/>
					<xsd:element name="cognom" type="xsd:string"/>
					<xsd:element name="dni" type="xsd:string"/>
				</xsd:all>
			</xsd:complexType>
		</xsd:schema>
	</types>
	<message name="peticion">
		<part name="per" type="typens:persona"/>
	</message>
	<message name="peticionResponse">
		<part name="peticionReturn" type="typens:persona"/>
	</message>
	<portType name="soapservicePortType">
		<documentation>
			servidor soap
		</documentation>
		<operation name="peticion">
			<documentation>
				provem de descriure
			</documentation>
			<input message="typens:peticion"/>
			<output message="typens:peticionResponse"/>
		</operation>
	</portType>
	<binding name="soapserviceBinding" type="typens:soapservicePortType">
		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
		<operation name="peticion">
			<soap:operation soapAction="urn:soapserviceAction"/>
			<input>
				<soap:body namespace="urn:server" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
			</input>
			<output>
				<soap:body namespace="urn:server" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
			</output>
		</operation>
	</binding>
	<service name="serverService">
		<port name="soapservicePort" binding="typens:soapserviceBinding">
			<soap:address location="http://oriol.joor.net/soap/server.php"/>
		</port>
	</service>
</definitions>

php: SimpleXML i xml2array

Reading time: < 1 minute

Dues funcions potentissimes de PHP, sobretot ara que cada dos per tres hem de treballar amb fitxers XML.

$xml = simplexml_load_file('fitxer.xml');

Llegeix el fitxer.xml del disc dur i el converteix en un objecte de la classe SimpleXMLElement. Per veure en què es converteix es pot usar print_r.

Ara que ja tenim les dades en forma d’objecte si encara no ens agraden podem convertir-les en un array amb una sola funció:

$varArray = get_object_vars($objXml);

També amb un print_r podem veure un array dels de tota la vida.

Sovint la potència esta en la simplicitat… val la pena no perdre de vista aquestes funcions.

cookbook: importar projecte de symfony al subversion

Reading time: 2 – 2 minutes

Petit cookbook de com hem d’importar un projecte que estiguem programant amb el framework symfony. Primer creem el directori del repositori on volem pujar el projecte, anem al directori del projecte. Borrem els directoris log i cache del qual no ens interessa mantenir un control de versions. I després importem el projecte.

svn mkdir -m "NOTA: Creem directori on hi posarem projecte" http://exemple.com/svn/repos/projecte/dev
cd directori_projecte_symfony
rm -rf log
rm -rf cache
svn import -m "NOTA: importem primera versio del projecte" . http://exemple.com/svn/repos/projecte/dev

Ara movem el directori actual del projecte i en creem un de nou, que ja tindrà control de versions. Fem un checkout (co) de la versió que em importat abans i defenim que els directoris log i cache no s’importaràn mai quan fem un commit (ci). Després creem els directoris en qüestió i els assignem un propietari igual al del procés d’apache perquè aquest el pugui escriure-hi mentre esta execucutant el nostre projecte symfony.

mv directori_projecte_symfony directori_projecte_symfony.bak
mkdir directori_projecte_symfony_amb_svn
cd directori_projecte_symfony_amb_svn
svn co http://exemple.com/svn/repos/projecte/dev .
svn propedit svn:ignore .
log
cache
mkdir log
mkdir cache
chown apache:apache log
chown apache:apache cache

PHP5: passant objectes com a paràmetre en una crida SOAP

Reading time: 2 – 3 minutes

Update: LES CONCLUSIONS D’AQUEST ARTICLE SÓN ERRONEES MIREU: PHP5: passant i tornant objectes com a paràmetre en una crida SOAP (Part II)

Treballant en un projecte que estem programant en PHP5 em va sortir aquest dubte, sobretot després de llegir les limitacions dels servidors SOAP que implementa PHP5. El tema finalment té una resposta positiva: SI, PERO…. A continuació us poso un exemple de com funciona aquest tema amb el PHP5 i quines limitacions té.

Definim el servidor SOAP que com podeu veure és senzillissim, l’únic que fa el servidor és re-enviar el mateix objecte que ha rebut.

<php
class SOAPservice {
        function peticion($per) {
                return $per;
  }
}
$server = new SoapServer("server.wsdl");
$server->setClass("SOAPservice");
$server->handle();
?>

A continuació podeu veure el codi del client, és un codi molt senzill de només dues línies. Com podem veure primer definim la classe persona i abans de cridar el client SOAP instanciem la classe, després usem aquest objecte com a paràmetre a la crida SOAP. Capturem l’objecte que ens retorna el servidor SOAP i a continuació mostrem el seu contingut amb l’ordre print_r.

<?php
/*
 definim classe persona
*/
class persona {
        public $nom;
        function setNom($nom) {
                $this->nom=$nom;
        }
        function setCognom($cognom) {
                $this->cognom=$cognom;
        }
        function getNom() {
                return $this->nom;
        }
        function getCognom() {
                return $this->cognom;
        }
}
/*
 creem objecte tipus persona
*/
$myPersona=new persona;
$myPersona->setNom("el_nom");
$myPersona->setCognom("el_cognom");
/*
 fem una crida SOAP amb un objecte com a parametre
*/
$client = new SoapClient("server.wsdl");
$myObj=$client->peticion($myPersona);
/*
 mostrem objecte generic que retorna la crida SOAP
*/
print_r($myObj);
?>

Si mirem la sortida que ens dona quan cridem el client podem veure que l’objecte que torna no és del tipus persona sinó d’un tipus intern del PHP5 anomenat stdClass. Aquest objecte només disposa dels atributs públics. No podem accedir als atributs privats o protegits i hem perdut tots els mètodes siguin del tipus que siguin.

stdClass Object
(
    [nom] => el_nom
    [cognom] => el_cognom
)

Això és tot el que he pogut aconseguir, no és gaire però com a mínim a mi m’ha estat suficient.

PHP4 vs PHP5

Reading time: 2 – 3 minutes

Tot llegint el PHP|Architect’s Guide to PHP Design Patterns (isbn: 0973589825) vaig trobar fa un parell de dies un petit llistat de quines eres les diferencies fonamentals entre PHP4 i PHP5, a part del tòpic que amb el motor de Zend 2.1 ara tenim suport de OOP i de que tothom em digui que el model d’objectes que usa PHP5 encara és molt dolent doncs bé amb això podrem saber fins a quin punt ho és.

  • Object handles
  • Better constructors (uniform name, changing $this not allowed)
  • Destructors now exist
  • Visibility (public, protected, private for methods and attributes)
  • Exceptions (an alternative to triggering errors using the new try{} catch{} syntax)
  • Classconstants (defines using the class for a name space)
  • Reflection (dynamic examination of classes, methods and arguments)
  • Type hinting (specifying expected classes or interfaces for method arguments)

També hi podem trobar altres novetats una mica menys conegudes:

  • New magic methods (__get() and __set() allow you to control attribute access; __call() lets you dynamically intercept all method calls to the object; __sleep() and __wakeup() let you override serialization behavior; and __toString() lets you control how an object represents itself when cast as a string)
  • Autoloading (allows the end user to try to automatically load the class the first time a reference to it is made)
  • Final (do not allow a method or a class to be overridden by subclasses)

A més hi ha coses com la part d’object handles que donen molt de si:

  • Create an object by reference, as in $obj =& new Class;
  • Pass an object by reference, like function funct(&$obj_param) {}
  • Catch an object by reference function &some_funct() {} $returned_obj =&
    some_funct()

En escència el que més m’ha agradat ho teniu resumit aquí, ja sé que esta en anglès però em feia una mandra brutal traduir-ho. Si m’animo un dia d’aquests donaré més detalls de com aprofitar aquestes avantatges per implementar design patterns amb PHP5. Realment ja comences a sentir-te com un programador de veritat quan veus que el PHP5 no té cap problema en suportar estructures que fins ara li quedaben tan lluny.

PHP5 – Generant WSDL amb Zend Studio 5.1

Reading time: 3 – 5 minutes

Al programar un webservice amb el PHP5 és més que trivial tal com podem llegir a la documentació sobre SOAP (local) del propi PHP. El problema ve quan volent aprofitar la simplicitat dels serveis SOAP toca programar a mà el WSDL. O sigui, agafa i posat a definir la interficie en XML de la/es classe/s que vols exportar a través del webservice, o sigui, una missión imposible.

La meva sorpresa ha estat veure que després d’adpotar el Zend Studio com a IDE de programació del meu codi PHP he vist que disposava d’un assistent ben senzill d’usar per tal de generar automàticament aquest fitxer WSDL. La cosa és ben senzilla imaginem que usem el codi de l’exemple de la web de Zend, com comentava abans.

El servidor SOAP:

<?php
class QuoteService {
  private $quotes = array("ibm" => 98.42);
	function getQuote($symbol) {
    if (isset($this->quotes[$symbol])) {
      return $this->quotes[$symbol];
    } else {
      throw new SoapFault("Server","Unknown Symbol '$symbol'.");
    }
  }
}
	$server = new SoapServer("server2.wsdl");
$server->setClass("QuoteService");
$server->handle();
?>

El client, encara més simple que el servidor:

<?php
  $client = new SoapClient("server2.wsdl");
  print($client->getQuote("ibm"));
?>

Ara que ja tenim el codi en el nostre Zend, només cal que anem a:

menu.png

Cal seguir l’assistent que no és massa difícil, aquí en teniu els passos capturats per l’exemple del que parlem:

wizard-wsdl-1.png
wizard-wsdl-2.png

Podeu veure com s’exporten automàticament les classes dels arxius que seleccioneu, cal que poseu al costat de cada classe exportada la URL a través de la qual s’accedeix al servei:

wizard-wsdl-3.png

Al final obtenim el fitxer WSDL, en el nostre cas aquest:

<?xml version='1.0' encoding='UTF-8'?>
	<!-- WSDL file generated by Zend Studio. -->
	<definitions name="server2" targetNamespace="urn:server2" xmlns:typens="urn:server2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">
	<message name="getQuote">
		<part name="symbol"/>
	</message>
	<message name="getQuoteResponse">
		<part name="getQuoteReturn"/>
	</message>
	<portType name="QuoteServicePortType">
		<operation name="getQuote">
			<input message="typens:getQuote"/>
			<output message="typens:getQuoteResponse"/>
		</operation>
	</portType>
	<binding name="QuoteServiceBinding" type="typens:QuoteServicePortType">
		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
		<operation name="getQuote">
			<soap:operation soapAction="urn:QuoteServiceAction"/>
			<input>
				<soap:body namespace="urn:server2" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
			</input>
			<output>
				<soap:body namespace="urn:server2" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
			</output>
		</operation>
	</binding>
	<service name="server2Service">
		<port name="QuoteServicePort" binding="typens:QuoteServiceBinding">
			<soap:address location="http://oriol.joor.net/soap/server2.php"/>
		</port>
	</service>
</definitions>

Si sou observadors notareu que aquest fitxer és lleugerament diferent al de la documentació que mostra Zend. Però jo l’he provat i funciona perfectament així doncs no m’hi trencaré el cap en saber a què es deuen aquests petits canvis al implmentar aquest format XML.

…i 1000!!!

Reading time: < 1 minute

Doncs després de més de 5 anys escribint al meu blog, ús informo que aquest és el post 1.000, per fi! hi ha molta gent que amb menys d’1 any de blog ja n’ha escrit més que jo de posts. Però bé suposo que el merit d’aquest blog no és el número d’articles sinó el temps que fa que els estic escribint 🙂

Aprofitant aquest motiu de celebració, també us informo que estreno verisó del blogcms, que no heu notat res, ja ho sé… hi hi hi! només he actualitzat el motor del sistema. A més també us informo que ara esta corrent amb un PHP 5.1.2 i no amb un PHP 4 com fins ara. Tinc intenció d’aprofundí molt més amb la programació orientada a objectes en PHP. Ja anireu tenint notícies del tema.