1 <?xml version="1.0" encoding="UTF-8"?>
3 <sect1 id="zend.amf.server">
4 <title>Zend_Amf_Server</title>
7 <classname>Zend_Amf_Server</classname> provides an <acronym>RPC</acronym>-style server for
8 handling requests made from the Adobe Flash Player using the <acronym>AMF</acronym>
9 protocol. Like all Zend Framework server classes, it follows the SoapServer
10 <acronym>API</acronym>, providing an easy to remember interface for creating servers.
13 <example id="zend.amf.server.basic">
14 <title>Basic AMF Server</title>
17 Let's assume that you have created a class <classname>Foo</classname> with a
18 variety of public methods. You may create an <acronym>AMF</acronym> server using the
22 <programlisting language="php"><![CDATA[
23 $server = new Zend_Amf_Server();
24 $server->setClass('Foo');
25 $response = $server->handle();
30 Alternately, you may choose to attach a simple function as a
34 <programlisting language="php"><![CDATA[
35 $server = new Zend_Amf_Server();
36 $server->addFunction('myUberCoolFunction');
37 $response = $server->handle();
42 You could also mix and match multiple classes and functions. When
43 doing so, we suggest namespacing each to ensure that no method name
44 collisions occur; this can be done by simply passing a second string
45 argument to either <methodname>addFunction()</methodname> or
46 <methodname>setClass()</methodname>:
49 <programlisting language="php"><![CDATA[
50 $server = new Zend_Amf_Server();
51 $server->addFunction('myUberCoolFunction', 'my')
52 ->setClass('Foo', 'foo')
53 ->setClass('Bar', 'bar');
54 $response = $server->handle();
59 The <classname>Zend_Amf_Server</classname> also allows services to be dynamically
60 loaded based on a supplied directory path. You may add as many directories as you wish
61 to the server. The order that you add the directories to the server will be the
62 order that the <acronym>LIFO</acronym> search will be performed on the directories to
63 match the class. Adding directories is completed with the
64 <methodname>addDirectory()</methodname> method.
67 <programlisting language="php"><![CDATA[
68 $server->addDirectory(dirname(__FILE__) .'/../services/');
69 $server->addDirectory(dirname(__FILE__) .'/../package/');
73 When calling remote services your source name can have underscore ("_") and dot (".")
74 directory delimiters. When an underscore is used <acronym>PEAR</acronym> and Zend
75 Framework class naming conventions will be respected. This means that if you call the
76 service com_Foo_Bar the server will look for the file
77 <filename>Bar.php</filename> in the each of the included paths at
78 <filename>com/Foo/Bar.php</filename>. If the dot notation is used for your remote
79 service such as <filename>com.Foo.Bar</filename> each included path will have
80 <filename>com/Foo/Bar.php</filename> append to the end to autoload
81 <filename>Bar.php</filename>
85 All <acronym>AMF</acronym> requests sent to the script will then be handled by the
86 server, and an <acronym>AMF</acronym> response will be returned.
91 <title>All Attached Methods and Functions Need Docblocks</title>
94 Like all other server components in Zend Framework, you must document your class
95 methods using <acronym>PHP</acronym> docblocks. At the minimum, you
96 need to provide annotations for each required argument as well as
97 the return value. As examples:
100 <programlisting language="php"><![CDATA[
101 // Function to attach:
104 * @param string $name
105 * @param string $greeting
108 function helloWorld($name, $greeting = 'Hello')
110 return $greeting . ', ' . $name;
114 <programlisting language="php"><![CDATA[
120 * @param string $name
121 * @param string $greeting
124 public function hello($name, $greeting = 'Hello')
126 return $greeting . ', ' . $name;
132 Other annotations may be used, but will be ignored.
136 <sect2 id="zend.amf.server.flex">
137 <title>Connecting to the Server from Flex</title>
140 Connecting to your <classname>Zend_Amf_Server</classname> from your Flex
141 project is quite simple; you simply need to point your endpoint <acronym>URI</acronym>
142 to your <classname>Zend_Amf_Server</classname> script.
146 Say, for instance, you have created your server and placed it in the
147 <filename>server.php</filename> file in your application root, and thus the
148 <acronym>URI</acronym> is <filename>http://example.com/server.php</filename>. In this
149 case, you would modify your <filename>services-config.xml</filename> file to set the
150 channel endpoint uri attribute to this value.
154 If you have never created a <filename>service-config.xml</filename> file you can do so
155 by opening your project in your Navigator window. Right click on the project name and
156 select 'properties'. In the Project properties dialog go into 'Flex Build Path' menu,
157 'Library path' tab and be sure the '<filename>rpc.swc</filename>' file is added to your
158 projects path and Press Ok to close the window.
162 You will also need to tell the compiler to use the
163 <filename>service-config.xml</filename> to find the RemoteObject endpoint. To do this
164 open your project properties panel again by right clicking on the project folder from
165 your Navigator and selecting properties. From the properties popup select 'Flex
166 Compiler' and add the string: <command>-services "services-config.xml"</command>. Press
167 Apply then OK to return to update the option. What you have just done is told the Flex
168 compiler to look to the <filename>services-config.xml</filename> file for runtime
169 variables that will be used by the RemotingObject class.
173 We now need to tell Flex which services configuration file to use for connecting to
174 our remote methods. For this reason create a new
175 '<filename>services-config.xml</filename>' file into your Flex project src folder. To
176 do this right click on the project folder and select 'new' 'File' which will popup a
177 new window. Select the project folder and then name the file
178 '<filename>services-config.xml</filename>' and press finish.
182 Flex has created the new <filename>services-config.xml</filename> and has it open. Use
183 the following example text for your <filename>services-config.xml</filename> file. Make
184 sure that you update your endpoint to match that of your testing server. Make sure you
188 <programlisting language="xml"><![CDATA[
189 <?xml version="1.0" encoding="UTF-8"?>
192 <service id="zend-service"
193 class="flex.messaging.services.RemotingService"
194 messageTypes="flex.messaging.messages.RemotingMessage">
195 <destination id="zend">
197 <channel ref="zend-endpoint"/>
206 <channel-definition id="zend-endpoint"
207 class="mx.messaging.channels.AMFChannel">
208 <endpoint uri="http://example.com/server.php"
209 class="flex.messaging.endpoints.AMFEndpoint"/>
210 </channel-definition>
216 There are two key points in the example. First, but last in the
217 listing, we create an <acronym>AMF</acronym> channel, and specify the endpoint as the
218 <acronym>URL</acronym> to our <classname>Zend_Amf_Server</classname>:
221 <programlisting language="xml"><![CDATA[
222 <channel-definition id="zend-endpoint"
223 <endpoint uri="http://example.com/server.php"
224 class="flex.messaging.endpoints.AMFEndpoint"/>
225 </channel-definition>
229 Notice that we've given this channel an identifier, "zend-endpoint".
230 The example create a service destination that refers to this channel,
231 assigning it an ID as well -- in this case "zend".
235 Within our Flex <acronym>MXML</acronym> files, we need to bind a RemoteObject to the
236 service. In <acronym>MXML</acronym>, this might be done as follows:
239 <programlisting language="xml"><![CDATA[
240 <mx:RemoteObject id="myservice"
241 fault="faultHandler(event)"
242 showBusyCursor="true"
247 Here, we've defined a new remote object identified by "myservice"
248 bound to the service destination "zend" we defined in the
249 <filename>services-config.xml</filename> file. We then call methods on it
250 in our ActionScript by simply calling "myservice.<method>".
254 <programlisting language="ActionScript"><![CDATA[
255 myservice.hello("Wade");
259 When namespacing, you would use
260 "myservice.<namespace>.<method>":
263 <programlisting language="ActionScript"><![CDATA[
264 myservice.world.hello("Wade");
268 For more information on Flex RemoteObject invocation, <ulink
269 url="http://livedocs.adobe.com/flex/3/html/help.html?content=data_access_4.html">
270 visit the Adobe Flex 3 Help site</ulink>.
274 <sect2 id="zend.amf.server.errors">
275 <title>Error Handling</title>
278 By default, all exceptions thrown in your attached classes or
279 functions will be caught and returned as <acronym>AMF</acronym> ErrorMessages. However,
280 the content of these ErrorMessage objects will vary based on whether
281 or not the server is in "production" mode (the default state).
285 When in production mode, only the exception code will be returned.
286 If you disable production mode -- something that should be done for
287 testing only -- most exception details will be returned: the
288 exception message, line, and backtrace will all be attached.
292 To disable production mode, do the following:
295 <programlisting language="php"><![CDATA[
296 $server->setProduction(false);
300 To re-enable it, pass a <constant>TRUE</constant> boolean value instead:
303 <programlisting language="php"><![CDATA[
304 $server->setProduction(true);
308 <title>Disable production mode sparingly!</title>
311 We recommend disabling production mode only when in development.
312 Exception messages and backtraces can contain sensitive system
313 information that you may not wish for outside parties to access.
314 Even though <acronym>AMF</acronym> is a binary format, the specification is now
315 open, meaning anybody can potentially deserialize the payload.
320 One area to be especially careful with is <acronym>PHP</acronym> errors themselves.
321 When the <property>display_errors</property> <acronym>INI</acronym> directive is
322 enabled, any <acronym>PHP</acronym> errors for the current error reporting level are
323 rendered directly in the output -- potentially disrupting the <acronym>AMF</acronym>
324 response payload. We suggest turning off the <property>display_errors</property>
325 directive in production to prevent such problems
329 <sect2 id="zend.amf.server.response">
330 <title>AMF Responses</title>
333 Occasionally you may desire to manipulate the response object
334 slightly, typically to return extra message headers. The
335 <methodname>handle()</methodname> method of the server returns the response
336 object, allowing you to do so.
339 <example id="zend.amf.server.response.messageHeaderExample">
340 <title>Adding Message Headers to the AMF Response</title>
343 In this example, we add a 'foo' MessageHeader with the value
344 'bar' to the response prior to returning it.
347 <programlisting language="php"><![CDATA[
348 $response = $server->handle();
349 $response->addAmfHeader(new Zend_Amf_Value_MessageHeader('foo', true, 'bar'))
355 <sect2 id="zend.amf.server.typedobjects">
356 <title>Typed Objects</title>
359 Similar to <acronym>SOAP</acronym>, <acronym>AMF</acronym> allows passing objects
360 between the client and server. This allows a great amount of flexibility and
361 coherence between the two environments.
365 <classname>Zend_Amf</classname> provides three methods for mapping
366 ActionScript and <acronym>PHP</acronym> objects.
372 First, you may create explicit bindings at the server level,
373 using the <methodname>setClassMap()</methodname> method. The first
374 argument is the ActionScript class name, the second the <acronym>PHP</acronym>
375 class name it maps to:
378 <programlisting language="php"><![CDATA[
379 // Map the ActionScript class 'ContactVO' to the PHP class 'Contact':
380 $server->setClassMap('ContactVO', 'Contact');
386 Second, you can set the public property <varname>$_explicitType</varname>
387 in your <acronym>PHP</acronym> class, with the
388 value representing the ActionScript class to map to:
391 <programlisting language="php"><![CDATA[
394 public $_explicitType = 'ContactVO';
401 Third, in a similar vein, you may define the public method
402 <methodname>getASClassName()</methodname> in your <acronym>PHP</acronym> class;
403 this method should return the appropriate ActionScript class:
406 <programlisting language="php"><![CDATA[
409 public function getASClassName()
419 Although we have created the ContactVO on the server we now need to make its
420 corresponding class in <acronym>AS3</acronym> for the server object to be mapped to.
424 Right click on the src folder of the Flex project and select New -> ActionScript
425 File. Name the file ContactVO and press finish to see the new file. Copy the
426 following code into the file to finish creating the class.
429 <programlisting language="as"><![CDATA[
433 [RemoteClass(alias="ContactVO")]
434 public class ContactVO
437 public var firstname:String;
438 public var lastname:String;
439 public var email:String;
440 public var mobile:String;
441 public function ProductVO():void {
448 The class is syntactically equivalent to the <acronym>PHP</acronym> of the same name.
449 The variable names are exactly the same and need to be in the same case
450 to work properly. There are two unique <acronym>AS3</acronym> meta tags in this class.
451 The first is bindable which makes fire a change event when it is updated.
452 The second tag is the RemoteClass tag which defines that this class can
453 have a remote object mapped with the alias name in this case
454 <emphasis>ContactVO</emphasis>. It is mandatory that this tag the value that was set
455 is the <acronym>PHP</acronym> class are strictly equivalent.
458 <programlisting language="as"><![CDATA[
460 private var myContact:ContactVO;
462 private function getContactHandler(event:ResultEvent):void {
463 myContact = ContactVO(event.result);
468 The following result event from the service call is cast instantly onto the Flex
469 ContactVO. Anything that is bound to myContact will be updated with the returned
474 <sect2 id="zend.amf.server.resources">
475 <title>Resources</title>
478 <classname>Zend_Amf</classname> provides tools for mapping resource types
479 returned by service classes into data consumable by ActionScript.
483 In order to handle specific resource type, the user needs to create a plugin class named
484 after the resource name, with words capitalized and spaces removed (so, resource
485 type "mysql result" becomes MysqlResult), with some prefix, e.g.
486 <classname>My_MysqlResult</classname>. This class should implement one method,
487 <methodname>parse()</methodname>, receiving one argument - the resource - and returning
488 the value that should be sent to ActionScript. The class should be located in the file
489 named after the last component of the name, e.g. <filename>MysqlResult.php</filename>.
493 The directory containing the resource handling plugins should be registered with
494 <classname>Zend_Amf</classname> type loader:
497 <programlisting language="php"><![CDATA[
498 Zend_Amf_Parse_TypeLoader::addResourceDirectory(
500 "application/library/resources/My"
505 For detailed discussion of loading plugins, please see
506 the <link linkend="zend.loader.pluginloader">plugin loader</link> section.
510 Default directory for <classname>Zend_Amf</classname> resources is registered
511 automatically and currently contains handlers for "mysql result" and "stream"
515 <programlisting language="php"><![CDATA[
516 // Example class implementing handling resources of type mysql result
517 class Zend_Amf_Parse_Resource_MysqlResult
520 * Parse resource into array
522 * @param resource $resource
525 public function parse($resource) {
527 while($row = mysql_fetch_assoc($resource)) {
536 Trying to return unknown resource type (i.e., one for which no handler plugin exists)
537 will result in an exception.
541 <sect2 id="zend.amf.server.flash">
542 <title>Connecting to the Server from Flash</title>
545 Connecting to your <classname>Zend_Amf_Server</classname> from your Flash project is
546 slightly different than from Flex. However once the connection Flash functions with
547 <classname>Zend_Amf_Server</classname> the same way is flex. The following example can
548 also be used from a Flex <acronym>AS3</acronym> file. We will reuse the same
549 <classname>Zend_Amf_Server</classname> configuration along with the World class for our
554 Open Flash CS and create and new Flash File (ActionScript 3). Name the document
555 <filename>ZendExample.fla</filename> and save the document into a folder that you will
556 use for this example. Create a new <acronym>AS3</acronym> file in the same directory
557 and call the file <filename>Main.as</filename>. Have both files open in your editor. We
558 are now going to connect the two files via the document class. Select ZendExample and
559 click on the stage. From the stage properties panel change the Document class to Main.
560 This links the <filename>Main.as</filename> ActionScript file with the user interface
561 in <filename>ZendExample.fla</filename>. When you run the Flash file ZendExample the
562 <filename>Main.as</filename> class will now be run. Next we will add ActionScript to
563 make the <acronym>AMF</acronym> call.
567 We now are going to make a Main class so that we can send the data to the server and
568 display the result. Copy the following code into your <filename>Main.as</filename> file
569 and then we will walk through the code to describe what each element's role is.
572 <programlisting language="as"><![CDATA[
574 import flash.display.MovieClip;
575 import flash.events.*;
576 import flash.net.NetConnection;
577 import flash.net.Responder;
579 public class Main extends MovieClip {
580 private var gateway:String = "http://example.com/server.php";
581 private var connection:NetConnection;
582 private var responder:Responder;
584 public function Main() {
585 responder = new Responder(onResult, onFault);
586 connection = new NetConnection;
587 connection.connect(gateway);
590 public function onComplete( e:Event ):void{
591 var params = "Sent to Server";
592 connection.call("World.hello", responder, params);
595 private function onResult(result:Object):void {
596 // Display the returned data
597 trace(String(result));
599 private function onFault(fault:Object):void {
600 trace(String(fault.description));
607 We first need to import two ActionScript libraries that perform the bulk of the work.
608 The first is NetConnection which acts like a by directional pipe between the client and
609 the server. The second is a Responder object which handles the return values from the
610 server related to the success or failure of the call.
613 <programlisting language="as"><![CDATA[
614 import flash.net.NetConnection;
615 import flash.net.Responder;
619 In the class we need three variables to represent the NetConnection, Responder, and
620 the gateway <acronym>URL</acronym> to our <classname>Zend_Amf_Server</classname>
624 <programlisting language="as"><![CDATA[
625 private var gateway:String = "http://example.com/server.php";
626 private var connection:NetConnection;
627 private var responder:Responder;
631 In the Main constructor we create a responder and a new connection to the
632 <classname>Zend_Amf_Server</classname> endpoint. The responder defines two different
633 methods for handling the response from the server. For simplicity I have called these
634 onResult and onFault.
637 <programlisting language="as"><![CDATA[
638 responder = new Responder(onResult, onFault);
639 connection = new NetConnection;
640 connection.connect(gateway);
644 In the onComplete function which is run as soon as the construct has completed we send
645 the data to the server. We need to add one more line that makes a call to the
646 <classname>Zend_Amf_Server</classname> World->hello function.
649 <programlisting language="as"><![CDATA[
650 connection.call("World.hello", responder, params);
654 When we created the responder variable we defined an onResult and onFault function to
655 handle the response from the server. We added this function for the successful result
656 from the server. A successful event handler is run every time the connection is handled
657 properly to the server.
660 <programlisting language="as"><![CDATA[
661 private function onResult(result:Object):void {
662 // Display the returned data
663 trace(String(result));
668 The onFault function, is called if there was an invalid response from the server. This
669 happens when there is an error on the server, the <acronym>URL</acronym> to the server
670 is invalid, the remote service or method does not exist, and any other connection
674 <programlisting language="as"><![CDATA[
675 private function onFault(fault:Object):void {
676 trace(String(fault.description));
681 Adding in the ActionScript to make the remoting connection is now complete. Running the
682 ZendExample file now makes a connection to <classname>Zend_Amf</classname>. In review
683 you have added the required variables to open a connection to the remote server, defined
684 what methods should be used when your application receives a response from the server,
685 and finally displayed the returned data to output via <methodname>trace()</methodname>.
689 <sect2 id="zend.amf.server.auth">
690 <title>Authentication</title>
693 <classname>Zend_Amf_Server</classname> allows you to specify authentication and
694 authorization hooks to control access to the services. It is using the infrastructure
695 provided by <link linkend="zend.auth"><classname>Zend_Auth</classname></link> and
696 <link linkend="zend.acl"><classname>Zend_Acl</classname></link> components.
700 In order to define authentication, the user provides authentication adapter extening
701 <classname>Zend_Amf_Auth_Abstract</classname> abstract class. The adapter should
702 implement the <methodname>authenticate()</methodname> method just like regular
703 <link linkend="zend.auth.introduction.adapters">authentication adapter</link>.
707 The adapter should use properties <emphasis>_username</emphasis> and
708 <emphasis>_password</emphasis> from the parent
709 <classname>Zend_Amf_Auth_Abstract</classname> class in order to authenticate. These
710 values are set by the server using <methodname>setCredentials()</methodname> method
711 before call to <methodname>authenticate()</methodname> if the credentials are received
712 in the <acronym>AMF</acronym> request headers.
716 The identity returned by the adapter should be an object containing property
717 <property>role</property> for the <acronym>ACL</acronym> access control to work.
721 If the authentication result is not successful, the request is not proceseed further
722 and failure message is returned with the reasons for failure taken from the result.
726 The adapter is connected to the server using <methodname>setAuth()</methodname> method:
729 <programlisting language="php"><![CDATA[
730 $server->setAuth(new My_Amf_Auth());
734 Access control is performed by using <classname>Zend_Acl</classname> object set by
735 <methodname>setAcl()</methodname> method:
738 <programlisting language="php"><![CDATA[
739 $acl = new Zend_Acl();
740 createPermissions($acl); // create permission structure
741 $server->setAcl($acl);
745 If the <acronym>ACL</acronym> object is set, and the class being called defines
746 <methodname>initAcl()</methodname> method, this method will be called with the
747 <acronym>ACL</acronym> object as an argument. The class then can create additional
748 <acronym>ACL</acronym> rules and return <constant>TRUE</constant>, or return
749 <constant>FALSE</constant> if no access control is required for this class.
753 After <acronym>ACL</acronym> have been set up, the server will check if access is
754 allowed with role set by the authentication, resource being the class name (or
755 <constant>NULL</constant> for
756 function calls) and privilege being the function name. If no authentication was
757 provided, then if the <emphasis>anonymous</emphasis> role was defined, it will be used,
758 otherwise the access will be denied.
761 <programlisting language="php"><![CDATA[
762 if($this->_acl->isAllowed($role, $class, $function)) {
765 require_once 'Zend/Amf/Server/Exception.php';
766 throw new Zend_Amf_Server_Exception("Access not allowed");