PHP Classes

File: src/Server.php

Recommend this page to a friend!
  Classes of Till Wehowski   PHP JSON RPC Discoverable Server   src/Server.php   Download  
File: src/Server.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP JSON RPC Discoverable Server
Implements a JSON RPC server that is discoverable
Author: By
Last change:
Date: 2 years ago
Size: 9,280 bytes
 

Contents

Class file image Download
<?php

declare(strict_types=1);

namespace
frdlweb\Api\Rpc;


use
frdlweb\Api\Rpc\DiscoverMethod;
use
frdlweb\Api\Rpc\MethodDiscoverableInterface;

use
frdlweb\Api\Rpc\SchemaLoader;

use
LogicException;
use
Psr\Container\ContainerExceptionInterface;
use
Psr\Container\ContainerInterface;
use
Psr\Container\NotFoundExceptionInterface;



use
TypeError;
use
UMA\JsonRpc\Error;
use
UMA\JsonRpc\Request;
use
UMA\JsonRpc\Response;
use
UMA\JsonRpc\Internal\Assert;
use
UMA\JsonRpc\Internal\Input;
use
UMA\JsonRpc\Internal\MiddlewareStack;
use
UMA\JsonRpc\Internal\Validator;
use
UMA\JsonRpc\Procedure;
use
Opis\JsonSchema\Validator as OpisValidator;


