Base.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <?php
  2. namespace Gogs\API\Request {
  3. /**
  4. * Base class for request types.
  5. *
  6. * Each request shall inherit this class to ensure
  7. * it will have the correct methods required by interface,
  8. * and get the cURL functionality.
  9. *
  10. * @author Joachim M. Giaever (joachim[]giaever.org)
  11. * @abstact
  12. */
  13. abstract class Base implements RequestInterface {
  14. private $tag;
  15. protected $loaded = false;
  16. protected $scope;
  17. use \Gogs\Lib\Curl\Client {
  18. get as private mget;
  19. post as private mpost;
  20. delete as private mdelete;
  21. }
  22. /**
  23. * @param string $api_url The URL to the API.
  24. * @param string $api_token A token for an authorized user
  25. */
  26. public function __construct(string $api_url, string $api_token) {
  27. $this->url = $api_url;
  28. $this->token = $api_token;
  29. $this->tag = strtolower(basename(str_replace("\\", "/", get_class($this))));
  30. }
  31. /**
  32. * Load an object.
  33. *
  34. * If `$force = true` the object will be fetched
  35. * from the Gogs API again.
  36. *
  37. * @deprecated since 2.0 to be removed in 3.0
  38. * @return object
  39. */
  40. final public function load(bool $force = false) {
  41. $this->set_scope("get");
  42. if ($this->loaded && !$force)
  43. return $this;
  44. $jenc = $this->mget($this->scope);
  45. $this->json_set_property($this->json_decode($jenc));
  46. /*
  47. * JSON set property should also do this, but
  48. * to ensure its done, we'll also do it here
  49. */
  50. $this->loaded = true;
  51. return $this;
  52. }
  53. /**
  54. * Perform a GET-request against the Gogs API.
  55. *
  56. * Ensure the correct scope i set first, with
  57. * ```php
  58. * $this->set_scope("*valid scope*"); // e.g create
  59. * ```
  60. *
  61. * @param array $params The parameters
  62. * @return string
  63. */
  64. final protected function method_get(array $params = array()) {
  65. return $this->mget($this->scope, $params);
  66. }
  67. /**
  68. * Perform a POST-request against the Gogs API.
  69. *
  70. * Ensure the correct scope i set first, with
  71. * ```php
  72. * $this->set_scope("*valid scope*"); // e.g create
  73. * ```
  74. *
  75. * @param array $params The parameters
  76. * @return string
  77. */
  78. final protected function method_post(array $params = array()) {
  79. return $this->mpost($this->scope, $params);
  80. }
  81. /**
  82. * Perform a DELETE-request against the Gogs API.
  83. *
  84. * Ensure the correct scope i set first, with
  85. * ```php
  86. * $this->set_scope("*valid scope*"); // e.g delete
  87. * ```
  88. *
  89. * @return string
  90. */
  91. final protected function method_delete() {
  92. return $this->mdelete($this->scope);
  93. }
  94. /**
  95. * Get object references by identifier.
  96. *
  97. * @param string $s Identifier to look up.
  98. * @return null
  99. */
  100. public function get(string $s) {
  101. if (!$this->set_scope("get"))
  102. throw new Exception\NotImplementedException("::get:: Not implemented for class '" . get_class($this) . "'");
  103. return null;
  104. }
  105. /**
  106. * Create object inherited by class.
  107. *
  108. * Child class must add a scope for 'create' and ensure child is not *loaded*,
  109. * otherwise will `create` throw an exception.
  110. *
  111. * @param string $args yeah, well
  112. * @return true
  113. * @throws Exception\InvalidMethodRequestException
  114. * @throws Exception\NotImplementedException
  115. */
  116. public function create(...$args) {
  117. if ($this->loaded)
  118. throw new Exception\InvalidMethodRequestException("::create:: Cant create on an git-initialized object. Create new object.");
  119. if (!$this->set_scope("create"))
  120. throw new Exception\NotImplementedException("::create:: Not implemented for class '" . get_class($this) . "'");
  121. $ret = $this->method_post(...$args);
  122. $this->json_set_property($this->json_decode($ret));
  123. return true;
  124. }
  125. /**
  126. * Patch (update) object
  127. *
  128. * @throws Exception\InvalidMethodRequestException
  129. * @throws Exception\NotImplementedException
  130. */
  131. public function patch() {
  132. if (!$this->loaded)
  133. throw new Exception\InvalidMethodRequestException("::patch:: Cant patch an git-uninitialized object. Load it first.");
  134. if (!$this->set_scope("patch"))
  135. throw new Exception\NotImplementedException("::patch:: Not implemented for class '" . get_class($this) . "'");
  136. }
  137. /**
  138. * Delete object.
  139. *
  140. * @throws Exception\NotImplementedException
  141. */
  142. public function delete() {
  143. if (!$this->set_scope("delete"))
  144. throw new Exception\NotImplementedException("::delete:: Not implemented for class '" . get_class($this) . "'");
  145. return $this->method_delete();
  146. }
  147. /**
  148. * Decode JSON-string.
  149. *
  150. * Will ensure that there weren't any errors by calling `$this->json_error`.
  151. *
  152. * @param string $jenc Encoded JSON string
  153. * @return object
  154. */
  155. final protected function json_decode(string $jenc) {
  156. $obj = json_decode($jenc);
  157. $this->json_error();
  158. return $obj;
  159. }
  160. /**
  161. * Encode JSON-object/array.
  162. *
  163. * Will ensure that there weren't any errors by calling `$this->json_error`.
  164. *
  165. * @param iterable $jdec JSON-data
  166. * @return string
  167. */
  168. final protected function json_encode(iterable $jdec) {
  169. $jenc = json_encode($jdec);
  170. $this->json_error();
  171. return $jenc;
  172. }
  173. /**
  174. * Check for errors on encoding/decoding.
  175. *
  176. * @throws Exception\RequestErrorException
  177. */
  178. final protected function json_error() {
  179. if (($err = json_last_error()) != JSON_ERROR_NONE)
  180. throw new Exception\RequestErrorException(json_last_error_msg(), $err);
  181. }
  182. /**
  183. * Get basename of a class (remove namespace).
  184. *
  185. * @param string $class The FQN
  186. * @return string
  187. */
  188. private function basename_class(string $class) {
  189. return strtolower(basename(str_replace("\\", "/", $class)));
  190. }
  191. /**
  192. * Return basename of parent class.
  193. *
  194. * @return string
  195. */
  196. private function get_parent() {
  197. $parent = get_parent_class($this);
  198. if ($parent != __CLASS__)
  199. return $this->basename_class($parent);
  200. return null;
  201. }
  202. /**
  203. * Get property key by name.
  204. *
  205. * Classes sets property from json directly, but they are
  206. * named within the class by `classname_propertyname`. This
  207. * method returns the key name.
  208. *
  209. * @param string $name Name of the key
  210. * @param bool $parent Get key in parent
  211. * @return string
  212. */
  213. final private function key(string $name, bool $parent = false) {
  214. $tag = sprintf("%s_", $this->tag);
  215. if (strpos($name, $tag) === 0) {
  216. if ($parent && !empty($ptag = $this->get_parent()))
  217. return sprintf("%s_%s", $ptag, substr($name, strlen($tag)));
  218. return $name . "?";
  219. }
  220. if ($parent && !empty($ptag = $this->get_parent()))
  221. return sprintf("%s_%s", $ptag, $name);
  222. return $tag . $name;
  223. }
  224. /**
  225. * Checks if the property (key) exists within self
  226. * or parent class.
  227. *
  228. * Returns the actual key if it does. A class key (aka property)
  229. * start with the tag `classname_` followed by property name,
  230. * reflecting the JSON-object, and can be reached by
  231. *
  232. * * `$class->parameter`,
  233. * * `$class->classname_parameter` or alternatively (for classes that inherits another class).
  234. * * `$class->parentclassname_parameter`.
  235. *
  236. * If a class override a parent class with the same parameter,
  237. * the class's own parameter will be favoured.
  238. *
  239. * As this is public properties this wont be an security issue;
  240. *
  241. * @param $name Name of the key.
  242. * @return string|false False on failure
  243. */
  244. final protected function property_exists($name) {
  245. if (property_exists($this, $key = $this->key($name)))
  246. return $key;
  247. if (property_exists($this, $key = $this->key($name, true)))
  248. return $key;
  249. return false;
  250. }
  251. /**
  252. * Get property by name.
  253. *
  254. * Checks both self and parent for the property.
  255. *
  256. * Returns the value if property exists, otherwise an `E_USER_NOTICE`
  257. * is triggered.
  258. *
  259. * @param string $name
  260. * @return mixed|null Null when unknown
  261. */
  262. final public function __get(string $name) {
  263. $key = $this->property_exists($name);
  264. if ($key)
  265. return $this->{$key};
  266. $trace = debug_backtrace();
  267. trigger_error(
  268. sprintf(
  269. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  270. $name,
  271. $this->key($name),
  272. $trace[0]["file"],
  273. $trace[0]["line"],
  274. $this->basename_class(get_parent_class($this))
  275. ),
  276. E_USER_NOTICE
  277. );
  278. return null;
  279. }
  280. /**
  281. * Set property by name.
  282. *
  283. * Checks both self and parent for the property.
  284. *
  285. * Returns the value if property exists, otherwise an `E_USER_NOTICE`
  286. * is triggered.
  287. *
  288. * @param string $name Property name
  289. * @param mixed $value Property value
  290. * @return mixed|null Null when unknown
  291. */
  292. final public function __set(string $name, $value) {
  293. $key = $this->property_exists($name);
  294. if ($key)
  295. return $this->{$key} = $value;
  296. $trace = debug_backtrace();
  297. trigger_error(
  298. sprintf(
  299. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  300. $name,
  301. $this->key($name),
  302. $trace[0]["file"],
  303. $trace[0]["line"],
  304. $this->basename_class(get_parent_class($this))
  305. ),
  306. E_USER_NOTICE
  307. );
  308. return null;
  309. }
  310. /**
  311. * Checks if property is set.
  312. *
  313. * Checks both self and parent for property.
  314. *
  315. * Triggers E_USER_NOTICE if property is unknown.
  316. *
  317. * @param string $name Property name
  318. * @return bool
  319. */
  320. final public function __isset(string $name) {
  321. $key = $this->property_exists($name);
  322. if ($key)
  323. return isset($this->{$key});
  324. $trace = debug_backtrace();
  325. trigger_error(
  326. sprintf(
  327. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  328. $name,
  329. $this->key($name),
  330. $trace[0]["file"],
  331. $trace[0]["line"],
  332. $this->basename_class(get_parent_class($this))
  333. ),
  334. E_USER_NOTICE
  335. );
  336. return false;
  337. }
  338. /**
  339. * Set properties for the current object.
  340. *
  341. * Each child class must implement this to set its data. Will
  342. * be called by methods such as `load` and from collection
  343. * classes.
  344. *
  345. * Will return true/false for singel objects but an array on collections.
  346. * The array will contain the newly inserted elements. This to prevent
  347. * additional iterations.
  348. *
  349. * This method should also set loaded to true or false, depending
  350. * on success or failure.
  351. *
  352. * @see Collection
  353. * @param mixed $obj
  354. * @return true|array
  355. */
  356. abstract protected function json_set_property($obj);
  357. /**
  358. * Set the scope for the request methods accepted by the child.
  359. *
  360. * This can be
  361. * * `get`,
  362. * * `search`,
  363. * * `delete` etc.
  364. *
  365. * Must return true if scope exists of false otherwise. Methods
  366. * the calls this will throw an exception if not true is returned.
  367. *
  368. * @param string $method Method type, e.g "get"
  369. * @return bool
  370. */
  371. abstract protected function set_scope(string $method);
  372. }
  373. }
  374. ?>