Base.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. * @version 0.1.4
  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. * @throws Exception\NotImplementedException when method doesnt support load
  38. * @return object
  39. */
  40. final public function load(bool $force = false) {
  41. if (!$this->set_scope("load"))
  42. throw new Exception\NotImplementedException("::load:: Not implemented for class '" . get_class($this) . "'");
  43. if ($this->loaded && !$force)
  44. return $this;
  45. $jenc = $this->mget($this->scope);
  46. $this->json_set_property($this->json_decode($jenc));
  47. /*
  48. * JSON set property should also do this, but
  49. * to ensure its done, we'll also do it here
  50. */
  51. $this->loaded = true;
  52. return $this;
  53. }
  54. /**
  55. * Perform a GET-request against the Gogs API.
  56. *
  57. * Ensure the correct scope i set first, with
  58. * ```php
  59. * $this->set_scope("*valid scope*"); // e.g create
  60. * ```
  61. *
  62. * @param array $params The parameters
  63. * @return string
  64. */
  65. final protected function method_get(array $params = array()) {
  66. return $this->mget($this->scope, $params);
  67. }
  68. /**
  69. * Perform a POST-request against the Gogs API.
  70. *
  71. * Ensure the correct scope i set first, with
  72. * ```php
  73. * $this->set_scope("*valid scope*"); // e.g create
  74. * ```
  75. *
  76. * @param array $params The parameters
  77. * @return string
  78. */
  79. final protected function method_post(array $params = array()) {
  80. return $this->mpost($this->scope, $params);
  81. }
  82. /**
  83. * Perform a DELETE-request against the Gogs API.
  84. *
  85. * Ensure the correct scope i set first, with
  86. * ```php
  87. * $this->set_scope("*valid scope*"); // e.g delete
  88. * ```
  89. *
  90. * @return string
  91. */
  92. final protected function method_delete() {
  93. return $this->mdelete($this->scope);
  94. }
  95. /**
  96. * Get object references by identifier.
  97. *
  98. * @param string $s Identifier to look up.
  99. * @return null
  100. */
  101. public function get(string $s) {
  102. //if (!$this->set_scope("get"))
  103. throw new Exception\NotImplementedException("::get:: Not implemented for class '" . get_class($this) . "'");
  104. return null;
  105. }
  106. /**
  107. * Create object inherited by class.
  108. *
  109. * Child class must add a scope for 'create' and ensure child is not *loaded*,
  110. * otherwise will `create` throw an exception.
  111. *
  112. * @param string $args yeah, well
  113. * @return true
  114. * @throws Exception\InvalidMethodRequestException
  115. * @throws Exception\NotImplementedException
  116. */
  117. public function create(...$args) {
  118. if ($this->loaded)
  119. throw new Exception\InvalidMethodRequestException("::create:: Cant create on an git-initialized object. Create new object.");
  120. if (!$this->set_scope("create"))
  121. throw new Exception\NotImplementedException("::create:: Not implemented for class '" . get_class($this) . "'");
  122. $ret = $this->method_post(...$args);
  123. $this->json_set_property((object)$this->json_decode($ret));
  124. return true;
  125. }
  126. /**
  127. * Patch (update) object
  128. *
  129. * @throws Exception\InvalidMethodRequestException
  130. * @throws Exception\NotImplementedException
  131. */
  132. public function patch() {
  133. if (!$this->loaded)
  134. throw new Exception\InvalidMethodRequestException("::patch:: Cant patch an git-uninitialized object. Load it first.");
  135. if (!$this->set_scope("patch"))
  136. throw new Exception\NotImplementedException("::patch:: Not implemented for class '" . get_class($this) . "'");
  137. }
  138. /**
  139. * Delete object.
  140. *
  141. * @throws Exception\NotImplementedException
  142. */
  143. public function delete() {
  144. if (!$this->set_scope("delete"))
  145. throw new Exception\NotImplementedException("::delete:: Not implemented for class '" . get_class($this) . "'");
  146. return $this->method_delete();
  147. }
  148. /**
  149. * Decode JSON-string.
  150. *
  151. * Will ensure that there weren't any errors by calling `$this->json_error`.
  152. *
  153. * @param string $jenc Encoded JSON string
  154. * @return object
  155. */
  156. final protected function json_decode(string $jenc) {
  157. $obj = json_decode($jenc);
  158. $this->json_error();
  159. return (object)$obj;
  160. }
  161. /**
  162. * Encode JSON-object/array.
  163. *
  164. * Will ensure that there weren't any errors by calling `$this->json_error`.
  165. *
  166. * @param iterable $jdec JSON-data
  167. * @return string
  168. */
  169. final protected function json_encode(iterable $jdec) {
  170. $jenc = json_encode($jdec);
  171. $this->json_error();
  172. return $jenc;
  173. }
  174. /**
  175. * Check for errors on encoding/decoding.
  176. *
  177. * @throws Exception\RequestErrorException
  178. */
  179. final protected function json_error() {
  180. if (($err = json_last_error()) != JSON_ERROR_NONE)
  181. throw new Exception\RequestErrorException(json_last_error_msg(), $err);
  182. }
  183. /**
  184. * Set properties for the current object.
  185. *
  186. * Each child class must implement this to set its data. Will
  187. * be called by methods such as `load` and from collection
  188. * classes.
  189. *
  190. * Will return true/false for singel objects but an array on collections.
  191. * The array will contain the newly inserted elements. This to prevent
  192. * additional iterations.
  193. *
  194. * This method should also set loaded to true or false, depending
  195. * on success or failure.
  196. *
  197. * @see Collection
  198. * @param mixed $obj
  199. * @return true|array Array with keys on collections
  200. */
  201. protected function json_set_property(\stdClass $obj) {
  202. foreach ($obj as $key => $value) {
  203. if ($this->property_exists($key))
  204. $this->{$key} = $value;
  205. else
  206. echo "Unknown proerty " . $key . "\n";
  207. }
  208. $this->loaded = true;
  209. return true;
  210. }
  211. /**
  212. * Get basename of a class (remove namespace).
  213. *
  214. * @param string $class The FQN
  215. * @return string
  216. */
  217. private function basename_class(string $class) {
  218. return strtolower(basename(str_replace("\\", "/", $class)));
  219. }
  220. /**
  221. * Return basename of parent class.
  222. *
  223. * @return string
  224. */
  225. private function get_parent() {
  226. $parent = get_parent_class($this);
  227. if ($parent != __CLASS__)
  228. return $this->basename_class($parent);
  229. return null;
  230. }
  231. /**
  232. * Get property key by name.
  233. *
  234. * Classes sets property from json directly, but they are
  235. * named within the class by `classname_propertyname`. This
  236. * method returns the key name.
  237. *
  238. * @param string $name Name of the key
  239. * @param bool $parent Get key in parent
  240. * @return string
  241. */
  242. final private function key(string $name, bool $parent = false) {
  243. $tag = sprintf("%s_", $this->tag);
  244. if (strpos($name, $tag) === 0) {
  245. if ($parent && !empty($ptag = $this->get_parent()))
  246. return sprintf("%s_%s", $ptag, substr($name, strlen($tag)));
  247. return $name . "?";
  248. }
  249. if ($parent && !empty($ptag = $this->get_parent()))
  250. return sprintf("%s_%s", $ptag, $name);
  251. return $tag . $name;
  252. }
  253. /**
  254. * Checks if the property (key) exists within self
  255. * or parent class.
  256. *
  257. * Returns the actual key if it does. A class key (aka property)
  258. * start with the tag `classname_` followed by property name,
  259. * reflecting the JSON-object, and can be reached by
  260. *
  261. * * `$class->parameter`,
  262. * * `$class->classname_parameter` or alternatively (for classes that inherits another class).
  263. * * `$class->parentclassname_parameter`.
  264. *
  265. * If a class override a parent class with the same parameter,
  266. * the class's own parameter will be favoured.
  267. *
  268. * As this is public properties this wont be an security issue;
  269. *
  270. * @param $name Name of the key.
  271. * @return string|false False on failure
  272. */
  273. final protected function property_exists($name) {
  274. if (property_exists($this, $key = $this->key($name)))
  275. return $key;
  276. if (property_exists($this, $key = $this->key($name, true)))
  277. return $key;
  278. return false;
  279. }
  280. /**
  281. * Get 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
  289. * @return mixed|null Null when unknown
  290. */
  291. final public function __get(string $name) {
  292. $key = $this->property_exists($name);
  293. if ($key)
  294. return $this->{$key};
  295. $trace = debug_backtrace();
  296. trigger_error(
  297. sprintf(
  298. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  299. $name,
  300. $this->key($name),
  301. $trace[0]["file"],
  302. $trace[0]["line"],
  303. $this->basename_class(get_parent_class($this))
  304. ),
  305. E_USER_NOTICE
  306. );
  307. return null;
  308. }
  309. /**
  310. * Set property by name.
  311. *
  312. * Checks both self and parent for the property.
  313. *
  314. * Returns the value if property exists, otherwise an `E_USER_NOTICE`
  315. * is triggered.
  316. *
  317. * @param string $name Property name
  318. * @param mixed $value Property value
  319. * @return mixed|null Null when unknown
  320. */
  321. final public function __set(string $name, $value) {
  322. $key = $this->property_exists($name);
  323. if ($key)
  324. return $this->{$key} = $value;
  325. $trace = debug_backtrace();
  326. trigger_error(
  327. sprintf(
  328. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  329. $name,
  330. $this->key($name),
  331. $trace[0]["file"],
  332. $trace[0]["line"],
  333. $this->basename_class(get_parent_class($this))
  334. ),
  335. E_USER_NOTICE
  336. );
  337. return null;
  338. }
  339. /**
  340. * Checks if property is set.
  341. *
  342. * Checks both self and parent for property.
  343. *
  344. * Triggers E_USER_NOTICE if property is unknown.
  345. *
  346. * @param string $name Property name
  347. * @return bool
  348. */
  349. final public function __isset(string $name) {
  350. $key = $this->property_exists($name);
  351. if ($key)
  352. return isset($this->{$key});
  353. $trace = debug_backtrace();
  354. trigger_error(
  355. sprintf(
  356. "Undefined property '%s' {%s} in '%s' on line %s. Neither does its parent '%s'",
  357. $name,
  358. $this->key($name),
  359. $trace[0]["file"],
  360. $trace[0]["line"],
  361. $this->basename_class(get_parent_class($this))
  362. ),
  363. E_USER_NOTICE
  364. );
  365. return false;
  366. }
  367. /**
  368. * Set the scope for the request methods accepted by the child.
  369. *
  370. * This can be
  371. * * `get`,
  372. * * `search`,
  373. * * `delete` etc.
  374. *
  375. * Must return true if scope exists of false otherwise. Methods
  376. * the calls this will throw an exception if not true is returned.
  377. *
  378. * @param string $method Method type, e.g "get"
  379. * @return bool
  380. */
  381. abstract protected function set_scope(string $method);
  382. /**
  383. * Search for an matching object.
  384. *
  385. * Methods do OR-ing and not AND-ing by default.
  386. *
  387. * Params should be key (object property) and value that
  388. * this parameter should match, e.g
  389. *
  390. * ```
  391. * $repo->search(
  392. * "name" => "this",
  393. * "owner" => array(
  394. * "username" => "that"
  395. * )
  396. * );
  397. * ```
  398. *
  399. * will match `"this" IN $repo->name OR "that" IN $repo->owner->username` .
  400. *
  401. * @param array $params Parameters
  402. * @param bool $strict Turn search into AND-ing, require match in each field.
  403. * @throws Exception\SearchParamException when invalid property
  404. * @return true
  405. */
  406. protected function search(array $params = array(), bool $strict = false) {
  407. if (empty($params))
  408. return false;
  409. foreach ($params as $key => $value) {
  410. if (!$this->property_exists($key))
  411. throw new Exception\SearchParamException("Invalid property exception");
  412. if (is_array($value) && !$strict && $this->{$key}->search($value, $strict))
  413. return true;
  414. else if (is_array($value) && $strict && $this->{$key}->search($value, $strict))
  415. return false;
  416. else if (!$strict && stripos($this->{$key}, $value) !== false)
  417. return true;
  418. else if ($strict && stripos($this->{$key},$value) === false)
  419. return false;
  420. }
  421. return (!$strict ? false : true);
  422. }
  423. }
  424. }
  425. ?>