class
Server /* extends \UMA\JsonRpc\Server */
{

    protected
$config = [];
   
   
/**
     * @var ContainerInterface
   */
   
protected $container;
 
   
/**
     * @var string[]
   */
   
protected $methods;
 
   
/**
     * @var string[]
    */
   
protected $middlewares;
 
   
/**
     * @var int|null
    */
   
protected $batchLimit;
    
    public function
__construct(ContainerInterface $container, int $batchLimit = null, array $config = null, bool $discovery = true)
    {
        if(!
is_array($config)){
         
$config = [];
        }
       
$this->config = array_merge([
       
'schemaLoaderPrefix' => '',
       
'schemaLoaderDirs' => [],
   
// 'schemaCacheDir' => __DIR__.\DIRECTORY_SEPARATOR.'schema-store'.\DIRECTORY_SEPARATOR,
       
'schemaCacheDir' => sys_get_temp_dir() . \DIRECTORY_SEPARATOR . get_current_user(). \DIRECTORY_SEPARATOR . 'json-schema-store' . \DIRECTORY_SEPARATOR,
       
'discovery' => $discovery,
       
'meta' => [
         
'openrpc' => '1.0.0-rc1',
         
"info" => [
             
"title" => "JSON-RPC Server",
             
"description" =>"This the RPC-part of an Frdlweb API Server definition https://look-up.webfan3.de/?goto=oid%3A1.3.6.1.4.1.37553.8.1.8.1.13878",
             
"version" => "1.0.0",
          ],
         
'servers' => [
            [
            
'name' => 'Webfan Homepagesystem RPC API',
            
'summary' => 'Webfan Homepagesystem RPC API methods description',
            
'description' => 'This is the RPC part of an implementation of the Frdlweb API Specification (1.3.6.1.4.1.37553.8.1.8.1.13878)',
            
'url' => 'https://'.$_SERVER['SERVER_NAME'].'/software-center/modules-api/rpc/0.0.2/',
            ]
             
          ],
           
'methods' => [],
           
'components' => [
                
'links' => [],
                
'contentDescriptors' => [],
                
'schemas' => [],
                
'examples' => [],
             
              ],
         ],
        ],
$config);
       
       
/*
        parent::__construct($container, $batchLimit);
        */
       
$this->container = $container;
       
$this->batchLimit = $batchLimit;
       
$this->methods = [];
       
$this->middlewares = [];

       
        if(
true === $this->config['discovery']){
           
/* $this->setDiscovery(DiscoverMethod::class, [$this, 'discoveryFactory']); */
           
               
$callable = [$this, 'discoveryFactory'];
               
$this->setDiscovery(DiscoverMethod::class,static function(ContainerInterface $c) use($callable){
                    return
call_user_func_array($callable, func_get_args());
                });
               
        }
    }

   public function
discoveryFactory(ContainerInterface $c) : MethodDiscoverableInterface{
         
$DiscoverMethod = new DiscoverMethod($this);
         
$DiscoverMethod->config(null, $this->config['meta']);
      
      
       return
$DiscoverMethod;
   }


   
  public function
setDiscovery($serviceId, callable $factory){
   if(!
$this->getContainer()->has( $serviceId)){
     if(
        
$this->container instanceof \Di\CompiledContainer
        
|| 'CompiledContainer' === basename(get_class($this->container))
      
       ) {
        
$this->getContainer()->set( $serviceId, call_user_func_array($factory, [$this->container]));
     }elseif(
$factory instanceof \closure || 'ContainerBuilder' === basename(get_class($this->container)) ){
        
$this->getContainer()->set( $serviceId, $factory);
     }else{
        
//$this->getContainer()->set( $serviceId, $factory);
         
$this->getContainer()->set( $serviceId, call_user_func_array($factory, [$this->container]));
     }
    }
     
         
     
$this->set('rpc.discover', $serviceId);
/**/
     
return $this;
  }
   
  public function
getMethodDefinitions(){
     return
$this->methods;
  }
   
  public function
getContainer():ContainerInterface{
     return
$this->container;
  }
   
  public function
getConfig(){
     return
$this->config;
  }
   
   
   
    public function
set(string $method, string $serviceId): Server
   
{
        if (!
$this->container->has($serviceId)) {
            throw new
LogicException("Cannot find service '$serviceId' in the container");
        }

       
$this->methods[$method] = $serviceId;

        return
$this;
    }

    public function
attach(string $serviceId): Server
   
{
        if (!
$this->container->has($serviceId)) {
            throw new
LogicException("Cannot find service '$serviceId' in the container");
        }

       
$this->middlewares[$serviceId] = null;

        return
$this;
    }

   
/**
     * @throws TypeError
     */
   
public function run(string $raw): ?string
   
{
   
       
       
$input = Input::fromString($raw, true);

        if (!
$input->parsable()) {
            return
self::end(Error::parsing());
        }

        if (
$input->isArray()) {
            if (
$this->tooManyBatchRequests($input)) {
                return
self::end(Error::tooManyBatchRequests($this->batchLimit));
            }

            return
$this->batch($input);
        }

        return
$this->single($input);
    }

    protected function
batch(Input $input): ?string
   
{
        \
assert($input->isArray());

       
$responses = [];
        foreach (
$input->data() as $request) {
           
$pseudoInput = Input::fromSafeData($request);

            if (
null !== $response = $this->single($pseudoInput)) {
               
$responses[] = $response;
            }
        }

        return empty(
$responses) ?
           
null : \sprintf('[%s]', \implode(',', $responses));
    }

   
/**
     * @throws TypeError
     */
   
protected function single(Input $input): ?string
   
{
        if (!
$input->isRpcRequest()) {
            return
self::end(Error::invalidRequest());
        }

       
$request = new Request($input);

        if (!\
array_key_exists($request->method(), $this->methods)) {
            return
self::end(Error::unknownMethod($request->id()), $request);
        }

        try {
           
$procedure = Assert::isProcedure(
               
$this->container->get($this->methods[$request->method()])
            );
        } catch (
ContainerExceptionInterface | NotFoundExceptionInterface $e) {
            return
self::end(Error::internal($request->id()), $request);
        }

       
       
        if (!
Validator::validate($procedure->getSpec(), $request->params())) {
            return
self::end(Error::invalidParams($request->id()), $request);
        }

       
$stack = MiddlewareStack::compose(
           
$procedure,
            ...\
array_map(function(string $serviceId) {
                return
$this->container->get($serviceId);
            }, \
array_keys($this->middlewares))
        );

       
       
        return
self::end($stack($request), $request, $procedure, $this);
    }

    protected function
tooManyBatchRequests(Input $input): bool
   
{
        \
assert($input->isArray());

        return \
is_int($this->batchLimit) && $this->batchLimit < \count($input->data());
    }

    protected static function
end(Response $response, Request $request = null, Procedure $procedure = null, Server $Server = null): ?string
   
{
        if(
$procedure && true !== $response instanceof Error && $procedure instanceof MethodDiscoverableInterface){
           
          
$spec = $procedure->getResultSpec();
               
           
$result = json_decode(json_encode($response));

           if (!
self::validateResponse($validation, $spec, $result->result, $Server)) {
              
$ea=$validation->getFirstError()->errorArgs();
              return
self::end(new Error($request->id(), 'Invalid result '.print_r($ea,true), $result->result), $request);
           }
        }
       
       
//$spec = $this->container->get($this->methods[$request->method()])->getResultSpec();
        //if (true !== $response instanceof Error && !Validator::validate( $this->container->get($this->methods[$request->method()])->getResultSpec(), $request->params())) {
         // return self::end(Error::invalidParams($request->id()), $request);
      // }
       
       
return $request instanceof Request && null === $request->id() ?
           
null : \json_encode($response);
    }
   
   
   
    public static function
validateResponse(&$validation = null, \stdClass $schema, $data, Server $Server = null): bool
   
{
       
       
        \
assert(false !== \json_encode($data));

       
       
        if(
null!==$Server){
         
$config =$Server->getConfig();
        }else{
           
$config = [
                       
'schemaLoaderPrefix' => 'https://json-schema.org',
                       
'schemaLoaderDirs' => [],
                        
'schemaCacheDir' =>sys_get_temp_dir() . \DIRECTORY_SEPARATOR . get_current_user(). \DIRECTORY_SEPARATOR . 'json-schema-store' . \DIRECTORY_SEPARATOR,
                ];
        }
   
       
$validation = (new OpisValidator)
            ->
setLoader(new SchemaLoader($config['schemaLoaderPrefix'],
                                    
$config['schemaLoaderDirs'],
                                    
$config['schemaCacheDir']))
       
            ->
dataValidation($data, $schema);
       
       
   
        return
$validation->isValid();
    }
   
}