OData - the best way to REST
www/odata-example
directory for the first project with OData. Create a file composer.json
with the following contents: { "require": { "qeti/simple-podata": ">=0.9.1" } }
composer install
localhost/odata-example/odata.svc$metadata
localhost/odata-example/odata.svc$metadata
.localhost/odata-example/odata.svc
localhost/odata-example/odata.svc
) went to index.php
. To do this, in the root of the project, place the .htaccess
file with the following contents: <IfModule mod_rewrite.c> RewriteEngine on RewriteRule (\.svc.*) index.php </IfModule>
IHTTPRequest
IHTTPRequest
interface. This class will use POData to get URL parameters. The implementation may vary depending on the framework used. We will go in the simple way and we will transfer in the designer $ _GET. Create a file RequestAdapter.php
with the following contents: <?php use POData\Common\ODataConstants; use POData\OperationContext\HTTPRequestMethod; use POData\OperationContext\IHTTPRequest; class RequestAdapter implements IHTTPRequest { protected $request; public function __construct($request) { $this->request = $request; } /** * get the raw incoming url * * @return string RequestURI called by User with the value of QueryString */ public function getRawUrl() { return $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . '/' . $_SERVER['REQUEST_URI']; } /** * get the specific request headers * * @param string $key The header name * * @return string|null value of the header, NULL if header is absent. */ public function getRequestHeader($key) { if (isset($this->request[$key])) { return $headers = $this->request[$key]; } return null; } /** * Returns the Query String Parameters (QSPs) as an array of KEY-VALUE pairs. If a QSP appears twice * it will have two entries in this array * * @return array[] */ public function getQueryParameters() { $data = []; if (is_array($this->request)) { foreach ($this->request as $key => $value) { $data[] = [$key => $value]; } } return $data; } /** * Get the HTTP method/verb of the HTTP Request * * @return HTTPRequestMethod */ public function getMethod() { return new HTTPRequestMethod('GET'); } }
IOperationContext
OutgoingResponse
, which is already implemented in POData so you can not deal with this class at this stage. Create an OperationContextAdapter.php
file: <?php use POData\OperationContext\IHTTPRequest; use POData\OperationContext\IOperationContext; use POData\OperationContext\Web\OutgoingResponse; class OperationContextAdapter implements IOperationContext { /** * @var RequestAdapter; */ protected $request; protected $response; /** * @param yii\base\Request $request */ public function __construct($request) { $this->request = new RequestAdapter($request); $this->response = new OutgoingResponse(); } /** * Gets the Web request context for the request being sent. * * @return OutgoingResponse reference of OutgoingResponse object */ public function outgoingResponse() { return $this->response; } /** * Gets the Web request context for the request being received. * * @return IHTTPRequest reference of IncomingRequest object */ public function incomingRequest() { return $this->request; } }
IQueryProvider
DocumentHasProduct
URL will be converted to the name of the document_has_product
table. If this does not suit you, you can override the getTableName()
method.QueryProvider.php
file: <?php use qeti\SimplePOData\BaseQueryProvider; class QueryProvider extends BaseQueryProvider { public function __construct(\PDO $db){ parent::__construct($db); } /** * Get associated array with rows * @param string $sql SQL query * @param array $parameters Parameters for SQL query * @return mixed[]|null */ protected function queryAll($sql, $parameters = null) { $statement = $this->db->prepare($sql); $statement->execute($parameters); return $statement->fetchAll(PDO::FETCH_ASSOC); } /** * Get one value * @param string $sql SQL query * @param array $parameters Parameters for SQL query * @return mixed|null */ protected function queryScalar($sql, $parameters = null) { $statement = $this->db->prepare($sql); $statement->execute($parameters); $data = $statement->fetchAll(PDO::FETCH_COLUMN); if ($data) { return $data[0]; } return null; } }
CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, added_at TIMESTAMP DEFAULT NOW(), name VARCHAR(250), weight DECIMAL(10, 4), code VARCHAR(45) );
INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (6,'2013-05-07 00:00:00','Kedi',2.9200,'Ked-25'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (9,'2009-08-05 00:00:00','Kedi',10.9100,'Ked-51'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (13,'2003-02-27 00:00:00','Kedi',11.7300,'Ked-17'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (29,'2014-12-19 00:00:00','Kedi',7.6100,'Ked-29'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (33,'2003-07-05 00:00:00','Kedi',11.8700,'Ked-99'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (36,'2015-09-15 00:00:00','Kedi',11.0000,'Ked-89'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (40,'2004-01-25 00:00:00','Kedi',14.8800,'Ked-83'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (47,'2006-04-23 00:00:00','Kedi',1.2100,'Ked-62'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (51,'2012-12-08 00:00:00','Kedi',12.4000,'Ked-86'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (54,'2010-06-09 00:00:00','Kedi',6.3800,'Ked-61'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (58,'2010-04-25 00:00:00','Kedi',8.8900,'Ked-74'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (106,'2004-04-11 00:00:00','Kedi',6.7100,'Ked-44'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (134,'2001-02-07 00:00:00','Kedi',2.3200,'Ked-29'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (153,'2002-01-13 00:00:00','Kedi',7.3300,'Ked-80'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (156,'2014-03-20 00:00:00','Kedi',10.9600,'Ked-30'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (165,'2003-07-11 00:00:00','Kedi',2.5300,'Ked-90'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (176,'2010-09-26 00:00:00','Kedi',7.0100,'Ked-38'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (182,'2007-05-07 00:00:00','Kedi',3.8900,'Ked-6'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (194,'2004-03-21 00:00:00','Kedi',3.1000,'Ked-20'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (205,'2000-06-02 00:00:00','Kedi',12.9500,'Ked-20'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (212,'2002-02-20 00:00:00','Kedi',2.5300,'Ked-62'); INSERT INTO `product` (`id`,`added_at`,`name`,`weight`,`code`) VALUES (220,'2000-10-19 00:00:00','Kedi',8.4000,'Ked-31');
models/Product.php
: <?php namespace models; use qeti\SimplePOData\EntityTrait; class Product { // This trait contains method for fields mapping (between database table and this class) use EntityTrait; public $id; public $added_at; public $name; public $weight; public $code; }
EntityTrait
trait, which implements the mapping of the names of the properties of the class and the fields of the base. In this implementation, the names of the database fields exactly match the name of the class properties. Those who are not satisfied for some reason, can simply make another implementation of the static fromRecord()
method.models/MetadataProvider.php
: <?php namespace models; use POData\Providers\Metadata\Type\EdmPrimitiveType; use POData\Providers\Metadata\SimpleMetadataProvider; class MetadataProvider { const MetaNamespace = "Data"; /** * Description of service * * @return IMetadataProvider */ public static function create() { $metadata = new SimpleMetadataProvider('Data', self::MetaNamespace); $metadata->addResourceSet('Products', self::createProductEntityType($metadata)); return $metadata; } /** * Describtion of Products */ private static function createProductEntityType(SimpleMetadataProvider $metadata) { $et = $metadata->addEntityType(new \ReflectionClass('\models\Product'), 'Products', self::MetaNamespace); $metadata->addKeyProperty($et, 'id', EdmPrimitiveType::INT32); $metadata->addPrimitiveProperty($et, 'added_at', EdmPrimitiveType::DATETIME); $metadata->addPrimitiveProperty($et, 'name', EdmPrimitiveType::STRING); $metadata->addPrimitiveProperty($et, 'weight', EdmPrimitiveType::DECIMAL); $metadata->addPrimitiveProperty($et, 'code', EdmPrimitiveType::STRING); return $et; } }
localhost/odata-example/odata.svc/Products
localhost/odata-example/odata.svc/Products
) and what are the fields in it.addKeyProperty()
defines a key field. This field is used to filter when you select a specific entry by requesting localhost/odata-example/odata.svc/Products(1)
localhost/odata-example/odata.svc/Products(1)
. addPrimitiveProperty()
defines a regular field.index.php
, in which you need to connect the created classes, create a connection to the database and ask POData to process the request. <?php use POData\OperationContext\ServiceHost; use qeti\SimplePOData\DataService; require(__DIR__ . '/vendor/autoload.php'); require(__DIR__ . '/OperationContextAdapter.php'); require(__DIR__ . '/RequestAdapter.php'); require(__DIR__ . '/QueryProvider.php'); require(__DIR__ . '/models/MetadataProvider.php'); require(__DIR__ . '/models/Product.php'); // DB Connection $dsn = 'mysql:dbname=yourdbname;host=127.0.0.1'; $user = 'username'; $password = 'password; $db = new \PDO($dsn, $user, $password); // Realisation of QueryProvider $db->queryProviderClassName = '\\QueryProvider'; // Controller $op = new OperationContextAdapter($_GET); $host = new ServiceHost($op); $host->setServiceUri("/odata.svc/"); $service = new DataService($db, \models\MetadataProvider::create()); $service->setHost($host); $service->handleRequest(); $odataResponse = $op->outgoingResponse(); // Headers for response foreach ($odataResponse->getHeaders() as $headerName => $headerValue) { if (!is_null($headerValue)) { header($headerName . ': ' . $headerValue); } } // Body of response echo $odataResponse->getStream();
odata.svc
<service xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app"xml:base="http://localhost:80/OData-base-example/odata.svc"> <workspace> <atom:title>Default</atom:title> <collection href="Products"> <atom:title>Products</atom:title> </collection> </workspace> </service>
odata.svc/$metadata
returns a description of the entities. <edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0"> <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"m:DataServiceVersion="1.0"> <Schema xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"xmlns="http://schemas.microsoft.com/ado/2007/05/edm" Namespace="Data"> <EntityType Name="Products"> <Key> <PropertyRef Name="id"/> </Key> <Property Name="id" Type="Edm.Int32" Nullable="false"/> <Property Name="added_at" Type="Edm.DateTime" Nullable="true"/> <Property Name="name" Type="Edm.String" Nullable="true"/> <Property Name="weight" Type="Edm.Decimal" Nullable="true"/> <Property Name="code" Type="Edm.String" Nullable="true"/> </EntityType> <EntityContainer Name="Data" m:IsDefaultEntityContainer="true"> <EntitySet Name="Products" EntityType="Data.Products"/> </EntityContainer> </Schema> </edmx:DataServices> </edmx:Edmx>
odata.svc/Products
returns all records from the Products collection. If this is a large collection, then you should not call it that way - it is better to limit the selection. For example, if you call odata.svc/Products?&$format=json&$filter=id le 5&$orderby=id desc
, {"odata.metadata":"http://localhost:80/odata-example/odata.svc/$metadata#Products","value": [{"id":5,"added_at":"2006-07-14T00:00:00","name":"Kon","weight":"14.1700","code":"Kon-59"}, {"id":4,"added_at":"2014-03-16T00:00:00","name":"Kon","weight":"2.4100","code":"Kon-89"}, {"id":3,"added_at":"2009-07-23T00:00:00","name":"Bicycle","weight":"4.3100","code":"Bic-18"}, {"id":2,"added_at":"2000-03-25T00:00:00","name":"Samokat","weight":"8.0200","code":"Sam-96"}, {"id":1,"added_at":"2006-10-22T00:00:00","name":"Kolyaska","weight":"10.1300","code":"Kol-97"}]}
$filter
, here it is worth paying special attention that conditions can be complicated, use brackets, and, or operators and much more. When parsing the conditions, the necessary checks are performed and the possibility of introducing SQL injections is excluded.odata.svc/Products?$skip=10&$top=5&$format=json&$select=id,name&$inlinecount=allpages
{"odata.metadata":"http://localhost:80/odata-example/odata.svc/$metadata#Products", "odata.count":"1002", "value": [{"id":11,"name":"Motoroller"}, {"id":12,"name":"Kolyaska"}, {"id":13,"name":"Kedi"}, {"id":14,"name":"Roliki"}, {"id":15,"name":"Doska"}]}
$inlinecount=allpages
you receive in the response in the odata.count field the number of records in the sample, as if the LIMIT operator was not used in the SQL query.odata.svc/Products(5)?$format=json
{ "odata.metadata":"http://localhost:80/odata-example/odata.svc/$metadata#Products/@Element", "id":1, "added_at":"2006-10-22T00:00:00", "name":"Kolyaska", "weight":"10.1300", "code":"Kol-97" }
odata.svc/Products/$count
- the number of records.Source: https://habr.com/ru/post/267811/
All Articles