Service Component Architecture for PHP

Reusable components and effort-free web services Matthew Peters, IBM Hursley Park matthew_peters@uk.ibm.com

Background
• IBM Hursley Park, Winchester, UK
• http://www5.ibm.com/uk/locations/hursley_explore.html

• 2-3K people • Services, outsourcing • Product development: CICS, MQSeries, Java • Incubator group:
• Porting, experimenting with technologies from Java world, implementing and simplifying in PHP

1

How to find us
• Google for OSOA
• (Open Service Oriented Architecture)

2

Agenda
• Two slide-overview • Some simple SCA components (hello world–style)
• Components calling each other locally • Make them all run as web services • (Slides and Zend Studio)

• More SCA:
• Interoperability with other web services • Exceptions • Data Structures

• Work in progress: DOJO, JSON-RPC and SCA • Summary, Futures and Links

3

Service Component AB Incubators Architecture SWG for PHP
• SCA for PHP allows a PHP programmer to write reusable components (classes) in PHP, which can be called either locally, or remotely via Web Services, with an identical interface. • Components use PHP annotations both to declare their dependencies on other components, and to define the interface which they expose as a service. Business logic is kept separate from interface and dependencies. • Deploying a PHP component as a web service can be as simple as copying it into a web server’s document root.

4

Making your component reusable
• Do not entangle the business logic with the “wiring” 1. Be flexible about how you are called
• Expose as many ‘bindings’ as needed – make sure your business logic does not need to know how it was called

2. Be flexible about your dependencies
• Declare the dependencies – but make sure your business logic does not need to know how to resolve these • Ideally get something else to “wire up” the components (Inversion of Control; Dependency Injection patterns)

Local binding

A local component, Same call stack

1. Be flexible about Web service binding how you are called
JSON-RPC binding

A component, containing business logic

2. Be flexible about your dependencies

A web service

5

Four scenarios
1. One component called locally 2. One component calling two others 3. Make the single component expose a Web service binding 4. Make them all use web services • Same interface, minimal effort

6

Scenario 1. Simplest
• A client script calling one local component
• What does the simplest SCA component look like?
GreetingComponent

client

7

Our first simple SCA component
<?php

• A PHP class
• @service annotation • include for SCA.php

include 'SCA/SCA.php'; /** * @service */ class GreetingComponent { public function greet($name) { return 'hello ' . $name; } } ?>

GreetingComponent

8

Calling an SCA component from a client script
• Client script
• Includes SCA.php • But is not itself a component
<?php include 'SCA/SCA.php'; $service = SCA::getService('./GreetingComponent.php');

• Uses SCA::getService()
• getService takes a path

echo $service->greet('PHP'); • Absolute or relative • Relative paths are resolved against the ?> location of the script "hello PHP"

• getService returns a ‘proxy’ object: • Enforces pass-byvalue

client

GreetingComponent

9

Scenario 2. Multiple
• A local component calling other local components
• How are the dependencies wired up?

GreetingComponent

client

ReversedGreeting Component

ReversingComponent

"PHP olleh"

10

Add a second component
• Like GreetingComponent:
• @service • Include for SCA.php
<?php include 'SCA/SCA.php'; /** * @service */ class ReversingComponent { function reverse ($in) { return strrev ($in); } } ?>

ReversingComponent

11

Dependencies
<?php

• Dependencies are declared • annotated with @reference
• The instance variable following will be assigned a proxy • Hence needs to be public • Initialised before any business logic

include 'SCA/SCA.php'; /** * @service */ class ReversedGreetingComponent { /** * @reference * @binding.php GreetingComponent.php */ public $greeting_component; /** * @reference * @binding.php ReversingComponent.php */ public $reversing_component; public function greet($name) { $greeting = $this-> greeting_component->greet($name); return $this->reversing_component->reverse($greeting); } } ?>

• @binding.php
• indicates how to find the component • and that it is local • same rules as getService

ReversedGreeting Component

12

What have we got so far?
• Sample call stack:
reverse( $in ) C:\Program Files\Apache Group\Apache2\htdocs\Konferenz\ReversingComponent.php line 13 __call( $method_name, $arguments ) c:\php\PEAR\SCA\SCA_LocalProxy.php line 109 greet( $name ) C:\Program Files\Apache Group\Apache2\htdocs\Konferenz\ReversedGreetingComponent.php line 24 __call( $method_name, $arguments ) c:\php\PEAR\SCA\SCA_LocalProxy.php line 109 main( ) C:\Program Files\Apache Group\Apache2\htdocs\Konferenz\client2.php line 7

