In this article I will provide an overview of Silverlight, which is a plug-in for web browsers, which simplifies development of rich internet applications. Rich internet application (RIA) is quite popular term in these days - it usually referst to applications that run in the web browsers, but provide functionality similar to desktop applications. This can be achieved using various technologies ranging from pure AJAX solutions, to applications that use Flash or Java on the client-side. Silverlight is a project developed by Microsoft based on .NET and it provides an alternative option to these technologies. There are various reasons why you may consider Silverlight as the right platform for you, but I believe that the most compeling reason is that you can choose from a wide range of programming languages to program the application running in the web browser. Aside from standard .NET languages, Silverlight supports Python, Ruby and - thanks to the Phalanger project - also PHP and indeed, we will focus on the PHP support available for Silverlight in this article.
Let me first shortly introduce Silverlight and Phalanger - the two projects that make it possible to write code running in the web browser in PHP language. Phalanger was already discussed in php|architect in the November 2007 issue. Phalanger... debugging
The runtime is available for Microsoft Windows and Mac OS X, with Linux support available via the third-party Moonlight runtime. silverlight 1.0, 2.0 ff/safari/ie/...?
.. 3 files xaml based on xml code + xaml vs. just code .. later in the article xaml - indexing
After the introduction, you may be thinking that there are to many different concepts in Silverlight that you have to understand before starting, but I believe that the first example will show the opposite. In this example, we will write almost everything directly in PHP. We will still need a XAML file, but it will contain just the necessary code for loading Phalanger and we will of course need HTML page (or server-side PHP script) where the Silverlight control can be embedded.
In the first example we will look how to manipulate with simple Silverlight graphics and we will start with a code that creates a smiley. Later, we will look how to add animations and how the Silverlight object can respond to mouse events. To make the code very simple, the smiley will be composed of a yellow ellipse and a transparent PNG image with eyes and mouth. You could of course create the whole icon just using Silverlight shapes, but it would be more work - we will return to more complicated graphics later and mention the tools that you can use to produce graphics for Silverlight.
<? // Listing 1 - 'sample1.phpx' include("slutils.phpx"); slcreate("Canvas", array( "Canvas.Name" => "smiley"), array( slcreate("Ellipse", array( "Ellipse.Name" => "face", "Width" => 100, "Height" => 100, "Fill" => "#FFFF00")), slcreate("Image", array( "Source" => "Smiley.png"))), $canvas); ?>
Let's now examine the code in listing 1. It starts with an include
directive, which includes
functions and classes from slutils.phpx
file. This file is available with Phalanger and it
contains a set of useful Silverlight extensions for PHP language. These extensions make it easy to manipulate
with Silverlight directly from the PHP code using various wrappers and functions. The first sample is written
mostly using these functions, but later we will also look at some more advanced topics, where we will work with
Silverlight objects directly.
The rest of the code consists of several calls to function slcreate
, which comes from the
slutils.phpx
file and can be used for creating Silverlight controls from PHP in a convenient way.
In the code we use it to create a Canvas
control, which serves as a container for other controls
and for creating Ellipse
and Image
controls in this container. The reason why we
use a container is that it makes it easier to change location of the whole smiley later.
The slcreate
function is rather flexible - it has four parameters and the last two are optional.
The first parameter speicifes a name of the control (e.g. "Ellipse"
). The second required parameter
is an associative array that specifies properties of the object, so for example when creating an ellipse,
we set width and height to 100
using this associative array. The third parameter is again
an array, but this time it is just a list of child controls of the newly created control. We use this
parameter when creating the canvas to specify two child controls of the canvas - that is the ellipse and
an image that form the smiley. Finally, the last parameter specifies the parent of the newly created control
- this is useful if you want to add new children to already existing control. In our code we use this feature
when creating the canvas that contains the smiley, because we want it to be added to the Silverlight object
in web browser, which is stored in a global $canvas
variable (this variable is also declared in the
slutils.phpx
file).
Note that two of the properties contain a dot in the name of the property. This is a bit unusuall extension
that Phalanger adds to allow using so called attached properties from Silverlight. These properties
are not declared as a part of the object and have to be set in a non-standard way, but this is handled in the
slutils.phpx
file. We will shortly discuss attached properties later, so for now it is just
important to remember that Name
is an attached property and has to be set using this dot-syntax.
This means that in this sample, the entire smiley has a name "smiley" and the ellipse has a name "face".
Names are in many ways similar to IDs from HTML, because they can be used for manipulating with the objects,
once these are created.
Now that we know how the PHP code that creates our smiley looks, we should also look at the
HTML page and XAML file. The HTML page in listing 2 is quite simple, but it requires a bit of JavaScript to load
the Silverlight object. This code is similar for all Silverlight applications and can be copied from an existing
project, so you won't need to write it by hand. The page refers to a Silverlight.js
file,
which handles the loading and is provided with Silverlight. The page later uses createObjectEx
function to load the XAML file, which is specified as one of the arguments.
<!-- Listing 2 - 'sample1.html' --> <html><head> <script type="text/javascript" src="Silverlight.js"></script> </head><body> <div id="slHost" style="width:800px;height:600px;"> <script type="text/javascript"> Silverlight.createObjectEx({ id: "slControl", source: "sample1.xaml", parentElement: document.getElementById("slHost"), properties: { width:"100%", height:"100%", version:"1.1" }, events: {} }); </script> </div> </body></html>
The listing 3 shows the content of the XAML file which is embedded in the HTML page above. The XAML
file serves as a container for all the visible controls and in our example with smiley, the
$canvas
variable refers to a root Canvas
object in the XAML file.
Usually most of the controls are created in the XAML file, but in the first example we focused on
the PHP-side of thing, but we will look at some examples that use XAML for creating controls further.
As already mentioned, the XAML file format is based on XML, so you can easily recognize that the file in
the listing 3 declares a root element Canvas
, which represents a container for all the
Silverlight controls. The root element has several attributes that specify basic properties (like width
and height) and also defines XML namespaces for the document. The declaration of XML namespace php
is interesting, because it imports controls from a specified .NET namespace of a DLL library - in this case
PhpNetCore.dll
, which is a core Phalanger library and this library also defines the
PhalangerLoader
control, which is created inside the canvas. As you can see, the control
has only Source
attribute and specifies the PHPX file, which we examined earlier. The control
doesn't create any visible content and just loads and executes the specified PHPX file, so it used
as an entry-point for any Silverlight control implemented using Phalanger.
<!-- Listing 3 - 'sample1.xaml' --> <Canvas Width="800" Height="600" Background="White" xmlns:php="clr-namespace:PHP.Silverlight;assembly=ClientBin/PhpNetCore.dll" xmlns="http://schemas.microsoft.com/client/2007"> <php:PhalangerLoader Source="sample1.phpx" /> </Canvas>
Now we have a nice smiley living in the Silverlight control, but that's still not showing any rich graphical capabilities of Silverlight, so let's add some animations. An animation that we will add will change the color of the ellipse that forms the smiley face. When we created the ellipse earlier, we assigned a name to this control, so we can access it by its name, which is "face".
The traditional approach to animations that you use for example in JavaScript is to create a timer and change the property (in our case a color) every time the timer ticks, however this makes the code quite complicated even for very simple animations, so in Silverlight a different approach is used. We will simply 'say' that we want to change the color of the face from yellow to red in 400 milliseconds and then back to yellow and we'll leave Silverlight to handle the timers automatically. This is exactly what the following code does:
slcreate("Storyboard", array( "Storyboard.Name" => "anim", "AutoReverse" => true), array( slcreate("ColorAnimation", array( "From" => "#FFFF00", "To" => "#FF4000", "Duration" => "0:0:0.4", "Storyboard.TargetName" => "face", "Storyboard.TargetProperty" => "(Fill).(Color)"))), $canvas->Resources);
As you can see we're again using the slcreate
function. This is because animations
are similar to other elements and controls that you can use in Silverlight and are created
using a declarative programming style. The code creates a Storyboard
, which can contain
and control one or more animations. The animations are created as a children of the storyboard.
In our example we create just a single ColorAnimation
which changes a color of the
object between colors specified by the From
and To
properties.
The two properties that specify what should be animated ar probably the most important part.
The Storyboard.TargetName
specifies a name of the control that we're animating
(in our example this refers to a "face" ellipse created earlier). The Storyboard.TargetProperty
is a path to the property that should be animated. In our example, we want to animate Color
,
which is a property of the brush that specifies how the ellipse is filled, so we need to access
the Fill
property of the ellipse first and then the Color
property of
the brush. The braces around the property name are required, because some properties can contain a dot
in the name - these are called attached properties and we shortly mentioned them earlier,
but we will get back to them in one more example later.
The last thing that we have to do is to start the animation. The storyboard has a name "anim",
so we can access it directly by the name using the global $canvas
variable. This is
an example, where PHP is really flexible, because we can use this syntax even when the animation
(or a control) that we want to access is created at runtime:
$canvas->anim->Begin();
As a next step, we will add a handling of mouse events to our code. We will do something quite simple - when user clicks on the smiley, the smiley will start the animation that we created earlier and it will move to another place on the screen. We will also create a counter that counts the number of times user clicked on the smiley.
It is important to understand that interactive applications (be it a Silverlight content or other type
of GUI application that can be created using Phalanger) work in a different way than usual server-side
PHP code. In these interactive application, some code executes at the beginning, but it stays loaded
in the memory until the application is closed, so when user performs some action, the state of the
application is stored in memory and can easily respond to the action. In our example with smiley this
means that we will write a function that will move the smiley and we will tell Silverlight to
call this function when user clicks on the image. Using the Silverlight terminology, we will register
an event handler (the function that should be called) to a MouseLeftButtonDown
event of the image.
Before looking at the events and event handlers, we will first add a single global variable to
count the number of clicks and we'll import a .NET namespace using import namespace
directive.
This is a non-standard PHP language extension in Phalanger that makes it possible to use .NET namespaces
(however they will be replaced by PHP 6 compatible namespaces in the future). In this case we're
importing a namespace that contains some classes that we'll need later for our event handling code
(note that import namespace
must be the first thing in your source file):
import namespace System:::Windows:::Input; global $count; $count = 0;
Next, we'll create a TextBlock
element. TextBlock
is simply a control that
displays some text and we will use it for displaying the number of the clicks on the smiley. We will
need to change the displayed text, so we need to assign a name ("txt"
) to the control.
The code is using the slcreate
function, which we already discussed:
slcreate("TextBlock", array( "TextBlock.Name" => "txt", "Canvas.Top" => 10, "Canvas.Left" => 300, "FontFamily" => "Arial", "FontSize" => 20, "Text" => "Count: $count"), array(), $canvas);
Now, we can finally start implementing our event handler. We will write a function that
starts the animation created earlier, moves the smiley to a random location and displays
incremented count of the clicks in a TextBlock
, which we just created:
function OnClick() { global $canvas; global $count; $canvas->anim->Begin(); $canvas->smiley->{ "Canvas.Left" } = rand(100, 600); $canvas->smiley->{ "Canvas.Top" } = rand(100, 500); $canvas->txt->Text = "Count: ".(++$count); }
The code that starts the animation and changes the text isn't using any new concepts. It simply
uses the global $canvas
variable to access the elements created earlier and
sets its properties. The code that sets position of the smiley however shows two interesting new things.
The first thing is that we can use standard PHP functions like rand
even when running in
Silverlight. This is possible because Silverlight version of Phalanger contains an implementation of
many standard PHP functions. Second, we're using an interesting syntax for setting the property. This
is because we're setting attached properties that can be accessed using a name with dot character.
This is an extension that can be used thanks to wrappers provided by the slutils.phpx
.
Attached properties used for setting a location are a good place to explain what is the purpose of
these. The location of a control is clearly managed by the parent Canvas
element, so
it doesn't make much sense to specifying a coordinates of a control that will not be placed in a canvas.
This is why attached properties exist - they are present in the control only when it is created in
some specified context, where the properties make sense. In our example this means that setting these
properties makes sense only when the smiley is placed in a canvas, wich also explains the name
of the property - the part before a dot specifies the owner of the property and the part after the dot
specifies the name of the property.
Finally, we have to tell Silverlight, that it should call our OnClick
function when
user clicks on the smiley:
$canvas->smiley->MouseLeftButtonDown->Add (new MouseEventHandler("OnClick"));
The code uses an event called MouseLeftButtonDown
, which has an Add
method.
This method can be used for registering a methods that should be called when the event occurs. Silverlight
requires the argument to be an instance of special object that represents the event handler, so we have
to create the event handler first. This is a concept that comes from .NET, so the same principle is used
in other kinds of GUI applications created using Phalanger. The event handler can take either a name
of a function as an argument or an array with a reference to an object and a member function name if you want
to call a member function of an object when the event occurs.
So far in the article we slightly overlooked the XAML files and we focused on creating
Silverlight content from the PHP code. Although creating content programatically is very important
(and in PHP it is quite ellegant thanks to the extensions provided by slutils.phpx
),
it is usually easier to create some parts of the GUI declaratively using XAML.
As already mentioned, XAML is based on XML and it is in many ways similar to HTML. When creating
a Silverlight control declaratively, you just create XML element with the same name as the control
and use XML attributes to specify properties of the control. XAML is published on the web as a plain
text, which also has one very important advantage - the content (for example text displayed using
TextBlock
element) can be indexed by web search engines. Finally, XML (and therefore also XAML)
is very tool-friendly format, so you can create your XAML files in any editor which supports XML. Also,
it can be generated by various tools and graphical editors. Aside from Microsoft Expression familly tools,
which are in particular focused on XAML, there is also a plugin allowing export of XAML content from Adobe Illustrator.
<!-- Listing 4 - 'sample2.xaml' --> <Canvas Width="800" Height="600" Background="White" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:php="clr-namespace:PHP.Silverlight;assembly=ClientBin/PhpNetCore.dll"> <php:PhalangerLoader Source="sample2.phpx" /> <Canvas.Resources> <Storyboard x:Name="anim" AutoReverse="true"> <ColorAnimation From="#FFFF00" To="#FF4000" Duration="0:0:0.4" Storyboard.TargetName="face" Storyboard.TargetProperty="(Fill).(Color)" /> </Storyboard> </Canvas.Resources> <Canvas x:Name="smiley"> <Ellipse x:Name="face" Width="100" Height="100" Fill="#FFFF00" /> <Image Source="Smiley.png" /> </Canvas> <TextBlock x:Name="txt" Canvas.Top="10" Canvas.Left="300" FontFamily="Arial" FontSize="20" Text="Count: 0" /> </Canvas>
Let's now look at the listing 4, which shows exactly the same smiley face as we created
earlier in the article, but this time created declaratively using XAML rather than programatically
from PHP code. The root Canvas
element with XML namespace declarations and
the PhalangerLoader
element (which specifies what PHPX file contains the
program code of the application) were already discussed earlier. The root element then contains
an element called Canvas.Resources
- this doesn't specify a new control to be
created and instead serves as a more flexible way for setting properties of the root element.
When the name of the element contains a dot character, it means that we're actually setting
a property of the parent element, so the element Canvas.Resources
specifies
that we're setting Resources
property of the root canvas. You may be wondering
why the complicated syntax? The answer is that sometimes you want to assign a complex value to some
property and this syntax gives us a way for creating the value of the property using
XAML elements.
In the example we first create a Storyboard
and add it to the root canvas resources.
The Storyboard
contains one animation which has the same properties as the
animation created earlier in the code. Finally, the root element contains two child elements
that simply declare two controls - the TextBlock
element, which displays the count and
a Canvas
, which represents the smiley composed of an ellipse and an image.
The file containing the PHP code for this example is quite similar to what we already created
in the first example, so we won't include a complete listing here. The only difference is
that all the calls to slcreate
function can be removed, because the controls
are created by Silverlight runtime from the XAML file in listing 4. This means that the file will
contain only the OnClick
function and the code that registers an event handler for
the MouseLeftButtonDown
event.
There are many advanced topics that we could discuss, but probably the most important topic when developing any real-world application is manipulation with data. In client-server environment this topic is almost essential. Silverlight applications run on the client (as a component in the web browser), so they form the client-side tier of the distributed application. The server-side tier will usually be PHP running on the server, but Phalanger support for Silverlight doesn't require any specific server-side environment - it can be standard PHP, Phalanger running on the server or any other server-side technology. Finally, the data tier is typically an SQL database server, which can be accessed from the server-side code.
The typical requirement will be to load and visualize some data on the client-side or collect some data from the user and submit them to the server. For this, we need a server-side code that will connect to the database and publish the data or process the request with entered data and store the data in the database. In this section we show how to connect from the client (Silverlight application running in a web browser) to the server (in our next example PHP code on a web server). As you can easily guess, the communication will be done using mechanism similar to the famous XML HTTP request, so the client can send HTTP requests to the server. We'll implement a simple application that displays famous quotes and loads a new quote from the server when some event occurs on the client.
As you're already familiar with all the boilerplate code, the following snippets present just the important part of the code. The first one shows the XAML markup that creates controls needed in this example:
<php:PhalangerLoader Source="sample3.phpx" /> <TextBlock x:Name="quote" Text="Click below to load a quote!"/> <TextBlock x:Name="new" Text="New quote" Canvas.Top="30" Foreground="Blue" TextDecorations="Underline" />
As a next thing, we will implement a server-side PHP script (sample3.php
) that
will generate a random quote. The script doesn't have any arguments, but the parameters
could be send either using HTTP GET/POST mechanism or as a raw POST data (in that case you
could read it for example from php://input
stream):
header("Cache-Control: no-cache"); $quotes = array( "Life, loathe it or ignore it, you can't like it.", "Life? Don't talk to me about life!", "I've got this pain in all the diodes down my left side.", "I think you ought to know I'm feeling very depressed."); echo $quotes[rand(0, sizeof($quotes))];
This is a standard PHP script running on the server, so there is nothing complicated. The only thing that is worth mentioning is that the script generates a different content for every request and so we have to disable caching in the web browser. The page will be requested by the XML HTTP request from Silverlight, but it still supports caching, so we would get the same quote every time.
Let's now move to the client-side. As in earlier samples, we'll need to write a function
that will handle MouseLeftButtonDown
event of a control. In this example we want to
handle click events of the control called new
, which simulates a hyperlink. When user
clicks on the control, we want to send an HTTP request to the server and request the page sample3.php
,
which we implemented earlier. A typical way for downloading a web page in PHP is using fopen
function, which also supports HTTP protocol:
$fh = fopen(sl_mkabsolute("sample3.php"), 'r'); $canvas->quote->Text = fgets($fh); fclose($fh);
The fopen
function requests a data from HTTP path only when the URL starts with
http://
prefix and otherwise it treats the path as a relative file path, so we
need to specify a complete URL. If you know the location of your application, you can use
a string, but if you want to request an URL relatively to the URL of the HTML page loaded currently
in the web browser, you can use sl_makeabsolute
function provided by Silverlight.
This function uses HtmlPage
object from Silverlight, which reveals the current
URL and combines this URL with the given argument, so in this example the HTML page URL
(something like http://svr/sample3.html
) will be combined with sample3.php
and the result will be http://svr/sample3.php
.
Finally, if you want to specify additional properties for the request, you can use the
stream_context_create
function. You can specify POST or GET method, additional
POST parameters or raw POST data.