xref: /dokuwiki/inc/Remote/ApiCall.php (revision 7de5ac5588c9296c42d9e9b42e57467e6a8b816f)
142e66c7aSAndreas Gohr<?php
242e66c7aSAndreas Gohr
342e66c7aSAndreas Gohrnamespace dokuwiki\Remote;
442e66c7aSAndreas Gohr
542e66c7aSAndreas Gohr
6dd7472d3SAndreas Gohruse dokuwiki\Remote\OpenApiDoc\DocBlockMethod;
76cce3332SAndreas Gohruse InvalidArgumentException;
86cce3332SAndreas Gohruse ReflectionException;
96cce3332SAndreas Gohruse ReflectionFunction;
106cce3332SAndreas Gohruse ReflectionMethod;
116cce3332SAndreas Gohruse RuntimeException;
12dd7472d3SAndreas Gohr
1342e66c7aSAndreas Gohrclass ApiCall
1442e66c7aSAndreas Gohr{
1542e66c7aSAndreas Gohr    /** @var callable The method to be called for this endpoint */
1642e66c7aSAndreas Gohr    protected $method;
1742e66c7aSAndreas Gohr
1842e66c7aSAndreas Gohr    /** @var bool Whether this call can be called without authentication */
1942e66c7aSAndreas Gohr    protected bool $isPublic = false;
2042e66c7aSAndreas Gohr
216cce3332SAndreas Gohr    /** @var string The category this call belongs to */
226cce3332SAndreas Gohr    protected string $category;
236cce3332SAndreas Gohr
24dd7472d3SAndreas Gohr    /** @var DocBlockMethod The meta data of this call as parsed from its doc block */
25dd7472d3SAndreas Gohr    protected $docs;
2666f07661SAndreas Gohr
2742e66c7aSAndreas Gohr    /**
2842e66c7aSAndreas Gohr     * Make the given method available as an API call
2942e66c7aSAndreas Gohr     *
3042e66c7aSAndreas Gohr     * @param string|array $method Either [object,'method'] or 'function'
316cce3332SAndreas Gohr     * @param string $category The category this call belongs to
3242e66c7aSAndreas Gohr     */
336cce3332SAndreas Gohr    public function __construct($method, $category = '')
3442e66c7aSAndreas Gohr    {
3542e66c7aSAndreas Gohr        if (!is_callable($method)) {
366cce3332SAndreas Gohr            throw new InvalidArgumentException('Method is not callable');
3742e66c7aSAndreas Gohr        }
3842e66c7aSAndreas Gohr
3942e66c7aSAndreas Gohr        $this->method = $method;
406cce3332SAndreas Gohr        $this->category = $category;
4142e66c7aSAndreas Gohr    }
4242e66c7aSAndreas Gohr
4342e66c7aSAndreas Gohr    /**
4442e66c7aSAndreas Gohr     * Call the method
4542e66c7aSAndreas Gohr     *
4642e66c7aSAndreas Gohr     * Important: access/authentication checks need to be done before calling this!
4742e66c7aSAndreas Gohr     *
4842e66c7aSAndreas Gohr     * @param array $args
4942e66c7aSAndreas Gohr     * @return mixed
5042e66c7aSAndreas Gohr     */
5142e66c7aSAndreas Gohr    public function __invoke($args)
5242e66c7aSAndreas Gohr    {
5342e66c7aSAndreas Gohr        if (!array_is_list($args)) {
5442e66c7aSAndreas Gohr            $args = $this->namedArgsToPositional($args);
5542e66c7aSAndreas Gohr        }
5642e66c7aSAndreas Gohr        return call_user_func_array($this->method, $args);
5742e66c7aSAndreas Gohr    }
5842e66c7aSAndreas Gohr
5942e66c7aSAndreas Gohr    /**
60dd7472d3SAndreas Gohr     * Access the method documentation
61dd7472d3SAndreas Gohr     *
62dd7472d3SAndreas Gohr     * This lazy loads the docs only when needed
63dd7472d3SAndreas Gohr     *
64dd7472d3SAndreas Gohr     * @return DocBlockMethod
65dd7472d3SAndreas Gohr     */
66dd7472d3SAndreas Gohr    public function getDocs()
67dd7472d3SAndreas Gohr    {
68dd7472d3SAndreas Gohr        if ($this->docs === null) {
69dd7472d3SAndreas Gohr            try {
70dd7472d3SAndreas Gohr                if (is_array($this->method)) {
716cce3332SAndreas Gohr                    $reflect = new ReflectionMethod($this->method[0], $this->method[1]);
72dd7472d3SAndreas Gohr                } else {
736cce3332SAndreas Gohr                    $reflect = new ReflectionFunction($this->method);
74dd7472d3SAndreas Gohr                }
75dd7472d3SAndreas Gohr                $this->docs = new DocBlockMethod($reflect);
766cce3332SAndreas Gohr            } catch (ReflectionException $e) {
776cce3332SAndreas Gohr                throw new RuntimeException('Failed to parse API method documentation', 0, $e);
78dd7472d3SAndreas Gohr            }
79dd7472d3SAndreas Gohr
80dd7472d3SAndreas Gohr        }
81dd7472d3SAndreas Gohr        return $this->docs;
82dd7472d3SAndreas Gohr    }
83dd7472d3SAndreas Gohr
84dd7472d3SAndreas Gohr
85dd7472d3SAndreas Gohr    /**
8642e66c7aSAndreas Gohr     * @return bool
8742e66c7aSAndreas Gohr     */
8842e66c7aSAndreas Gohr    public function isPublic(): bool
8942e66c7aSAndreas Gohr    {
9042e66c7aSAndreas Gohr        return $this->isPublic;
9142e66c7aSAndreas Gohr    }
9242e66c7aSAndreas Gohr
9342e66c7aSAndreas Gohr    /**
9442e66c7aSAndreas Gohr     * @param bool $isPublic
9542e66c7aSAndreas Gohr     * @return $this
9642e66c7aSAndreas Gohr     */
9742e66c7aSAndreas Gohr    public function setPublic(bool $isPublic = true): self
9842e66c7aSAndreas Gohr    {
9942e66c7aSAndreas Gohr        $this->isPublic = $isPublic;
10042e66c7aSAndreas Gohr        return $this;
10142e66c7aSAndreas Gohr    }
10242e66c7aSAndreas Gohr
10342e66c7aSAndreas Gohr
10442e66c7aSAndreas Gohr    /**
10542e66c7aSAndreas Gohr     * @return array
10642e66c7aSAndreas Gohr     */
10742e66c7aSAndreas Gohr    public function getArgs(): array
10842e66c7aSAndreas Gohr    {
109dd7472d3SAndreas Gohr        return $this->getDocs()->getParameters();
11042e66c7aSAndreas Gohr    }
11142e66c7aSAndreas Gohr
11242e66c7aSAndreas Gohr    /**
11342e66c7aSAndreas Gohr     * @return array
11442e66c7aSAndreas Gohr     */
11542e66c7aSAndreas Gohr    public function getReturn(): array
11642e66c7aSAndreas Gohr    {
117dd7472d3SAndreas Gohr        return $this->getDocs()->getReturn();
11842e66c7aSAndreas Gohr    }
11942e66c7aSAndreas Gohr
12042e66c7aSAndreas Gohr    /**
12142e66c7aSAndreas Gohr     * @return string
12242e66c7aSAndreas Gohr     */
12342e66c7aSAndreas Gohr    public function getSummary(): string
12442e66c7aSAndreas Gohr    {
125dd7472d3SAndreas Gohr        return $this->getDocs()->getSummary();
12642e66c7aSAndreas Gohr    }
12742e66c7aSAndreas Gohr
12842e66c7aSAndreas Gohr    /**
12942e66c7aSAndreas Gohr     * @return string
13042e66c7aSAndreas Gohr     */
13142e66c7aSAndreas Gohr    public function getDescription(): string
13242e66c7aSAndreas Gohr    {
133dd7472d3SAndreas Gohr        return $this->getDocs()->getDescription();
13442e66c7aSAndreas Gohr    }
13542e66c7aSAndreas Gohr
13642e66c7aSAndreas Gohr    /**
1376cce3332SAndreas Gohr     * @return string
1386cce3332SAndreas Gohr     */
1396cce3332SAndreas Gohr    public function getCategory(): string
1406cce3332SAndreas Gohr    {
1416cce3332SAndreas Gohr        return $this->category;
1426cce3332SAndreas Gohr    }
1436cce3332SAndreas Gohr
1446cce3332SAndreas Gohr    /**
14542e66c7aSAndreas Gohr     * Converts named arguments to positional arguments
14642e66c7aSAndreas Gohr     *
14742e66c7aSAndreas Gohr     * @fixme with PHP 8 we can use named arguments directly using the spread operator
14842e66c7aSAndreas Gohr     * @param array $params
14942e66c7aSAndreas Gohr     * @return array
15042e66c7aSAndreas Gohr     */
15142e66c7aSAndreas Gohr    protected function namedArgsToPositional($params)
15242e66c7aSAndreas Gohr    {
15342e66c7aSAndreas Gohr        $args = [];
15442e66c7aSAndreas Gohr
155*7de5ac55SAndreas Gohr        foreach ($this->getDocs()->getParameters() as $arg => $arginfo) {
15642e66c7aSAndreas Gohr            if (isset($params[$arg])) {
15742e66c7aSAndreas Gohr                $args[] = $params[$arg];
15842e66c7aSAndreas Gohr            } else {
159*7de5ac55SAndreas Gohr                if ($arginfo['optional'] && array_key_exists('default', $arginfo)) {
160*7de5ac55SAndreas Gohr                    $args[] = $arginfo['default'];
161*7de5ac55SAndreas Gohr                } else {
162*7de5ac55SAndreas Gohr                    throw new InvalidArgumentException("Missing argument $arg");
163*7de5ac55SAndreas Gohr                }
16442e66c7aSAndreas Gohr            }
16542e66c7aSAndreas Gohr        }
16642e66c7aSAndreas Gohr
16742e66c7aSAndreas Gohr        return $args;
16842e66c7aSAndreas Gohr    }
16942e66c7aSAndreas Gohr
17042e66c7aSAndreas Gohr}
171