breakpoint

GreetingComponent

SCA_Local proxy

client

ReversedGreeting Component

ReversingComponent

"PHP olleh" 13

Scenario 3. Web Service
• A client script calling one remote component
• How to expose a web service binding
client
GreetingComponent

= SOAP Web service request/response

14

Exposing a web service binding
<?php

• @binding
• Expose a web service binding • Public methods are in the interface

include 'SCA/SCA.php'; /** * @service * @binding.ws */ class GreetingComponent { /** * @param string $name * @return string */ public function greet($name) { return 'hello ' . $name; } } ?>

• @param/@return
• Need more information about each method

GreetingComponent

15 = SOAP Web service request/response

Generating the WSDL
• Generated in response to HTTP GET with ?wsdl
• http://www.example.com/GreetingComponent.php?wsdl • Do it in a browser • file_get_contents('http://www.example.com/GreetingCom ponent.php?wsdl');

• Currently cached in the same directory as the component
• http://www.example.com/GreetingComponent.wsdl

• (in future need to do something different to avoid need for write access into htdocs)

16

Generated WSDL
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xsi:type="tDefinitions« xmlns:tns2="http:// GreetingComponent" xmlns:tns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns3="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="http://GreetingComponent"> <types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://GreetingComponent"> <xs:element name="greet"> <xs:complexType> <xs:sequence> <xs:element name=“name" type="xs:string" nillable="true"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="greetResponse"> <xs:complexType> <xs:sequence> <xs:element name="greetReturn" type="xs:string" nillable="true"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> </types> ...

• Document/literal wrapped style
• Message formats are explicit within the schema

17

Generated WSDL
• message … • post … • binding …
... <message name="greetRequest"> <part name="greetRequest" element="tns2:greet"/> </message> <message name="greetResponse"> <part name="return" element="tns2:greetResponse"/> </message> <portType name="GreetingComponentPortType"> <operation name="greet"> <input message="tns2:greetRequest"/> <output message="tns2:greetResponse"/> </operation> </portType> <binding name="GreetingComponentBinding" type="tns2:GreetingComponentPortType"> <operation name="greet"> <input> <tns3:body xsi:type="tBody" use="literal"/> </input> <output> <tns3:body xsi:type="tBody" use="literal"/> </output> <tns3:operation xsi:type="tOperation" soapAction=""/> </operation> <tns3:binding xsi:type="tBinding" transport="http://schemas.xmlsoap.org/soap/http" style="document"/> </binding> ...

18

Generated WSDL
• Location attribute is decided once the file is in place
• Currently using the URL to determine the location with respect to the document root • (in future need to do something different to cope with proxies, rewriting, firewalls)

• Ends with a distinctive comment
• Special handling of exceptions when one component talks to another
... <service name="GreetingComponentService"> <port name="GreetingComponentPort" binding="tns2:GreetingComponentBinding"> <tns3:address xsi:type="tAddress" location="http://www.example.com/GreetingComponent.php"/> </port> </service> </definitions> <!-- this line identifies this file as WSDL generated by SCA for PHP. Do not remove -->

19

Calling a remote SCA component from a script
• SCA::getService() takes the location of the WSDL
• Once again, $service is a proxy: SCA_SoapProxy • Proxy contains within it an instance of the ext/SOAP client
<?php include 'SCA/SCA.php'; $service = SCA::getService( 'http://www.example.com/GreetingComponent.wsdl'); echo $service->greet(); ?>

• Location can be a URL…
• In which case the soap extension will probably cache it
= SCA_SoapProxy

• Location can be a path
• Relative paths resolved against location of script

client

Greeting Component

20

Sample Soap request
• Document/literal wrapped, so <greet> element enclosing <name> element
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <tns:greet xmlns= "http://GreetingComponent" xmlns:tns= "http://GreetingComponent" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="greet"> <name>PHP</name> </tns:greet> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

21

Scenario 4. Multiple web service
• Everything separated

GreetingComponent

client

ReversedGreeting Component

ReversingComponent

= SOAP Web service request/response

22

A second remote component
<?php

• Annotations
• @binding • @param • @return

include 'SCA/SCA.php'; /** * @service * @binding.ws */ class ReverseComponent { /** * @param string $in * @return string */ function reverse ($in) { return strrev ($in); } } ?>

