Permalink
Browse files

First rough version

There's still a lot missing but the basics work.
  • Loading branch information...
lanthaler committed Feb 7, 2013
1 parent 7b9cef4 commit 2e5a8dbe5f7200a3f70ba89c23070be2954ed804
Showing with 1,152 additions and 1 deletion.
  1. +3 −0 .gitignore
  2. +219 −0 Client.php
  3. +135 −0 Command.php
  4. +70 −0 Document.php
  5. +46 −0 DocumentFactory.php
  6. +79 −0 Graph.php
  7. +60 −0 Hydra.php
  8. +111 −0 IriTemplate.php
  9. +19 −0 LICENSE
  10. +218 −0 Node.php
  11. +163 −0 Operation.php
  12. +4 −1 README.md
  13. +25 −0 composer.json
@@ -0,0 +1,3 @@
vendor
composer.lock
phpunit.xml
@@ -0,0 +1,219 @@
<?php
/*
* (c) Markus Lanthaler <mail@markus-lanthaler.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ML\HydraClient;
use ML\JsonLD\JsonLD;
use ML\JsonLD\GraphInterface;
use Guzzle\Common\Collection;
use Guzzle\Service\Client as ServiceClient;
/**
* Hydra Client
*
* @author Markus Lanthaler <mail@markus-lanthaler.com>
*/
class Client extends ServiceClient
{
/**
* @var GraphInterface The graph containing the API documentation.
*/
protected $apiDocumentation;
/**
* Factory method to create a new Hydra Client
*
* The following array keys and values are available options:
* - entrypoint: The Web API's entry point, an absolute URL
* - documentation: A direct link to the Web API's documentation; if
* not passed, the client will try to discover the
* documentation using the passed entry point
* - context: A context used to abbreviate long IRIs
*
* @param array|Collection $config Configuration data
*
* @return self
*/
public static function factory($config = array())
{
$default = array(
'documentation' => null,
'context' => null
);
$required = array('entrypoint');
$config = Collection::fromConfig($config, $default, $required);
$client = new static($config->get('entrypoint'), $config);
$doc = $config->get('documentation') ?: $config->get('entrypoint');
$client->apiDocumentation = JsonLD::getDocument(
$doc,
array('base' => $config->get('entrypoint'), 'documentFactory' => new DocumentFactory($client))
)->getGraph();
return $client;
}
/**
* {@inheritdoc}
*
* @param string $baseUrl Base URL of the web service
* @param array|Collection $config Configuration settings
*/
public function __construct($baseUrl = '', $config = null)
{
parent::__construct($baseUrl, $config);
$this->context = $this->getConfig('context');
$this->getConfig()->remove('context');
$this->setUserAgent(sprintf(
'HydraClient/1.0 (PHP %s; curl %s)',
PHP_VERSION,
\Guzzle\Http\Curl\CurlVersion::getInstance()->get('version')
));
}
/**
* Get the API documentation
*
* @return GraphInterface The API documentation.
*/
public function getApiDocumentation()
{
return $this->apiDocumentation;
}
/**
* Get an operation by IRI
*
* @param string $iri The IRI of the operation to retrieve.
*
* @return null|Operation The operation or null if not found.
*/
public function getOperation($iri)
{
if (null === ($operation = $this->apiDocumentation->getNode($iri))) {
return null;
}
return new Operation($operation);
}
/**
* Get all operations documented in the API documentation
*
* @return array[Operation] The documented operations.
*/
public function getOperations()
{
$operations = $this->apiDocumentation->getNodesByType(Hydra::Operation);
return array_map(function ($op) { return new Operation($op); }, $operations);
}
/**
* Get a command by IRI
*
* @param string $iri The IRI of the operation defining the command to retrieve.
* @param array $args Arguments to pass to the command.
*
* @return CommandInterface
* @throws InvalidArgumentException If the operation can not be found.
*/
public function getCommand($iri, array $args = array())
{
// if (!($command = $this->getCommandFactory()->factory($iri, $args))) {
// throw new InvalidArgumentException("Command was not found matching {$iri}");
// }
if (null === ($operation = $this->getOperation($iri))) {
throw new \Exception('Operation with the passed IRI not found.');
}
/***************************************************************
Construct IRI
Target IRI known -> target IRI must be known
or
Expand IRI template -> requires template and variable values
****************************************************************/
$nodeId = null;
if (isset($args['@id'])) {
$target = $nodeId = $args['@id'];
unset($args['@id']);
} else {
if (null === ($template = $operation->getIriTemplate())) {
throw new \Exception('Passed no node and no IRI template is defined for this operation');
}
$target = $template->expand($args);
}
if (null !== ($type = $operation->getExpectedType())) {
$graph = new Graph();
$node = $graph->createNode($nodeId);
$node->setType($graph->createNode($type->getId()));
// TODO Add additional types if passed!?
// if (isset($args['@type'])) {
// $types = (is_array(($args['@type'])) ? $args['@type'] : array($args['@type']));
// foreach ($types as $type) {
// if ($type instanceof NodeInterface) {
// $type = $type->getId();
// } elseif (false === is_string($type)) {
// var_dump($type);
// throw new \Exception('Invalid @type passed');
// }
// $node->addType($graph->createNode($type));
// }
// unset($args['@type']);
// }
// TODO Switch to Hydra::properties as soon as it is available
$properties = $type->getReverseProperty('http://www.w3.org/1999/02/22-rdf-syntax-ns#domain');
foreach ($properties as $property) {
$property = $property->getId();
if (isset($args[$property])) {
$node->setProperty($property, $args[$property]);
}
}
} else {
$graph = null;
}
$command = new Command($operation, $graph, $target, $args);
// ---------- From here on the same as Service/Client ---------------
$command->setClient($this);
// Add global client options to the command
if ($command instanceof Collection) {
if ($options = $this->getConfig(self::COMMAND_PARAMS)) {
foreach ($options as $key => $value) {
if (!$command->hasKey($key)) {
$command->set($key, $value);
}
}
}
}
$this->dispatch('client.command.create', array(
'client' => $this,
'command' => $command
));
return $command;
}
}
@@ -0,0 +1,135 @@
<?php
/*
* (c) Markus Lanthaler <mail@markus-lanthaler.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ML\HydraClient;
use ML\JsonLD\JsonLD;
use Guzzle\Service\Command\AbstractCommand;
use Guzzle\Service\Exception\CommandException;
use Guzzle\Common\Collection;
// TODO Abstract command depends on OperationInterface, should be changed in Guzzle
class Command extends AbstractCommand
{
protected $graph;
protected $target;
/**
* Constructor
*
* @param array|Collection $parameters Collection of parameters to set on the command
*
* TODO $operation parameter uses a different type than abstract command
*
* @param Operation $operation Command definition from API documentation
*/
public function __construct(Operation $operation, $graph = null, $target = null, $parameters = null)
{
parent::__construct($parameters);
$this->operation = $operation;
$this->graph = $graph;
$this->target = $target;
$headers = $this->get(self::HEADERS_OPTION);
if (!$headers instanceof Collection) {
$this->set(self::HEADERS_OPTION, new Collection((array) $headers));
}
// You can set a command.on_complete option in your parameters to set an onComplete callback
if ($onComplete = $this->get('command.on_complete')) {
$this->remove('command.on_complete');
$this->setOnComplete($onComplete);
}
$this->init();
}
/**
* {@inheritdoc}
*/
protected function build()
{
/***************************************************************
Serialize body -> requires data
Query for expects type
Serialize result
or
Just serialize graph and let server do the work?
Perhaps check at least if graph contains expects!?
****************************************************************/
$headers = null;
if (null !== ($expected = $this->operation->getExpectedType(true))) {
if (null === $this->graph) {
throw new \Exception("The operation expects $expected but no graph has been passed.");
}
$nodes = $this->graph->getNodesByType($expected);
if (0 === count($nodes)) {
throw new \Exception("The operation expects $expected but no such nodes have been found in the passed graph.");
}
}
if (null === $this->graph) {
$body = null;
} else {
$body = JsonLD::toString(JsonLD::compact($this->graph->toJsonLd()), true); // TODO Do pretty print by default?
$headers = array('Content-Type' => 'application/ld+json');
}
$this->request = $this->client->createRequest($this->operation->getHttpMethod(), $this->target, $headers, $body);
}
/**
* {@inheritdoc}
*/
protected function validate()
{
// This method overwrites AbstractCommand's validate method to disable validation completely.
}
/**
* Initialize the command (hook that can be implemented in subclasses)
*/
protected function init() {
// TODO Called from constructor, remove if not needed
}
/**
* Processes the response
*/
protected function process()
{
/***************************************************************
Parse response
graph = JsonLD::getDocument()
primaryNode = graph->getNodeByType(returns)
****************************************************************/
// Do not process the response if 'command.response_processing' is set to 'raw'
if ($this->get(self::RESPONSE_PROCESSING) === self::TYPE_RAW) {
$this->result = $this->request->getResponse();
} else {
// TODO What about Content-Location? Make this smarter! There are several cases to consider
$response = $this->request->getResponse();
$base = $response->getLocation();
if (null === $base) {
$base = $this->request->getUrl();
}
$this->result = JsonLD::getDocument($response->getBody(true), array('base' => $base));
}
return $this->result;
}
}
Oops, something went wrong.

0 comments on commit 2e5a8db

Please sign in to comment.