aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/inc/Remote/ApiCall.php
blob: fa05fe6c212248ab3f48510a016d9bd409dcf68c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<?php

namespace dokuwiki\Remote;

use dokuwiki\Remote\OpenApiDoc\DocBlockMethod;
use InvalidArgumentException;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use RuntimeException;

class ApiCall
{
    /** @var callable The method to be called for this endpoint */
    protected $method;

    /** @var bool Whether this call can be called without authentication */
    protected bool $isPublic = false;

    /** @var string The category this call belongs to */
    protected string $category;

    /** @var DocBlockMethod The meta data of this call as parsed from its doc block */
    protected $docs;

    /**
     * Make the given method available as an API call
     *
     * @param string|array $method Either [object,'method'] or 'function'
     * @param string $category The category this call belongs to
     */
    public function __construct($method, $category = '')
    {
        if (!is_callable($method)) {
            throw new InvalidArgumentException('Method is not callable');
        }

        $this->method = $method;
        $this->category = $category;
    }

    /**
     * Call the method
     *
     * Important: access/authentication checks need to be done before calling this!
     *
     * @param array $args
     * @return mixed
     */
    public function __invoke($args)
    {
        if (!array_is_list($args)) {
            $args = $this->namedArgsToPositional($args);
        }
        return call_user_func_array($this->method, $args);
    }

    /**
     * Access the method documentation
     *
     * This lazy loads the docs only when needed
     *
     * @return DocBlockMethod
     */
    public function getDocs()
    {
        if ($this->docs === null) {
            try {
                if (is_array($this->method)) {
                    $reflect = new ReflectionMethod($this->method[0], $this->method[1]);
                } else {
                    $reflect = new ReflectionFunction($this->method);
                }
                $this->docs = new DocBlockMethod($reflect);
            } catch (ReflectionException $e) {
                throw new RuntimeException('Failed to parse API method documentation', 0, $e);
            }
        }
        return $this->docs;
    }

    /**
     * Is this a public method?
     *
     * Public methods can be called without authentication
     *
     * @return bool
     */
    public function isPublic()
    {
        return $this->isPublic;
    }

    /**
     * Set the public flag
     *
     * @param bool $isPublic
     * @return $this
     */
    public function setPublic(bool $isPublic = true)
    {
        $this->isPublic = $isPublic;
        return $this;
    }

    /**
     * Get information about the argument of this call
     *
     * @return array
     */
    public function getArgs()
    {
        return $this->getDocs()->getParameters();
    }

    /**
     * Get information about the return value of this call
     *
     * @return array
     */
    public function getReturn()
    {
        return $this->getDocs()->getReturn();
    }

    /**
     * Get the summary of this call
     *
     * @return string
     */
    public function getSummary()
    {
        return $this->getDocs()->getSummary();
    }

    /**
     * Get the description of this call
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->getDocs()->getDescription();
    }

    /**
     * Get the category of this call
     *
     * @return string
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * Converts named arguments to positional arguments
     *
     * @fixme with PHP 8 we can use named arguments directly using the spread operator
     * @param array $params
     * @return array
     */
    protected function namedArgsToPositional($params)
    {
        $args = [];

        foreach ($this->getDocs()->getParameters() as $arg => $arginfo) {
            if (isset($params[$arg])) {
                $args[] = $params[$arg];
            } elseif ($arginfo['optional'] && array_key_exists('default', $arginfo)) {
                $args[] = $arginfo['default'];
            } else {
                throw new InvalidArgumentException("Missing argument $arg");
            }
        }

        return $args;
    }
}