OData SDK for PHP User Guide
Introduction
OData SDK for PHP is designed to make it easier for PHP applications to consume data services created using the WCF Data Services framework. The goal is to provide the same functionality that is available in the libraries available to .NET developers. The library includes support for accessing and modifying the data exposed by the WCF Data Services.
Protocol documentation and the list of publicly available OData services
Requirements
This toolkit requires the following extensions to be installed and enabled
PHP XSL Extension
PHP CURL Extension
Package Content
\Doc:
\Samples\WCFDataServices:
A Visual Studio 2008 project for three sample WCF Data Services used by the
sample applications included in the PHP Toolkit. The Visual Studio project
creates three services:
\Sample\PHPApplications:
\framework:
Installation and Configuration
The assumption is that PHP is already installed and configured on the machine
where the WCF Data Services toolkit is installed.
The toolkit does not have any dependency on the host OS so it can run on
Windows, Linux or Mac OSX machines.
To Install and Configure:
1.
Create a folder named 'odataphp' eg: C:\PHPLib\odataphp
2.
Copy the files and folders in the framework folder to the folder
created above.
3.
Add the path to the folder created in step 1 to the 'include_path' directive in
php.ini. e.g.
include_path =
".;C:\PHPLib\odataphp"
4.
Create a variable called 'ODataphp_path' in the php.ini file and set it to
the path where the PHP Toolkit was installed (step 1). Open php.ini and search
for 'Paths and Directories' section. Just below the definition of 'include_path'
directive, add the following two lines:
;OData SDK for PHP Library Path
ODataphp_path =
"C:\PHPLib\odataphp"
5.
On linux platform, make sure you have the php-xml module installed. This can be
installed using yum as follows,
yum install php-xml
6.
Enable php_xsl.dll in php.ini. Search for 'extension=php_xsl.dll' in the php.ini
file and remove the semicolon (;) in front.
7.
Enable php_curl.dll in php.ini. Search for 'extension=php_curl.dll' in the
php.ini file and remove the semicolon (;) in front.
8.
Set your time zone in php.ini. Search for the directive 'date.timezone' in php.ini, remove semicolon (;) in front and set value of this directive as your time zone. Refer http://www.php.net/manual/en/timezones.php for list of
supported time zones.
[Date]
;Defines the default timezone used by the date functions
date.timezone = America/Los_Angeles
Optional: The content of the PHPApplications directory can be deployed on a web
server to provide access to the sample applications from a single web page. The
PHPApplications directory contains some php files, template and CSS files that
are used to build this Sample Web Page. All Samples must be configured before
they can be run from the Web Page. Refer to the readme files in the
WCFDataServiceEditor, SimpleApplications and GameStoreApplication directories
for setup instructions.
The PHP samples use the services in sample WCFDataServices directory.
For more information, refer to the readme file in WCFDataServices to
configure these sample services.
Creating Proxy Class
The first step is to create a proxy class that is used to connect to the WCF
Data service.
The PHP client library provides a PHPDataSvcUtil.php file which creates the
proxy class.
The command to create the proxy class is:
php <path of Client Libary>PHPDataSvcUtil.php /uri=<data service Uri> |
/metadata=<service metadata file> [/out=<output
file path>] [/auth=windows|acs /u=username /p=password [/sn=servicenamespace
/at=applies_to] ] [/ph=proxy-host /pp=proxy-port [/pu=proxy-user
/ppwd=proxy-password] [/ups=yes|no] ]
<Path of Toolkit Library> is the path where the toolkit library files are
installed (See the installation instructions).
Note: In order to use this utility, the configuration option
{'ODataphp_path'} should be set in php.ini configuration file. (Refer
Installation and Configuration section for more details).
Command-line parameters
Option |
Definition |
Note |
/config |
Path to the configuration file. |
The PHPDataSvcUtil.php can be
used in two ways, either you can specify required options (described
below) through command-line or you can define the same options in a
configuration file and use /config option to specify path to the config
file. |
/uri |
OData Service Uri |
The PHPDataSvcUtil.php uses
this Uri to retrieve the service metadata.
e.g. http://localhost:13985/NorthWindDataService.svc |
/metadata |
Path to service meta data file. |
If you already have the service metadata file saved on your local
machine, you can use this option to specify the path to this metadata
file. |
/out |
Output directory or file path |
The output path where the generated proxy file is to be saved. If file
name is not specified, then the Entity Container name (defined in the
WCF Data Service) is used as filename. If this parameter is absent then
proxy file will be generated in the current directory. |
/auth |
Type of authentication. Possible values are:
windows
acs |
This parameter is needed if access to the service requires
authentication. Currently PHPDataSvcUtil supports two type of
authentication.
a.
Windows Authentication
b.
Azure Access Control Service Authentication. |
/u |
Windows username or acs scope |
If /auth is of type ‘windows’, then /u will be the windows username in
the form domain/username. If /auth is of type ‘acs’, then /u will be the
acs scope name. |
/p |
Windows password or acs issuer key |
If /auth is of type ‘windows, then /u will be the windows password. If
/auth is of type ‘acs’, then /u will be the acs issuer key |
/sn |
acs service namesapce |
If /auth is of type ‘acs’, then /sn will be the acs service namespace. |
/at |
acs applies to |
If /auth is of type ‘acs’, then /at will be the acs ‘applies to’ value |
/ph |
Http Proxy Host |
If you are running behind a proxy, then these parameters are required. |
pp |
Http Proxy port |
|
/pu |
Http Proxy username |
If your proxy requires authentication. |
/ppwd |
Http Proxy password |
|
/ups |
Use proxy settings for service request. Possible values are:
yes
no |
By default the user specified
proxy settings will be used while requesting metadata from OData
Service. If you are using
ACS auth and access to your service not require any proxy settings
(e.g. service running locally)
then set this flag to no, /ups=no |
Example usage:
Generating proxy for a service running locally which is configured for anonymous
access, using /uri option.
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/uri=http://localhost:13986/NorthWindDataServices.svc /out=D:
\samples\SimpleApplication
PHPDataSvcUtil will connect to
http://localhost:13986/NorthWindDataServices.svc,
retrieves the metadata and generate the proxy file in D:
\samples\SimpleApplication with name same as container name in the metadata.
Generating proxy from local metadata file using /metadata option
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/metadata=D: \samples\SimpleApplication\NorthWindMeta.xml /out=D:
\samples\SimpleApplication
PHPDataSvcUtil will use the metadata
from the local disk and generate the proxy file in
D: \samples\SimpleApplication with name same as container name in the metadata.
Generating proxy for remote service if you are running behind http proxy, using
/ph and /pp options
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/uri=http://netflix.cloudapp.net/catalog.svc /out=D:
\samples\SimpleApplication /ph=itgproxy.redmond.corp.microsoft.com /pp=80
PHPDataSvcUtil will use the proxy settings specified with /ph and /pp option to
connect to the remote service and generates the proxy file in D:
\samples\SimpleApplication with name same as container name in the metadata.
Generating proxy for a service in the same domain but configured to require
windows authentication, using /auth, /u and /p options
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/uri=
http://mflasko-dev/_vti_bin/listdata.svc /out=D: \samples\SimpleApplication
/auth=windows /u=redmond\odphp /p=passw0rd!
PHPDataSvcUtil will use windows credential provided over
/u and /p options to connect to the
service and generates the proxy file in D: \samples\SimpleApplication with name
same as container name in the metadata.
Generating proxy for a service running locally but configured to require ACS
authentication, using /auth, /u, /p, /sn and /at options
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/uri=
http://localhost:13985/ACSNorthWindDataService.svc /out=D:
\samples\SimpleApplication /auth=acs /u=infocorp
/p=qfimKj6Vm6HKAJryTRno5USPpvz0c8bBzFhlqKex6lQ= /sn=wcfdataservice
/at=http://localhost/WCFNorthWindService/ /ups=no
/ph=itgproxy.redmond.corp.microsoft.com /pp=80
PHPDataSvcUtil will use acs credential provided over /u, /p, /sn and /at options
to connect to the service and generates the proxy file in D:
\samples\SimpleApplication with name same as container name in the metadata.
Using /config option
In the above example, there are too many options passed using command line.
Alternatively you can define all these options in a config file and can use
/config option.
settings.ini
[Data service Url]
/uri=http://localhost:13985/ACSNorthWindDataService.svc/
[Out Directory]
/out= D: \samples\SimpleApplication
[ACS Auth Information]
/auth=acs
/u=infocorp
;Note that issuer key should be inside double quotes as it contain '='
symbol
/p="qfimKj6Vm6HKAJryTRno5USPpvz0c8bBzFhlqKex6lQ="
/sn=wcfdataservice
/at=http://localhost/WCFNorthWindService/
[Proxy Settings to acess ACS]
/ph=itgproxy.redmond.corp.microsoft.com
/pp=80
[Want to use the above proxy for getting metadata]
; No because the service is running locally
/ups=no
C:\PHPLib\ odataphp>php PHPDataSvcUtil.php
/config=D:/myApp/settings.ini
Using Proxy Class
After the proxy class for the WCF Data Service has been created, it can be used
in the application to access the data on the server. The proxy class generated
by the PHPDataSvcUtil needs to be included first:
require_once 'MyProxyClass.php';
The MyProxyClass.php file contains the definition of a class that can be use to
execute various queries using the Execute method.
The following code snippet shows an example on how to use the proxy class to
access a customer record for a service that exposes the NorthWind database.
The goal is to retrieve the record for the Costumer with CustomerID=’ALFKI’
$proxy = new NorthwindEntities();
$response = $proxy->Execute(“Customers(‘ALFKI’)”);
$customer = $response->Result[0];
echo ($customer->CompanyName);
This code sample uses the proxy class to connect to the WCF Data Service,
retrieves a customer, and prints the CompanyName field using the customers class
defined in the myproxyclass.php file.
Note: The syntax EntitySet(<key>) is used to select an entity instance from the
data service. Here EntitySet can be mapped to the database table, <key> to the
primary key and entity instance to table record.
Data Service Query Expressions
The data service query expressions are conditions that determine what data is
retrieved from the WCF Data Service. Using query expressions we can perform
traditional query operations against resources (entity sets), such as filtering,
sorting, and paging. Query expressions are composed of query options, query
operators, and query function. There are nine Data Service Query options
supported by the OData SDK for PHP.
·
expand
·
filter
·
orderby
·
skip
·
top
·
inlinecount
·
count
·
skiptoken
·
select
Complete documentation on the Data Service Query expressions can be found on the
MSDN
site
Running Query Expressions
The OData SDK for PHP exposes two APIs which can be used for running query
expressions against WCF Data Service servers.
1.
ObjectContext::Execute
2.
DataServiceQuery::Execute
The ObjectContext: :Execute accepts query expression URI as parameter, this
method will use HTTP GET request to retrieve the entity set (in Atom format)
addressed by the query expression URI, create a collection of entity objects by
parsing the atom response and returns DataServiceResponse object which includes
this collection.
An instance of DataServiceQuery class represents single query request to a data
service, this class exposes few methods which helps to reduce the difficulty in
building the query expression URI.
DataServiceQuery Class
OData SDK for PHP includes a class that represents a single query request to a
data service.
The
DataServiceQuery
class exposes the following member function: the 'Query Options in Detail'
section contains examples on how to use the DataServiceQuery functions
API Prototype |
Parameters |
Description |
AddQueryOption($name,
$value) |
$name: The string value that contains the name of the query string
option (filter, orderby, skip, top, select, expand) to add.
$value: The string that contains the value of the query string option. |
The query options are added to the resultant query expression URI using
?name=value&name2=value2…
syntax where the name maps directly to the
$name
parameter and the
value
is from $value
parameter. . |
Expand($relatedEntities) |
$relatedEntities: The related entities separated by comma, which is to
be embedded in the result. |
Add the $expand option in the resultant query expression URI. |
Filter($expression) |
$expression: The expression to be applied to restrict the entities to be
embedded in the result. |
Add the $filter option in the resultant query expression URI. |
Select($properties) |
$properties: Properties of the entity separated by comma. |
Add the $select option in the resultant query expression URI. |
Top($count) |
$count: The maximum number of entities to be embedded in the result. |
Add the $top option in the resultant query expression URI. |
Skip(count) |
$count: The number of rows to be skipped when returning results |
Add the $skip option in the resultant query expression URI. |
IncludeTotalCount() |
None. |
Requests to include the number of entities returned by the query
expression URI along with the query result. |
Count() |
None. |
Returns only count of entities satisfying the resultant query URI
expression. |
RequestUri() |
None. |
Returns the resultant query URI expression. |
Execute() |
None. |
Executes the query expression and returns QueryOperationResponse object
which holds the results as a collection. |
If any error occurred while building the query expression URI (ex: you cannot
use IncludeTotalCount() and Count() together) or running the query
(ex: Unauthorized) then this class will throw exception of type
DataServiceRequestException.
QueryOperationResponse Class
OData SDK for PHP includes a class that represents the return type of
ObjectContext::Execute and DataServiceQuery::Execute APIs.
The QueryOperationResponse class exposes the following member functions:
API Prototype |
Parameters |
Description |
None. |
Returns total number of entities in the entity set, if user asked for
row count (using DataServiceQuery::IncludeInlineCount or
inlinecount=allpages option in the query expression). |
|
getQuery() |
None. |
Returns the
query
expression URI that generates the
QueryOperationResponse
item. |
getStatusCode() |
None. |
Returns the HTTP response code associated with HTTP response for the
query expression URI. |
getHeaders() |
None. |
Returns an associative array which contains the HTTP response headers
associated with HTTP response for the query expression URI. |
getError() |
None. |
Gets error thrown by the data service query operation. |
GetContinuation($collection) |
$collection: The collection of related objects being loaded |
Returns
DataServiceQueryContinuation
object that contains the URI that is used to retrieve the next page of
related entities in the specified collection. See Server Side Paging
Section for more details. |
This class includes the following member variable:
Member variable |
Description |
Result |
The result of execution of 'query expression URI' as a collection. |
OData Exception Classes
OData SDK for PHP supports 4 types of exception.
DataServiceRequestException class
Exception class representing any exception that can occur, while building the
query expression URI (using DataServiceQuery member APIs) or running the
query.
Member variable |
Description |
Response |
Type: QueryOperationResponse
The Response::getError(), Response::getStatusCode(), and
Response::getHeaders can be used for getting error details. |
This exception class includes one member variable of type
QueryOperationResponse, Response. Please see the samples in
Query Options in Details section to understand how to handle this exception.
ODataServiceException class
Exception class representing any error returned from the WCF Data services,
while processing the requests (expect query request) from the client.
The ODataServiceException class exposes following functions:
API Prototype |
Parameters |
Description |
getError() |
None. |
Returns short description of error. |
getDetailedError() |
None. |
Returns detailed description of error. |
getStatusCode() |
None. |
Returns the HTTP response code associated with HTTP response
representing the error |
getHeaders() |
None. |
Returns an associative array which contains the HTTP response headers
associated with HTTP response representing the error. |
InvalidOperation class
Exception class representing any exception that can occur, due to the invalid
usage of the APIs by the client application. For example if client application
try to add same entity instance twice, then the context tracking logic will
throw this exception with message ‘The context is already tracking the entity’.
The InvalidOperation class exposes following functions
API Prototype |
Parameters |
Description |
getError() |
None. |
Returns short description of error. |
getDetailedError() |
None. |
Returns detailed description of error. |
ACSUtilException class
Exception class representing any error that occurred while trying to retrieve
ACS token from Azure. This error can occur while using ACSUtil class or
ACSCredential class.
API Prototype |
Parameters |
Description |
getError() |
None. |
Returns description of error from ACS. |
getStatusCode() |
None. |
Returns the HTTP response code associated with HTTP response
representing the error |
getHeaders() |
None. |
Returns an associative array which contains the HTTP response headers
associated with HTTP response representing the error. |
Query Options in Detail
This section explains the query options in detail and the samples use
DataServiceQuery class for building and executing query expressions.
Expand
The ‘expand’ option allows you to embed one or more sets of related entities in
the results. For example, if you want to display a customer and its sales
orders, you could execute two requests, one for Customers(‘ALFKI’) and one for
Customers(‘ALFKI’) Orders. The ‘expand’ option on the other hand allows you to
return the related entities in-line with the response of the parent in a single
HTTP request.
You may specify multiple navigation properties to expand by separating them with
commas, and you may traverse more than one relationship by using a dot to jump
to the next navigation property. The benefit of using the Expand option is that
data can be retrieved with only one server invocation.
Example:
try
{
//customer ALFKI with
related sales orders
$query = new
DataServiceQuery(‘Customers’, $proxy)
->Filter(“CustomerID eq ‘ALFKI’”)
->Expand(‘Orders’);
$response =
$query->Execute();
$customer =
$response->Result[0];
echo
count($customer-Orders);
//In the proxy class,
for each entity set, there will be one function (with name same as name of
entity set) that returns DataServiceQuery object for that particular entity
set.
//customer ALFKI with
related sales orders and employee information related to those orders
$response =
$proxy->Customers()
->Filter( “CustomerID eq ‘ALFKI’”)
->Expand(‘Orders/Employees’)
->Execute();
$customer =
$response->Result[0]
echo
count($customer->Orders(10248)->Employees);
//Orders entity with id 10248 with
related employees information and related shipper information
$response = $proxy->Orders()
->Filter( ‘OrderID eq 10248’)
->Expand(‘Employees,Shippers’)->Execute();
$order = $response->Result[0];
echo
count($order->Employees(5)->Shippers);
echo count($order->Employees);
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Instead of using DataServiceQuery class, you can also use the Execute() API of
ObjectContext class. For example,
$query = new DataServiceQuery(‘Customers’, $proxy)
->Filter(“CustomerID eq ‘ALFKI’”)
->Expand(‘Orders’);
$response = $query->Execute();
Can be replaced using ObjectContext::Execute API, in this case the correct query strings need to be built:
$response =
$proxy->Execute(“Customers(‘ALFKI’)?/$expand=Orders”);
Filter
Restrict the entities returned from a query by applying the expression specified
in this operator to the entity set identified by the last segment of the URI
path.
Example:
try
{
//all customers in London
$response =
$proxy->Customers()
->Filter(
“City eq ‘London’”)
->Execute();
$customers =
$response->Result;
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Conditions that can be used in the filter condition are:
Condition |
Description |
eq |
Equals |
ne |
Not equals |
lt |
Less than |
le |
Less than or equal |
gt |
Greater than |
ge |
Greater than or equal |
OrderBy
Sort the results by the criteria given in this value. Multiple properties can be
indicated by separating them with a comma. The sort order can be controlled by
using the “asc” (default) and “desc” modifiers.
For example:
try
{
$response =
$proxy->Customers()->OrderBy(‘City’)->Execute();
$customers =
$response->Result;
$response =
$proxy->Customers()->OrderBy(‘City desc’)->Execute();
$customers =
$response->Result;
$response =
$proxy->Customers()->OrderBy(‘City desc,CompanyName asc’)->Execute();
$customers =
$response->Result;
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Skip
Skip the number of rows given in this parameter when returning results. This is
useful in combination with “top” to implement paging (e.g. suppose there are 40
total entities, if using 10-entity pages, saying $skip=30&top=$10 would return
the fourth page).
NOTE: if an orderby option is included, ‘skip’ will skip entities in the order
given by that option. If no orderby option is given, ‘skip’ will sort the
entities by primary key and then perform the skip operation.
For example:
try
{
//return all customers
except the first 10
$response =
$proxy->Customers()->Skip( ‘10’)->Execute();
$customers =
$response->Result;
//return the 4th page, in
10-row pages
$response =
$proxy->Customers()
->Skip(‘30’)
->Top(‘10’)
->Execute();
$customers =
$response->Result;
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Top
Restrict the maximum number of entities to be returned. This option is useful
both by itself and in combination with skip, where it can be used to implement
paging as discussed in the description of ‘skip’.
try
{
//top 5 sales orders
$response =
$proxy->Orders()->Top(‘5’)->Execute();
$orders =
$response->Result;
//top 5 sales orders
with the highest TotalDue
$response =
$proxy->Orders()
->Orderby(‘TotalDue’)
->Top(‘5’)
->Execute();
$sorders =
$response->Result;
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
inlinecount
This option directs the service to include the count of all the entities along
with the entities that are returned by a URI. This count is executed as a
separate query, and the returned value is independent of any defined page size
limits (concept of page size limit will be discussed in Server Side Paging
section).
For example:
try
{
//all customers in London
$response =
$proxy->Customers()
->Filter(“City eq ‘London’”)
->IncludeTotalCount()
->Execute();
$customers =
$response->Result;
echo ‘Total Number of
Customers:’. $response->TotalCount();
echo ‘Number of Customers in
London:’ . count($customers);
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Count
The count query option can be added to any entity sets on the server, the result
is a plaintext response that represents the count of entities in that set.
For example:
try
{
//all customers in London
$query = $proxy->Customers()
->Filter(“City eq ‘London’”);
echo ‘Number of Customers with
Lodon as City:’ . $query->Count();
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Detailed discussion of inlinecount and count can be found on this
blog;
Select
The projections (select) feature of WCF Data Services allows working with a
subset of an entity’s properties. The projections feature allows clients to
address only the relevant properties to a specific request. By only requesting
the applicable subset of an entity’s properties for a particular query, client
applications can optimize for bandwidth consumption.
The following example shows how to use this feature:
try
{
$svc = new
NorthwindEntities();
$query =
$svc->Customers()->filter("Country eq 'USA'")
->Select('CustomerID,CompanyName');
$customersResponse = $query->Execute();
foreach($customersResponse->Result as $customer)
{
echo "CustomerID:" . $customer->CustomerID . "<br/>";
echo "Company Name:" . $customer->CompanyName . "<br/>";
echo "Country:" .$customer->Country . "(This will be null as we
selected only CustomerID and CompanyName)" . "<br/>";
echo "----------" . "<br/>";
}
}
catch(DataServiceRequestException $ex)
{
echo 'Error: while running the query ' . $ex->Response->getQuery();
echo "<br/>";
echo $ex->Response->getError();
}
Server Driven Paging
The Server-Driven Paging feature allows a service author to set the number of
entities returned for each request. In addition to limiting the number of
entities returned per request, the server provides the client a "next link"
which is simply a URI specifying how to continue retrieving the rest of the
entities in the collection not returned by the first request.
Please refer this
blog
to understand how to configure a service with Server-Driven Paging feature.
The QueryOperationResponse::GetContinuation() API can be used to get the “next
link”. The following sample shows how to use
QueryOperationResponse::GetContinuation().
For example:
try
{
$svc = new
NorthwindEntities(NORTHWIND_SERVICE_URL);
$query =
$svc->Customers()->Filter(”Country eq 'USA'”);
$customersResponse = $query->Execute();
echo
"CustomerID" . "<br/>";
echo
"----------" . "<br/>";
$nextCustomerToken = null;
do
{
if($nextCustomerToken != null)
{
$customersResponse = $svc->Execute($nextCustomerToken);
}
foreach($customersResponse->Result as $customer)
{
echo $customer->CustomerID . "<br/>";
}
}while(($nextCustomerToken = $customersResponse->GetContinuation()) !=
null);
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Please refer the samples provided in the next section to understand how to use
QueryOperationResponse::GetContinuation(..)
API to load related entities.
Loading Related Entities using expand option and LoadProperty API
When you execute a query using ObjectContext::Execute or
DataServiceQuery::Execute, only the entities in the addressed entity set are
returned (in QueryOperationResponse::Result). For example, when a query against
the Northwind data service returns
Customers entities, by default the related
Orders entities are not returned,
even though there is a relationship between
Customers and
Orders. There are two ways to load
related entities in a single call to the server:
1.
Using expand query option to request that the query return entities that are
related by an association to the entity set that the query requested, this is
known as eager loading.
The following sample, List all customer’s ID in NorthWind DB with USA as Country
and associated Order's ID using expand option. This sample application also
shows how to use QueryOperationResponse::GetContinutation(..) API to continue
retrieving the rest of the related entities in the collection if the WCF service
implements server side paging:
try
{
$svc = new
NorthwindEntities(NORTHWIND_SERVICE_URL);
$query =
$svc->Customers()
->Filter(”Country eq 'USA'”)
->Expand('Orders');
$customerResponse = $query->Execute();
$nextCustomerToken = null;
do
{
if($nextCustomerToken != null)
{
$customerResponse = $svc->Execute($nextCustomerToken);
}
foreach($customerResponse->Result as $customer)
{
echo '<br/>CustomerID: ' . $customer->CustomerID . "<br/>";
$nextOrderToken = null;
$firstTime = true;
$ordersResponse;
echo "<br/>Associcated Orders <br/>";
echo "-----------------------<br/>";
do
{
$orders = array();
if(!$firstTime)
{
$ordersResponse = $svc->Execute($nextOrderToken);
$orders = $ordersResponse->Result;
$nextOrderToken = $ordersResponse->GetContinuation();
}
if($firstTime)
{
$orders = $customer->Orders;
$nextOrderToken =
$customerResponse->GetContinuation($customer->Orders);
$firstTime = false;
}
foreach($orders as $order)
{
echo "
" . $order->OrderID . "<br/>";
}
}while($nextOrderToken != null);
}
}while(($nextCustomerToken = $customerResponse->GetContinuation()) != null);
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
2.
You can call the
LoadProperty
method of the
ObjectContext
class to explicitly load related entities. Each call to the LoadProperty method
creates a separate request to the data service. The following sample, List all
cutomer's ID in NorthWind DB with USA as Country and associated Order's ID using
LoadProperty API. This sample application also shows how to use
QueryOperationResponse::GetContinutation(..) API to continue retrieving the rest
of the related entities in the collection:
try
{
$svc = new
NorthwindEntities(NORTHWIND_SERVICE_URL);
$query =
$svc->Customers()->Filter(”Country eq 'USA'”);
$customerResponse =
$query->Execute();
$nextCustomerToken = null;
do
{
if($nextCustomerToken !=
null)
{
$customerResponse =
$svc->Execute($nextCustomerToken);
}
foreach($customerResponse->Result
as $customer)
{
echo '<br/>CustomerID: '
. $customer->CustomerID . "<br/>";
$nextOrderToken = null;
echo "<br/>Assoicated
Orders <br/>";
echo
"-----------------------<br/>";
do
{
$ordersResponse =
$svc->LoadProperty($customer, 'Orders', $nextOrderToken);
foreach($customer->Orders
as $order)
{
echo "
" . $order->OrderID . "<br/>";
}
}while(($nextOrderToken
= $ordersResponse->GetContinuation()) != null);
}
}while(($nextCustomerToken =
$customerResponse->GetContinuation()) != null);
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
Creating new instance in data service
In this scenario a new object is created on the client and then made persistent
by saving it on the database exposed by the WCF Data Service.
To create a new instance in the data service, first a new php object needs to be
created and the required properties populated, then by calling AddObject and
passing the object and the target entity-set, the object is added to the
collection of objects in the database. A final call to the SaveChanges method
makes the actual call to make the changes permanent in the database.
The Following code snippet shows how to use this functionality.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
//Create a Customer php Object
$customer =
Customers::CreateCustomers("CHAN9", "channel9");
//inserting Customers
object context tracking system
$proxy->AddObject(‘Customers’, $customer);
//SaveChange insert the
object into data service
$proxy->SaveChanges();
}
catch(ODataServiceException $exception)
{
Echo
$exception->getError();
}
Updating an existing instance in data service
To update an existing instance in the data service, first an instance from data
service needs to be retrieved using the ObjectContext::Execute or LoadProperty
or DataServiceQuery::Execute methods. After the object has been retrieved and
the properties set with the new values a call to the UpdateObject applies the
changes to the table(s) in the database. The following code snippet shows how to
use UpdateObject API.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
$response =
$proxy->Execute (“Customers(‘CHAN9’));
$customer =
$response->Result[0];
//update the
CompanyName property
$customer->CompanyName = "Channel8";
//add the object
to the list of objects that needs to be updated in the database
$proxy->UpdateObject($customer);
//SaveChanges
updates the object in the data service
$proxy->SaveChanges();
}
catch(DataServiceRequestException $e)
{
echo
$exception->Response->getError();
}
catch(ODataServiceException $exception)
{
echo
$exception->getError();
}
Deleting an existing instance from data service
Similar to the Update scenario, to delete an existing instance in the data
service, first an instance needs to be retrieved using ObjectContext::Execute or
LoadProperty or DataServiceQuery::Execute methods, then a call to the
DeleteObject methods adds the objects to the list of objects that needs to be
deleted in the database. . The following code snippet shows how to use
DeleteObject API.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
$response =
$proxy->Execute (“Customers(‘CHAN9’));
$customer =
$response->Result[0];
//The object is
added to the list of objects to delete in the database
$proxy->DeleteObject($customer);
//SaveChanges
persists the
changes in the database
$proxy->SaveChanges();
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
catch(ODataServiceException $exception)
{
echo
$exception->getError();
}
Creating link between instances using AddLink
If you have a relationship defined between two entities in the data service with
multiplicity = *, then you
can use AddLink to create relationship between the corresponding entity
instances.
The syntax of this method is:
AddLink(soureObject, sourceProperty, targetObject);
Note:
This method only supports adding links to relationships with multiplicity = * (one-to-many relationships), i.e. the source property is
a collection. Use SetLink method for adding links to relationships with multiplicity = 1 (many-to-one relationships).
The following code snippet shows how to create link between a
Customers instance and Orders instances.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
//Create a Customers
instance
$customer = new
Customers();
$customer ->CustomerID =
'CHAN9';
$customer ->CompanyName
= 'channel9';
$proxy->AddToCustomers($customer);
//Create an Orders
instance
$Order = new Orders();
//Create link between
Customer and Order.
$proxy->AddLink($cust,
"Orders", $Order);
$proxy->SaveChanges();
}
catch(ODataServiceException $exception)
{
echo
$exception->getError();
}
Deleting link between instances using DeleteLink
If you have a relationship defined between two entities in the data service with
multiplicity = * , then you can use
DeleteLink to remove the relationship between corrosponding entity instances. The following code
snippet shows how to delete the link.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
//Retrive the
Customers entity and related orders with key ‘CHAN9’
$response =
$proxy->Customers()
->Filter(”CustomerID eq ’CHAN9’”)
->Expand(‘Orders’)
->Execute();
$customers =
$response->Result;
foreach($customers
as $customer)
{
foreach($customer->Orders
as $o)
{
$proxy->DeleteLink($customer,
"Orders", $o);
}
}
$proxy->SaveChanges();
}
catch(DataServiceRequestException $exception)
{
echo
$exception->Response->getError();
}
catch(ODataServiceException $exception)
{
echo
$exception->getError();
}
Creating and Deleting link between instances using SetLink
If you have a relationship defined between two entities in the data service with
multiplicity = 1, then you
can use SetLink to create and delete relationship between the corresponding entity
instances.
The syntax of this method is:
SetLink(soureObject, sourceProperty, targetObject);
Note:
This method only supports adding links to relationships with multiplicity = 1 (many-to-one relationships), i.e. the source property is
a reference. Use AddLink method for adding links to relationships with multiplicity = * (one-to-many relationships).
If the targetObject argument is null, the link represented by sourceProperty
will be deleted.
The following code snippet shows how to create link between a
Order_Details instance and Orders instance.
require_once 'MyProxyClass.php';
try
{
$proxy = new
NorthwindEntities();
//Get the specific product
//Get the specific Order
instance
$orderResponse = $proxy->Orders() ->Filter('OrderID eq 10248) ->Execute();
$order =
$orderResponse->Result[0];
///Create a new order detail
for the specific product.
$newItem =
Order_Details::CreateOrder_Details(0, $order->OrderID, $product->ProductID, 10,
5);
$proxy->AddToOrder_Details(newItem);
//Set reference links for the many-to-one relationships.
$proxy->SetLink(newItem, "Orders", $order);
$proxy->SaveChanges()
}o:p>
catch(ODataServiceException $exception)
{
echo
$exception->getError();
}
When you call the
SaveChanges
API, all changes that the context tracks are translated into REST-based
operations, by default, these changes are packed in a batch and sent in a single
request message. To require that changes to be sent in separate HTTP requests,
you must set the Save Change mode to non-batch.
$svc->SetSaveChangesOptions(SaveChangesOptions::None);
To reset to batch mode:
$svc->SetSaveChangesOptions(SaveChangesOptions::Batch);
Enhanced BLOB Support
The V1of of the OData protocol allows defining entities with property of type
BLOB. The issue with this implementation was you could not be able to defer the
loading of BLOB property, for example if you have an Employee entity definition
with a BLOB property that represent employee's photo, then when you request for
a specific set of employees, the resultant payload from data service will be
huge as each entity includes BLOB stream.
The V2.0of of the OData protocol enhances the BLOB support provided in Version 1
to enable data services to stream arbitrarily large BLOBs, store binary content
separate from its metadata this helps to easily defer the loading of BLOB
content when its metadata is requested, these type of entities are known as
Media Link Entry (MLE) Entity.
OData SDK for PHP exposes the following APIs to support the enhanced BLOB
feature of OData protocol V2.0.
API Prototype |
Parameters |
Expected Exceptions |
Description |
GetReadStream($entity, $args = null) |
entity:
The entity that has the binary property to retrieve.
args:
values can be null, string or object of DataServiceRequestArgs. |
Throws InvalidOperation
if args is not null, string or
instance of DataServiceRequestArgs, exception will throw if entity is
not a media link entry.
If WCF Data Service returns any error library will throw
ODataServiceException |
Synchronously requests a data stream that contains the binary property
of requested Media Link
Entry $entity. The $args argument can be null, a string representing
Accept HTTP header or instance of DataServiceRequestArgs class which
contains settings for the HTTP request message (Slug, Accept,
Content-Type etc..) |
GetReadStreamUri($entity) |
entity:
The entity that has the binary property to retrieve. |
Throws InvalidOperation, if
entity is not currently tracked by the context. |
Gets the URI that is used to return binary property data as a data
stream. |
SetSaveStream($entity, $stream, $closeStream, $contentType, $slug) |
entity:
The entity that has the binary property to set.
stream:
The binary stream to set.
contentType:
The type of stream
slug:
The default path and name to be given to the stream once it
is uploaded. |
Throws InvalidOperation, if
any one of the following criteria is not satisfied.
contentType
cannot be null
slug
cannot be null
stream
cannot be null
Context should track the entity. |
Sets a new data stream as the binary property of an entity, with the
specified settings in the
request messages |
Getting Stream associated with MLE entity using GetReadStream
The following code snippet uses GetReadStream API to retrieve the stream
associated with MLE entity set SharedDocuments of SharePoint 2010 site.
require_once 'TeamSiteDataContext.php';
try
{
$queryResponse = $context->SharedDocuments()->Execute();
$sharedDocItem = $queryResponse->Result[0];
$responseStream = $context->GetReadStream($sharedDocItem);
$stream = $responseStream->getStream();
if (!$handle = fopen("download.xlsx", 'wb'))
{
echo "Cannot open file for writing output";
exit;
}
if (fwrite($handle, $stream) === FALSE)
{
echo "Cannot write to file";
exit;
}
fclose($handle);
}
catch (ODataServiceException $e)
{
echo $e->getDetailedError()
echo $e->getError();
}
catch(DataServiceRequestException $e)
{
echo $e->Response->getError();
}
The above sample connects to the Shared Document
library on a SharePoint site and downloads the first document that is then saved
as download.xslx
Creating a Media Link Entry and associate a stream with it using SetSaveStream
require_once 'TeamSiteDataContext.php';
try
{
$stream = null;
if (!$handle = fopen("WSR.docx", 'rb'))
{
echo "Cannot open file for reading ";
exit;
}
if (($stream = fread($handle, filesize("WSR.docx"))) === FALSE)
{
echo "Cannot read to file";
exit;
}
$doc = new SharedDocumentsItem();
//make sure that WSR.docx is present in current working
directory
$doc->Name = "WSR1.docx";
$doc->ContentType = ContentType::MS_OFFICE_DOCX;
$context->AddToSharedDocuments($doc);
$context->SetSaveStream($doc,
$stream,
false,
ContentType::MS_OFFICE_DOCX,
"/Shared Documents/WSR2.docx");
$context->SaveChanges();
}
catch (ODataServiceException $e)
{
echo $e->getDetailedError();
echo $e->getError();
}
catch(DataServiceRequestException $e)
{
echo $e->Response->getError();
}
Working with Azure Storage Tables
The following basic operations are supported on azure storage tables and
entities
·
Create a table or entity
·
Retrieve a table or entity, with filters
·
Update an entity (but not a table)
·
Delete a table or entity.
Operations against azure storage tables requires user to specify azure
credentials. The constructor of AzureTableCredential class accepts azure account
name and key.
$proxy->Credential = new AzureTableCredential(account-name, account-key);
Create Azure Storage Table
The following code snippet shows how to create a table with name Employees in
azure.
require_once 'Context/ObjectContext.php';
define AZURE_SERVICE_URL(“http://<Account>.table.core.windows.net”);
define AZURE_ACCOUNT_NAME(specify your account name here);
define AZURE_ACCOUNT_KEY(specify your account key here);
try
{
$svc = new
ObjectContext(AZURE_SERVICE_URL);
//specify azure
credentials
$svc->Credential =
new AzureTableCredential(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
//Create a table
entity and add it to the context
$table = new
Tables();
$table->TableName
= ‘Employees’;
$svc->AddObject(‘Tables’, $table);
$svc->SaveChanges();
}
catch(ODataServiceException $e)
{
echo $e->getError();
}
Inserting a blog entity into azure table
The following code snippet shows how to insert an entity into the table created
in the above sample. Here we have to define the entity definition (table entry
metadata) for the Employee entities to be stored in the Employees table. The
important points to be noted while defining table entry metadata are:
1.
Table entry must be derived from TableEntry class
2.
All the properties which is to be stored in the azure table must have the
attribute @Type:EntityProperty
3.
The table entry must have the following attributes
@Class:name_of_table_Entry_class
@Key:PartitionKey
@Key:RowKey
require_once 'Context/ObjectContext.php';
define AZURE_SERVICE_URL(“http://<Account>.table.core.windows.net”);
define AZURE_ACCOUNT_NAME(specify your account name here);
define AZURE_ACCOUNT_KEY(specify your account key here);
/**
*@Class:Employees
*@Key:PartitionKey
*@Key:RowKey
*/
class Employees extends TableEntry
{
/**
*@Type:EntityProperty
*/
Public
$Name;
/**
*@Type:EntityProperty
*/
Public $Age;
/**
*@Type:EntityProperty
*/
Public
$Visible;
}
try
{
$svc = new
ObjectContext(AZURE_SERVICE_URL);
//specify azure
credentials
$svc->Credential =
new AzureTableCredential(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
//Create a
employees entity and add it to the context
$tableEntity = new
Employees();
$tableEntity->PartitionKey = ‘Partition1’;
$tableEntity->RowKey = ‘Row1’;
$tableEntity->Name
= ‘ALKFI’;
$tableEntity->Age
= 26;
$tableEntity->Visible = true;
$svc->AddObject(‘Employees’, $tableEntity);
$svc->SaveChanges();
}
catch(ODataServiceException $e)
{
echo $e->getError();
}
Updating Table entry by adding a new property
The following code snippet shows how to update the previously inserted table
entity by adding address property. Here we have to update the definition of
entity (table entry metadata) for the Employee entities with address property.
require_once 'Context/ObjectContext.php';
define AZURE_SERVICE_URL(“http://<Account>.table.core.windows.net”);
define AZURE_ACCOUNT_NAME(specify your account name here);
define AZURE_ACCOUNT_KEY(specify your account key here);
/**
*@Class:Employees
*@Key:PartitionKey
*@Key:RowKey
*/
class Employees extends TableEntry
{
/**
*@Type:EntityProperty
*/
Public
$Name;
/**
*@Type:EntityProperty
*/
Public $Age;
/**
*@Type:EntityProperty
*/
Public
$Visible;
/**
*@Type:EntityProperty
*/
Public
$Address;
}
try
{
$svc = new
ObjectContext(AZURE_SERVICE_URL);
//specify azure
credentials
$svc->Credential =
new AzureTableCredential(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
//Create query
object to fetch the record
$query = new
DataServiceQuery(‘Employees’, $svc);
$response =
$query->
Filter( “Name eq ’ALFKI’”)->Execute();
$tableEntity =
$response->Result[0];
//Update the table
entry by adding Address Property
$tableEntity->Address = ‘Line No1’;
$svc->UpdateObject($tableEntity);
$svc->SaveChanges();
}
catch(ODataServiceException $e)
{
echo $e->getError();
}
catch(DataServiceRequestException $e)
{
echo
$e->Response->getError();
}
Updating Table entry by removing an existing property
The following code snippet shows how to update the previously updated table
entity by removing address property.
Here we have to update the definition of entity (table entry metadata)
for the Employee entities by removing address property. Since we require a
replace (removal of address property) while update, we have to set the
replaceOnUpdate flag to true by using the following API.
$svc->ReplaceOnUpdate(true);
require_once 'Context/ObjectContext.php';
define AZURE_SERVICE_URL(“http://<Account>.table.core.windows.net”);
define AZURE_ACCOUNT_NAME(specify your account name here);
define AZURE_ACCOUNT_KEY(specify your account key here);
/**
*@Class:Employees
*@Key:PartitionKey
*@Key:RowKey
*/
class Employees extends TableEntry
{
/**
*@Type:EntityProperty
*/
Public
$Name;
/**
*@Type:EntityProperty
*/
Public $Age;
/**
*@Type:EntityProperty
*/
Public
$Visible;
}
try
{
$svc = new
ObjectContext(AZURE_SERVICE_URL);
//Set
ReplaceOnUpdate flag true
$svc->ReplaceOnUpdate(true);
//specify azure
credentials
$svc->Credential =
new AzureTableCredential(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
//Create query
object to fetch the record
$query = new
DataServiceQuery(‘Employees’, $svc);
$response =
$query->Filter(“Name eq ’ALFKI’”)->Execute();
$tableEntity =
$response->Result[0];
//Update the table
entry
$svc->UpdateObject($tableEntity);
$svc->SaveChanges();
}
catch(ODataServiceException $e)
{
echo $e->getError();
}
catch(DataServiceRequestException $e)
{
echo
$e->Response->getError();
}
Delete Table entry and Table
The following code snippet shows how to delete table entry from table and table
from azure. For this, we will fetch the table entry and table, mark it as
deleted (using DeleteObject API) and then save the changes.
require_once 'Context/ObjectContext.php';
define AZURE_SERVICE_URL(“http://<Account>.table.core.windows.net”);
define AZURE_ACCOUNT_NAME(specify your account name here);
define AZURE_ACCOUNT_KEY(specify your account key here);
/**
*@Class:Employees
*@Key:PartitionKey
*@Key:RowKey
*/
class Employees extends TableEntry
{
/**
*@Type:EntityProperty
*/
Public
$Name;
/**
*@Type:EntityProperty
*/
Public $Age;
/**
*@Type:EntityProperty
*/
Public
$Visible;
}
try
{
$svc = new
ObjectContext(AZURE_SERVICE_URL);
//specify azure
credentials
$svc->Credential =
new AzureTableCredential(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
//Create query
object to fetch the record
$query = new
DataServiceQuery(‘Employees’, $svc);
$response =
$query->Filter(“Name eq ’ALFKI’”)->Execute();
$tableEntity =
$response->Result[0];
//mark the table
entry as deleted
$svc->DeleteObject($tableEntity);
$svc->SaveChanges();
$query = new
DataServiceQuery(‘Tables’, $svc);
$response =
$query->Filter(“TableName eq ’Employees’”)->Execute();
$table=
$response->Result[0];
//mark the table
as deleted
$svc->DeleteObject($table);
$svc->SaveChanges();
}
catch(ODataServiceException $e)
{
echo $e->getError();
}
catch(DataServiceRequestException $e)
{
echo
$e->Response->getError();
}
Using Call-Back Feature
oData library allows client application to register call back functions. The
library exposes two APIs for registering call-backs.
·
ObjectConext::OnBeforeRequest
·
ObjectContext::OnAfterResponse
OnBeforeRequest
API can be used to register a function
which is to be invoked before making actual request to the WCF Data Service by
the library. The library makes service request in 3 cases:
1.
When client executes any query using DataServiceQuery::Execute,
DataServiceRequest::Count, ObjectContext::LoadProperty or ObjectContext:Execute
APIs.
2.
When client retrieves media stream associated with a Media Link entry (MLE)
using ObjectContex::GetReadStream API.
3.
When client invokes ObjectContext::SaveChanges API to save the entity instances
and relationships (links) in the context to the underlying WCF Data Service.
Once the client application register a call-back function using OnBeforeRequest
API, before making any request to the WCF Data Service, the oData library will
invoke the registered function by passing a reference to the HttpRequest object
which will be used to make the actual service request. Client application can
manipulate this HttpRequest object, for e.g. client can view and change the
request body, the http method and the headers set by the library, add additional
headers, specifying credential and proxy settings.
The HttpRequest exposes the following methods:
Prototype |
Parameters |
Return Value |
Description |
getBody() |
none |
string |
To get raw body of the http request. |
getHTMLFriendlyBody() |
none |
string |
To get the body of the http request as HTML friendly string. |
setBody($body) |
$body: string representing http request body. |
none |
To set the body of the http request. |
getMethod() |
none |
HttpVerb: The enum representing the possible http methods. |
The HttpVerb enum has the following members:
HttpVerb::DELETE
HttpVerb::GET
HttpVerb::MERGE
HttpVerb::POST
HttpVerb::PUT |
setMethod(httpVerb::*) |
httpVerb::* HttpVerb enum |
none |
See the description section of getMethod() function for possible values
of HttpVerb enum. |
getUri() |
none |
string |
To get the Uri of http request |
setUri($uri) |
$uri: String representing the uri |
none |
To set the Uri of http request. |
getCredential() |
none |
Credential instance reference |
To get the reference to ObjectContext::Credential member variable. The
reference can be one of the following type:
WindowsCredential
AzureTableCredential
ACSCredential |
setCredential($credential) |
Reference to Credential instance. |
none |
To set the credential to be used for request. The possible types are:
WindowsCredential
AzureTableCredential
ACSCredential |
getProxy() |
none |
Reference to HttpProxy instance |
To get the reference to ObjectContext::HttpProxy member variable |
setProxy($proxy) |
$proxy: HttpProxy instance. |
none |
To set the http proxy to be used for request |
The ‘Headers’ Member variable of HttpRequest give access to the request headers.
The methods exposed by this member variable are:
Prototype |
Parameters |
Return Value |
Description |
Add($key, $value) |
$key: header name
$value: header value |
none |
To add a new key-value (headername-value) pair to the
HttpRequest::Headers collection. If an item with key exists then
overwrite it. |
Remove($key) |
$key: header name |
none |
To remove key-value (headername-value) pair from the
HttpRequest::Headers collection identified by $key |
TryGetValue($key, &$value) |
$key: header name
$value [out]: header vlaue |
boolean |
If a headername-value pair exists with header name as $key, set the out
parameter $value with header value and return true. If not found return
false. |
HasKey($key) |
$key: header name |
boolean |
Returns true if a headername-value pair exists with headername as $key,
else false. |
GetAllKeys() |
none |
array |
To get the all header name as an array. |
GetAll() |
none |
array |
To get all headername-value pair as array. |
Clear() |
none |
none |
To remove all headername-value pairs. |
CopyFrom($srcArray) |
$srcArray: array holding headername-value paris. |
none |
To merge the contents of $srcArray with existing headername-value pairs. |
The following sample shows how to use the call-back feature to add authorization
header to the http request headers before making request to an ACS protected WCF
Data Service.
Note: Refer Samples/WCFDataServices/ACSNorthWindDataServices to see how to
configure a service with acs authentication.
define("ACS_NORTHWIND_SERVICE_URL",
"http://localhost:13985/ACSNorthWindDataService.svc");
define("SERVICE_NAMESPACE", 'wcfdataservice');
define("ACS_USER", 'infocorp');
define("ACS_PWD", 'qfimKj6Vm6HKAJryTRno5USPpvz0c8bBzFhlqKex6lQ=');
define("ACS_APPLIESTO", 'http://localhost/WCFNorthWindService/');
/**
* CallBack function, this will be
invoked before making request to
* OData service.
*/
function OnBeforeCallBack($httpRequest)
{
$proxy = new
HttpProxy(PROXY_HOST, PROXY_PORT);
//Use the ACSUtil
helper class of toolkit
$acsutil = new
ACSUtil(SERVICE_NAMESPACE,
ACS_USER,
ACS_PWD,
ACS_APPLIESTO,
array(),
$proxy);
try
{
//Get the ACS Token
$token = $acsutil->GetACSToken();
//Format the token in the way DataService Expects
$authHeaderValue = 'WRAP access_token="' . urldecode($token) . '"';
//Add the acs auth header
$httpRequest->Headers->Add('authorization', $authHeaderValue);
}
catch(ACSUtilException $ex)
{
echo 'Failed to get the ACS token' . "<br/>";
echo $ex->getError();
exit;
}
}
try
{
$svc = new
NorthwindEntities1(ACS_NORTHWIND_SERVICE_URL);
//Regitser
the call-back
$svc->OnBeforeRequest('OnBeforeCallBack', null);
$query =
$svc->Customers()->filter("CustomerID eq 'ALFKI'");
$customersResponse = $query->Execute();
if(count($customersResponse->Result))
{
echo "<br/><br/>Customer Information <br/><br/>";
$customer = $customersResponse->Result[0];
echo 'CustomerID: ' . $customer->CustomerID . "<br/>";
echo 'CompanyName: ' . $customer->CompanyName . "<br/>";
}
else
{
echo "Failed to retrieve the Customer with ID 'ALFKI'";
}
}
catch(DataServiceRequestException $ex)
{
echo 'Error: while running the query ' . $ex->Response->getQuery();
echo "<br/>";
echo $ex->Response->getError();
}
catch (ODataServiceException $e)
{
echo "Error:" .
$e->getError() . "<br>" . "Detailed Error:" . $e->getDetailedError();
}
In this above sample we used ACSUtil class to retrieve ACS token, then added
this token as value of http authorization header. You can also use ACSCredential
class and HttpRequest::Credential instead of ACSUtil as in
the call-back follows:
function OnBeforeCallBack($httpRequest)
{
$proxy = new
HttpProxy(PROXY_HOST, PROXY_PORT);
$acsCredential = new ACSCredential(SERVICE_NAMESPACE,
ACS_USER,
ACS_PWD,
ACS_APPLIESTO,
array(), /*additional claims*/
$proxy /*proxy
required to access ACS
note that we will not set $svc->HttpProxy
because the service is running locally
*/
);
$httpRequest->setCredential($acsCredential);
}
OnAfterRepsonse
API can be used to register a function
which is to be invoked after receiving response from the WCF Data Service and
before converting the response to entity instances in the context.
Once the client application register a
call-back function using OnAfterResponse API, after receiving the response from
the WCF Data Service, the oData library will invoke the registered function by
passing a reference to the HttpResponse object which contains the response
information.
The HttpResponse exposes the following methods:
Prototype |
Parameters |
Return Value |
Description |
getBody() |
none |
string |
To get the raw body of the http response. |
getHTMLFriendlyBody() |
none |
string |
To get the body of the http response as a HTML friendly string. |
getHeaders() |
none |
array |
To get the HTTP response headers as array of headername-value paris. |
getHeader($key) |
$key: string |
string |
To get value of header identified by the header name $key |
getMessage() |
none |
string |
To get the Http message associated with the response. e.g. ‘Created’,
‘Not Found’ etc.. |
getCode() |
none |
integer |
To get the http code associated with the response. |
The following sample shows how to use the call-back feature to print the
response body for query from server.
function OnAfterCallBack($httpResponse)
{
echo '<h1>The HTML
Friendly Response body</h1>' .
"<br/>";
echo
$httpResponse->getHTMLFriendlyBody();
exit;
}
$svc = new NorthwindEntities();
try
{
$svc = new
NorthwindEntities();
$svc->OnAfterResponse('OnAfterCallBack', null);
$query =
$svc->Customers()->filter("Country eq 'USA'")
$customersResponse =
$query->Execute();
}
catch(DataServiceRequestException $exception)
{
echo 'Error during
running the query:';
echo "<br/>";
echo
$exception->Response->getError();
exit;
}
Note: The library will throw
InvalidOperation exception if the definition of call-back function not found
while trying to invoke them.
Authentication in the Client Library
This toolkit can work against WCF Data Service hosted on IIS with Windows (or
anonymous) authentication or Azure Storage Tables with Azure Authentication or
WCF Data Services with ACS authentication. By default, the client library
assumes the WCF Data Service uses an anonymous Windows connection so user
credentials don't need to be specified. However, you can set the Credentials
property in the proxy object to point to an object holding the credential
information. The Credential property will be used if the WCF Data Service
requires the user to be authenticated.
Windows Authentication:
$proxy->Credential = new WindowsCredential(domain\user-name, password);
Azure Table Authentication:
$proxy->Credential = new AzureTableCredential(account-name, account-key);
ACS Authentication:
$proxy->Credential = new ACSCredential(service_namespace, wrap_name,
wrap_password, wrap_scope, claims /*array of claims*/, proxy /*HttpProxy
instance*/);
Note that one can pass proxy settings as an argument of ACSCredential class.
This is required if your service is running in localmachine with acs
authentication, In this case proxy information is required to retrieve token
from acs, but not required for accessing the service (so no need to set
HttpProxy member variable of generated proxy class)
Custom authentication Support
In more advanced scenario, the client application might need to set custom
headers; registering call-back using
OnBeforeRequest API will help in this case.
Connecting to Data Service through proxy server
If connecting to data service requires proxy server, you can set the Proxy
property in the Http proxy object to point to an object holding the proxy server
information.
$proxy->HttpProxy = new HttpProxy(proxy-server-name, proxy-server-port,
proxy-user-name, proxy-password);
The proxy-user-name and proxy-password are optional, which are needed only if
the proxy requires authentication.
Reference Section
List of APIs supported by PHP Client Library.
API Prototype |
Parameters |
Return Value |
Description |
Execute (query-expression) |
The query to be executed against data service. Please refer ‘Data
Service Queries’ section for more details |
OnSuccess:
Returns result of query as a collection. OnFailure: If any error
occurs (ex: malformed query), then client library will throw
DataServiceRequestException exception with proper message. |
To execute queries against the data service |
AddObject(TargetEntitySet, Object) |
TargetEntitySet
: The name of entity set to which Object belongs to. Object : PHP
Object represents the instance of a data service entity to be inserted. |
If any error occurs (ex: if entity instance already exists), then client
library will throw
InvalidOperation exception with proper message |
To create a new instance in the data service |
UpdateObject(Object) |
Object
: PHP Object represents the existng instance of a data service entity,
which is to be updated. |
If any error occurs (ex: if entity instance not exists), then client
library will throw
InvalidOperation exception with proper message. |
To modify an existing entity instance. |
DeleteObject(Object) |
Object
: PHP Object represents the existing instance of a data service entity,
which is to be deleted. |
If any error occurs (ex: if entity instance not exists), then client
library will throw
InvalidOperation exception with proper message. |
To delete an entity instance. |
AddLink(Source, SourceProperty, Target) |
Source:
The source object in the link. SourceProperty: The name of the
property on the source object that represents the source in the link
between the source and the target. Target: The target object
involved in the link that is bound to the source object. |
If any error occurs, then client library will throw
InvalidOperation exception
with proper message. |
To add a link between two entity instances, assuming that there is a
relationship between Two entities. |
DeleteLink(Source, SourceProperty, Target) |
Source:
The source object in the link. SourceProperty: The name of the
property on the source object that represents the source in the link
between the source and the target. Target: The target object
involved in the link that is bound to the source object. |
If any error occurs, then client library will throw
InvalidOperation exception
with proper message. |
To delete link between two entity instances. |
SetLink(Source, SourceProperty, Target) |
Source:
The source object in the link. SourceProperty: The name of the
property on the source object that represents the source in the link
between the source and the target. Target: The target object
involved in the link that is bound to the source object. |
If any error occurs, then client library will throw
InvalidOperation exception
with proper message. |
To add a link between two entity instances, assuming that there is a
relationship between Two entities |
AddHeader(Header-Name, Header-Value) |
Header-Name:
The name of custom HTTP header Header-Value: The value of custom
HTTP header. |
No return value. |
To add custom headers |
RemoveHeaders() |
No argument |
No Return value |
To Remove all custom headers aded by user |
SaveChanges() |
No arguments |
Throws InvalidOperation
exception if any error occurred while generating atom pub xml to be send
to the service.
Thows ODataServiceException exception with proper message if WCF
Data Service returns any error. |
To save the changes made to the entities in the context to data service. |
SetSaveChangesOptions($scOptions) |
scOptions:
SaveChanges Option. Possible values are
SaveChangesOptions::None
SaveChangesOptions::Batch |
Throws InvalidOperation, if
user passes invalid parameter. |
To set the SaveChanges mode, if mode is Batch all the changes will be
packed in a single HTTP request, otherwise framework will generate
separate HTTP request for each change. The Default mode is Batch. |
SetReplaceOnUpdate($rOnUpdate) |
rOnUpdate:
boolean |
none |
Used to decide the HTTP verb used for updation. If this flag is true
framework will use PUT verb, else MERGE verb. |
GetReadStream($entity, $args = null) |
entity:
The entity that has the binary property to retrieve
args:null, string or object of DataServiceRequestArgs. |
Throws InvalidOperationif
args is not null, string or instance of DataServiceRequestArgs,
exception will throw if entity is not a media link entry. If WCF Data
Service returns any error this API will throw
ODataServiceException. |
Synchronously requests a data stream that contains the binary property
of requested Media Link
Entry $entity. The $args argument can be null,
a
string representing Accept message header or instance of
DataServiceRequestArgs
class
which contains settings for the HTTP request message (Slug, Accept,
Content-Type etc..) |
GetReadStreamUri($entity) |
entity:
The entity that has the binary property to retrieve |
Throws InvalidOperation, if
entity is not currently tracked by the context. |
Gets the URI that is used to return binary property data as a data
stream |
SetSaveStream($entity, $stream, $closeStream, $contentType, $slug) |
entity:
The entity that has the binary property to set.
stream:
The binary stream to set.
contentType:
The type of stream
slug:
The default path and name to be given to the stream once it
is uploaded. |
Throws InvalidOperation, if
any one of the following criteria is not satisfied.
contentType
cannot be null
slug
cannot be null
stream
cannot be null
context should track the entity. |
Sets a new data stream as the binary property of an entity, with the
specified settings in the
request message |
OnBeforeReqest($function_name, $class_instance) |
function_name:
The name of the function to be registered as call-back.
class_instance:
If the above function is a member function of a class, then this will be
the reference to that class else null. |
none |
To register call-back function to be invoked before sending any request
to the service. |
OOnAfterResponse($function_name, $class_instance) |
function_name: /span>
The name of the function to be registered as call-back.
class_instance:
If the above function is a member function of a class, then this will be
the reference to that class else null. |
none |
To register call-back function to be invoked after receiving any
response from the service. |