xref: /dokuwiki/inc/Remote/ApiCall.php (revision d1f06eb4f0e4febc5434c97e319fce6d0253e533)
1<?php
2
3namespace dokuwiki\Remote;
4
5
6use dokuwiki\Remote\OpenApiDoc\DocBlockMethod;
7use InvalidArgumentException;
8use ReflectionException;
9use ReflectionFunction;
10use ReflectionMethod;
11use RuntimeException;
12
13class ApiCall
14{
15    /** @var callable The method to be called for this endpoint */
16    protected $method;
17
18    /** @var bool Whether this call can be called without authentication */
19    protected bool $isPublic = false;
20
21    /** @var string The category this call belongs to */
22    protected string $category;
23
24    /** @var DocBlockMethod The meta data of this call as parsed from its doc block */
25    protected $docs;
26
27    /**
28     * Make the given method available as an API call
29     *
30     * @param string|array $method Either [object,'method'] or 'function'
31     * @param string $category The category this call belongs to
32     */
33    public function __construct($method, $category = '')
34    {
35        if (!is_callable($method)) {
36            throw new InvalidArgumentException('Method is not callable');
37        }
38
39        $this->method = $method;
40        $this->category = $category;
41    }
42
43    /**
44     * Call the method
45     *
46     * Important: access/authentication checks need to be done before calling this!
47     *
48     * @param array $args
49     * @return mixed
50     */
51    public function __invoke($args)
52    {
53        if (!array_is_list($args)) {
54            $args = $this->namedArgsToPositional($args);
55        }
56        return call_user_func_array($this->method, $args);
57    }
58
59    /**
60     * Access the method documentation
61     *
62     * This lazy loads the docs only when needed
63     *
64     * @return DocBlockMethod
65     */
66    public function getDocs()
67    {
68        if ($this->docs === null) {
69            try {
70                if (is_array($this->method)) {
71                    $reflect = new ReflectionMethod($this->method[0], $this->method[1]);
72                } else {
73                    $reflect = new ReflectionFunction($this->method);
74                }
75                $this->docs = new DocBlockMethod($reflect);
76            } catch (ReflectionException $e) {
77                throw new RuntimeException('Failed to parse API method documentation', 0, $e);
78            }
79
80        }
81        return $this->docs;
82    }
83
84
85    /**
86     * @return bool
87     */
88    public function isPublic(): bool
89    {
90        return $this->isPublic;
91    }
92
93    /**
94     * @param bool $isPublic
95     * @return $this
96     */
97    public function setPublic(bool $isPublic = true): self
98    {
99        $this->isPublic = $isPublic;
100        return $this;
101    }
102
103
104    /**
105     * @return array
106     */
107    public function getArgs(): array
108    {
109        return $this->getDocs()->getParameters();
110    }
111
112    /**
113     * @return array
114     */
115    public function getReturn(): array
116    {
117        return $this->getDocs()->getReturn();
118    }
119
120    /**
121     * @return string
122     */
123    public function getSummary(): string
124    {
125        return $this->getDocs()->getSummary();
126    }
127
128    /**
129     * @return string
130     */
131    public function getDescription(): string
132    {
133        return $this->getDocs()->getDescription();
134    }
135
136    /**
137     * @return string
138     */
139    public function getCategory(): string
140    {
141        return $this->category;
142    }
143
144    /**
145     * Converts named arguments to positional arguments
146     *
147     * @fixme with PHP 8 we can use named arguments directly using the spread operator
148     * @param array $params
149     * @return array
150     */
151    protected function namedArgsToPositional($params)
152    {
153        $args = [];
154
155        foreach ($this->getDocs()->getParameters() as $arg => $arginfo) {
156            if (isset($params[$arg])) {
157                $args[] = $params[$arg];
158            } else {
159                if ($arginfo['optional'] && array_key_exists('default', $arginfo)) {
160                    $args[] = $arginfo['default'];
161                } else {
162                    throw new InvalidArgumentException("Missing argument $arg");
163                }
164            }
165        }
166
167        return $args;
168    }
169
170}
171