service component architecture for php · • components use php annotations both to declare their...
TRANSCRIPT
Service Component Architecture for PHP
Reusable components and effort-free web servicesMatthew Peters, IBM Hursley Park
1
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
2
How to find us
• Google for OSOA
• (Open Service Oriented Architecture)
3
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
4
SWG AB IncubatorsService Component Architecture
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.
5
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)
JSON-RPC binding
A component,
containing
business logic
A local component,
Same call stack
A web service
2. Be flexible about your dependencies1. Be flexible about
how you are called
Local binding
Web service binding
6
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
7
Scenario 1. Simplest
• A client script calling one local component
• What does the simplest SCA component look like?
GreetingComponentclient
8
Our first simple SCA component
• A PHP class
• @service annotation
• include for SCA.php
<?php
include 'SCA/SCA.php';
/*** @service*/class GreetingComponent{
public function greet($name) {
return 'hello ' . $name;}
}?>
GreetingComponent
9
Calling an SCA component from a client script
• Client script • Includes SCA.php• But is not itself a
component
• Uses SCA::getService()• getService takes a
path• Absolute or relative• Relative paths are
resolved against the location of the script
• getService returns a ‘proxy’ object:
• Enforces pass-by-value
<?php
include 'SCA/SCA.php';
$service = SCA::getService('./GreetingComponent.php');
echo $service->greet('PHP');
?>
GreetingComponentclient
"hello PHP"
10
Scenario 2. Multiple
• A local component calling other local components• How are the dependencies wired up?
ReversingComponent
ReversedGreeting
Component
GreetingComponent
client
"PHP olleh"
11
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
12
Dependencies• 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
• @binding.php• indicates how to find the
component• and that it is local• same rules as getService
<?php
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);
}}
?>
ReversedGreeting
Component
13
What have we got so far?
• Sample call stack:
ReversingComponent
ReversedGreeting
Component
GreetingComponent
client
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
SCA_Localproxy
breakpoint
"PHP olleh"
14
Scenario 3. Web Service
• A client script calling one remote component
• How to expose a web service binding
GreetingComponentclient
= SOAP Web service request/response
15
Exposing a web service binding• @binding
• Expose a web service binding
• Public methods are in the interface
• @param/@return• Need more information
about each method
<?php
include 'SCA/SCA.php';
/*** @service* @binding.ws*/
class GreetingComponent{
/*** @param string $name* @return string*/
public function greet($name) {
return 'hello ' . $name;}
}?>
GreetingComponent
= SOAP Web service request/response
16
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/GreetingComponent.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)
17
Generated WSDL
• Document/literal wrapped style• Message
formats are explicit within the schema
<?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>
...
18
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>...
19
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 -->
20
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
• Location can be a URL…• In which case the soap
extension will probably cache it
• Location can be a path• Relative paths resolved
against location of script
<?php
include 'SCA/SCA.php';
$service = SCA::getService('http://www.example.com/GreetingComponent.wsdl');
echo $service->greet();
?>
Greeting
Componentclient
= SCA_SoapProxy
21
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>
22
Scenario 4. Multiple web service
• Everything separated
ReversingComponent
ReversedGreeting
Component
GreetingComponent
client
= SOAP Web service request/response
23
A second remote component
• Annotations• @binding• @param• @return
• And generate WSDL as before
<?php
include 'SCA/SCA.php';
/*** @service* @binding.ws*/
class ReverseComponent{
/*** @param string $in* @return string*/function reverse ($in){
return strrev ($in);}
}?>
ReversingComponent
24
Remote dependencies
• @binding.wsfor remote component
• locates wsdl
<?phpinclude 'SCA/SCA.php';
/*** @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
25
What have we got now?• What have we achieved?
• Client <–> local <-> local • Client <-> remote <-> remote
• What had to change?• 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
• But the business logic remains unchanged
ReversingComponent
ReversedGreeting
Component
GreetingComponent
client
SOAP Web service request/response
26
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
38
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
39
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
40
41
A component exposing a JSON binding
• @binding.jsonrpc
• Generates .smd
• <url>?smd
• smd = service method description
<?phpinclude '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;}
}?>
42
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"}
} ]}
43
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);
}
44
JSON-RPC - POST• POST Style - Request
• POST Style - Response
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}
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 "}
45
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
46
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
47
The end