• And generate WSDL as before

ReversingComponent

23

Remote dependencies
<?php include 'SCA/SCA.php';

• @binding.ws for remote component
• locates wsdl

/** * @service * @binding.ws */ class ReversedGreetingComponent { /** * @reference * @binding.ws GreetingComponent.wsdl */ public $greeting_component; /** * @reference * @binding.ws ReversingComponent.wsdl */ public $reversing_component; /** * @param string $name * @return string */ public function greet($name) { $greeting = $this-> greeting_component->greet($name); return $this->reversing_component->reverse($greeting); } } ?>

ReversedGreeting Component

24

What have we got now?
• • What have we achieved?
• • • • • Client <–> local <-> local Client <-> remote <-> remote Arguments to getService(), or @binding.php to @binding.ws Annotatations to describe the interface in more detail Generating WSDL on demand; otherwise deployment is just copying the component

What had to change?

But the business logic remains unchanged

SOAP Web service request/response
GreetingComponent

client

ReversedGreeting Component

ReversingComponent

25

Interlude
• “But, you have to change the files themselves…”
• True, but changing an annotation in an interpreted file – is that different from a line in a config file?

• Essential point is that “wiring” and business logic are separated
• Same file, but in different worlds • Wiring is declarative, in annotations • Business logic is imperative, in code

26

Futures
• Annotation overriding
• Changing service targets, bindings, properties from outside

• PHP classes rather than xsds for data structures • Simple database services • Other bindings
• Atompub, REST (XML and JSON), RSS

38

DOJO, JSON-RPC and SCA
• DOJO is a user interface widget set written in JavaScript
• Can talk back to the server asynchronously - AJAX style • Can use JSON-RPC to do so

• JSON = JavaScript Object Notation
• • • • Like a simplified XML Name/value pairs { for structure [ array

• A JSON-RPC interface can be defined in SMD = Service Method Description
• Like a simplified WSDL • Also written in JSON

• SCA components can expose a JSON-RPC binding too

39

40

A component exposing a JSON binding
• @binding.jsonrpc • Generates .smd
• <url>?smd • smd = service method description
<?php include 'SCA/SCA.php'; /** * @service * @binding.jsonrpc */ class HelloService { /** * @param string $name The name to say hello to * @return string The string hello <name> */ public function sayHello ($name) { return ‘hello ‘ . $name; } } ?>

41

HelloService.php?smd
• Defines a service that has:
• One method sayHello(), with … • One parameter, name
{ "SMDVersion":".1", "serviceType":"JSON-RPC", "serviceURL":"http://localhost/Samples/JsonRpc/hello/HelloService.php", "methods": [ { "name":"sayHello", "parameters": [ { "name":"name", "type":"string“ } ], "return": {"type":"string"} } ] }

42

A DOJO function to call sayHello
• Obtain .smd • Issue the call
function sayHello() { var SCA = new dojo.rpc.JsonService({smdUrl: "HelloService.php?smd"}); var inputfield = document.getElementById("hellotext").value; SCA.sayHello(inputfield).addCallback(handleResponse); }

43

JSON-RPC - POST
• POST Style - Request
POST /json-rpc/HelloService.php HTTP/1.1 Host: localhost:8081 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Content-Type: application/json-rpc Content-Length: 48 Pragma: no-cache Cache-Control: no-cache {"params":["Hello!"],"method":"sayHello","id":1}

• POST Style - Response
HTTP/1.1 200 OK Date: Tue, 03 Oct 2006 18:14:35 GMT Server: Apache/2.0.55 (Win32) PHP/5.2.0RC5-dev X-Powered-By: PHP/5.2.0RC5-dev Content-Length: 27 Keep-Alive: timeout=15, max=89 Connection: Keep-Alive Content-Type: application/json-rpc {“return":"Hello "}

44

Links
• SCA for PHP homepage
• http://osoa.org/display/PHP/SOA+PHP+Homepage

• Discussion Group
• http://groups.google.com/group/phpsoa/

• Blog
• http://www.ibm.com/developerworks/blogs/page/phpblog

• SDO for PHP
• http://www.php.net/sdo

45

Acknowledgements
• Other members of the SCA for PHP team
• Graham Charters, Megan Beynon, Chris Miller, Caroline Maynard, Simon Laws

• Special thanks to Dmitry Stogov for help with the SOAP extension, serialising and de-serialising SDOs

46

The end

47

Sign up to vote on this title
UsefulNot useful