142e66c7aSAndreas Gohr<?php 242e66c7aSAndreas Gohr 342e66c7aSAndreas Gohrnamespace dokuwiki\Remote; 442e66c7aSAndreas Gohr 5dd7472d3SAndreas Gohruse dokuwiki\Remote\OpenApiDoc\DocBlockMethod; 66cce3332SAndreas Gohruse InvalidArgumentException; 76cce3332SAndreas Gohruse ReflectionException; 86cce3332SAndreas Gohruse ReflectionFunction; 96cce3332SAndreas Gohruse ReflectionMethod; 106cce3332SAndreas Gohruse RuntimeException; 11dd7472d3SAndreas Gohr 1242e66c7aSAndreas Gohrclass ApiCall 1342e66c7aSAndreas Gohr{ 1442e66c7aSAndreas Gohr /** @var callable The method to be called for this endpoint */ 1542e66c7aSAndreas Gohr protected $method; 1642e66c7aSAndreas Gohr 1742e66c7aSAndreas Gohr /** @var bool Whether this call can be called without authentication */ 1842e66c7aSAndreas Gohr protected bool $isPublic = false; 1942e66c7aSAndreas Gohr 206cce3332SAndreas Gohr /** @var string The category this call belongs to */ 216cce3332SAndreas Gohr protected string $category; 226cce3332SAndreas Gohr 23dd7472d3SAndreas Gohr /** @var DocBlockMethod The meta data of this call as parsed from its doc block */ 24dd7472d3SAndreas Gohr protected $docs; 2566f07661SAndreas Gohr 2642e66c7aSAndreas Gohr /** 2742e66c7aSAndreas Gohr * Make the given method available as an API call 2842e66c7aSAndreas Gohr * 2942e66c7aSAndreas Gohr * @param string|array $method Either [object,'method'] or 'function' 306cce3332SAndreas Gohr * @param string $category The category this call belongs to 3142e66c7aSAndreas Gohr */ 326cce3332SAndreas Gohr public function __construct($method, $category = '') 3342e66c7aSAndreas Gohr { 3442e66c7aSAndreas Gohr if (!is_callable($method)) { 356cce3332SAndreas Gohr throw new InvalidArgumentException('Method is not callable'); 3642e66c7aSAndreas Gohr } 3742e66c7aSAndreas Gohr 3842e66c7aSAndreas Gohr $this->method = $method; 396cce3332SAndreas Gohr $this->category = $category; 4042e66c7aSAndreas Gohr } 4142e66c7aSAndreas Gohr 4242e66c7aSAndreas Gohr /** 4342e66c7aSAndreas Gohr * Call the method 4442e66c7aSAndreas Gohr * 4542e66c7aSAndreas Gohr * Important: access/authentication checks need to be done before calling this! 4642e66c7aSAndreas Gohr * 4742e66c7aSAndreas Gohr * @param array $args 4842e66c7aSAndreas Gohr * @return mixed 4942e66c7aSAndreas Gohr */ 5042e66c7aSAndreas Gohr public function __invoke($args) 5142e66c7aSAndreas Gohr { 5242e66c7aSAndreas Gohr if (!array_is_list($args)) { 5342e66c7aSAndreas Gohr $args = $this->namedArgsToPositional($args); 5442e66c7aSAndreas Gohr } 5542e66c7aSAndreas Gohr return call_user_func_array($this->method, $args); 5642e66c7aSAndreas Gohr } 5742e66c7aSAndreas Gohr 5842e66c7aSAndreas Gohr /** 59dd7472d3SAndreas Gohr * Access the method documentation 60dd7472d3SAndreas Gohr * 61dd7472d3SAndreas Gohr * This lazy loads the docs only when needed 62dd7472d3SAndreas Gohr * 63dd7472d3SAndreas Gohr * @return DocBlockMethod 64dd7472d3SAndreas Gohr */ 65dd7472d3SAndreas Gohr public function getDocs() 66dd7472d3SAndreas Gohr { 67dd7472d3SAndreas Gohr if ($this->docs === null) { 68dd7472d3SAndreas Gohr try { 69dd7472d3SAndreas Gohr if (is_array($this->method)) { 706cce3332SAndreas Gohr $reflect = new ReflectionMethod($this->method[0], $this->method[1]); 71dd7472d3SAndreas Gohr } else { 726cce3332SAndreas Gohr $reflect = new ReflectionFunction($this->method); 73dd7472d3SAndreas Gohr } 74dd7472d3SAndreas Gohr $this->docs = new DocBlockMethod($reflect); 756cce3332SAndreas Gohr } catch (ReflectionException $e) { 766cce3332SAndreas Gohr throw new RuntimeException('Failed to parse API method documentation', 0, $e); 77dd7472d3SAndreas Gohr } 78dd7472d3SAndreas Gohr } 79dd7472d3SAndreas Gohr return $this->docs; 80dd7472d3SAndreas Gohr } 81dd7472d3SAndreas Gohr 82dd7472d3SAndreas Gohr /** 83*d48c2b25SAndreas Gohr * Is this a public method? 84*d48c2b25SAndreas Gohr * 85*d48c2b25SAndreas Gohr * Public methods can be called without authentication 86*d48c2b25SAndreas Gohr * 8742e66c7aSAndreas Gohr * @return bool 8842e66c7aSAndreas Gohr */ 89*d48c2b25SAndreas Gohr public function isPublic() 9042e66c7aSAndreas Gohr { 9142e66c7aSAndreas Gohr return $this->isPublic; 9242e66c7aSAndreas Gohr } 9342e66c7aSAndreas Gohr 9442e66c7aSAndreas Gohr /** 95*d48c2b25SAndreas Gohr * Set the public flag 96*d48c2b25SAndreas Gohr * 9742e66c7aSAndreas Gohr * @param bool $isPublic 9842e66c7aSAndreas Gohr * @return $this 9942e66c7aSAndreas Gohr */ 100*d48c2b25SAndreas Gohr public function setPublic(bool $isPublic = true) 10142e66c7aSAndreas Gohr { 10242e66c7aSAndreas Gohr $this->isPublic = $isPublic; 10342e66c7aSAndreas Gohr return $this; 10442e66c7aSAndreas Gohr } 10542e66c7aSAndreas Gohr 10642e66c7aSAndreas Gohr /** 107*d48c2b25SAndreas Gohr * Get information about the argument of this call 108*d48c2b25SAndreas Gohr * 10942e66c7aSAndreas Gohr * @return array 11042e66c7aSAndreas Gohr */ 111*d48c2b25SAndreas Gohr public function getArgs() 11242e66c7aSAndreas Gohr { 113dd7472d3SAndreas Gohr return $this->getDocs()->getParameters(); 11442e66c7aSAndreas Gohr } 11542e66c7aSAndreas Gohr 11642e66c7aSAndreas Gohr /** 117*d48c2b25SAndreas Gohr * Get information about the return value of this call 118*d48c2b25SAndreas Gohr * 11942e66c7aSAndreas Gohr * @return array 12042e66c7aSAndreas Gohr */ 121*d48c2b25SAndreas Gohr public function getReturn() 12242e66c7aSAndreas Gohr { 123dd7472d3SAndreas Gohr return $this->getDocs()->getReturn(); 12442e66c7aSAndreas Gohr } 12542e66c7aSAndreas Gohr 12642e66c7aSAndreas Gohr /** 127*d48c2b25SAndreas Gohr * Get the summary of this call 128*d48c2b25SAndreas Gohr * 12942e66c7aSAndreas Gohr * @return string 13042e66c7aSAndreas Gohr */ 131*d48c2b25SAndreas Gohr public function getSummary() 13242e66c7aSAndreas Gohr { 133dd7472d3SAndreas Gohr return $this->getDocs()->getSummary(); 13442e66c7aSAndreas Gohr } 13542e66c7aSAndreas Gohr 13642e66c7aSAndreas Gohr /** 137*d48c2b25SAndreas Gohr * Get the description of this call 138*d48c2b25SAndreas Gohr * 13942e66c7aSAndreas Gohr * @return string 14042e66c7aSAndreas Gohr */ 141*d48c2b25SAndreas Gohr public function getDescription() 14242e66c7aSAndreas Gohr { 143dd7472d3SAndreas Gohr return $this->getDocs()->getDescription(); 14442e66c7aSAndreas Gohr } 14542e66c7aSAndreas Gohr 14642e66c7aSAndreas Gohr /** 147*d48c2b25SAndreas Gohr * Get the category of this call 148*d48c2b25SAndreas Gohr * 1496cce3332SAndreas Gohr * @return string 1506cce3332SAndreas Gohr */ 151*d48c2b25SAndreas Gohr public function getCategory() 1526cce3332SAndreas Gohr { 1536cce3332SAndreas Gohr return $this->category; 1546cce3332SAndreas Gohr } 1556cce3332SAndreas Gohr 1566cce3332SAndreas Gohr /** 15742e66c7aSAndreas Gohr * Converts named arguments to positional arguments 15842e66c7aSAndreas Gohr * 15942e66c7aSAndreas Gohr * @fixme with PHP 8 we can use named arguments directly using the spread operator 16042e66c7aSAndreas Gohr * @param array $params 16142e66c7aSAndreas Gohr * @return array 16242e66c7aSAndreas Gohr */ 16342e66c7aSAndreas Gohr protected function namedArgsToPositional($params) 16442e66c7aSAndreas Gohr { 16542e66c7aSAndreas Gohr $args = []; 16642e66c7aSAndreas Gohr 1677de5ac55SAndreas Gohr foreach ($this->getDocs()->getParameters() as $arg => $arginfo) { 16842e66c7aSAndreas Gohr if (isset($params[$arg])) { 16942e66c7aSAndreas Gohr $args[] = $params[$arg]; 170*d48c2b25SAndreas Gohr } elseif ($arginfo['optional'] && array_key_exists('default', $arginfo)) { 1717de5ac55SAndreas Gohr $args[] = $arginfo['default']; 1727de5ac55SAndreas Gohr } else { 1737de5ac55SAndreas Gohr throw new InvalidArgumentException("Missing argument $arg"); 1747de5ac55SAndreas Gohr } 17542e66c7aSAndreas Gohr } 17642e66c7aSAndreas Gohr 17742e66c7aSAndreas Gohr return $args; 17842e66c7aSAndreas Gohr } 17942e66c7aSAndreas Gohr} 180