Base.php 15 KB

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