Browse Source

Fetch had been working.

Cixo Develop 4 tháng trước cách đây
mục cha
commit
c08bf6283c
3 tập tin đã thay đổi với 304 bổ sung46 xóa
  1. 154 39
      sources/fetch.php
  2. 127 7
      sources/response.php
  3. 23 0
      tests/fetch.php

+ 154 - 39
sources/fetch.php

@@ -1,68 +1,183 @@
 <?php
 
+/**
+ * This file contain fetch class, which is used to communicate with NTFY.sh
+ * REST api. It makes that HTTP request using buildin CURL extension is 
+ * much easier, and looks better in high level code.
+ * 
+ * @package phpnotify
+ * @author Cixo (Cixo Electronic)
+ */
+namespace phpnotify;
+
+use \TypeError as TypeError;
+use \RuntimeException as RuntimeException;
+
+use \CurlHandle as CurlHandle;
+use \curl_init as curl_init;
+use \curl_setopt as curl_setopt;
+use \curl_close as curl_close;
+use \curl_exec as curl_exec;
+use \curl_error as curl_error;
+use \curl_errno as curl_errno;
+
 require('response.php');
 
+/**
+ * This class is responsible for downloading data from the server. It uses 
+ * buildin CURL extension to make HTTP request. It not parsing response 
+ * directly, but use {@link response} 
+ */
 class fetch {
-    private bool $has_content;
+    
+    /**
+     * This store content of the requst body.
+     * @var ?string
+     */
+    private ?string $content;
+
+    /**
+     * This store type of content.
+     * @var ?string
+     */
+    private ?string $content_type;
+
+    /**
+     * This store method of the request.
+     * @var string
+     */
     private string $method;
-    private ?string $received;
-    private CurlHandle $request;
-
-    public function __construct(string $url, string $method = 'GET') {
-        $this->received = null;
-        $this->has_content = false;
-        $this->method = $method;
-
-        $this->request = curl_init();
-        $this->setopt(CURLOPT_RETURNTRANSFER, true);
-        $this->setopt(CURLOPT_FAILONERROR, true);
-        $this->setopt(CURLOPT_HEADER, true);
-        $this->setopt(CURLOPT_CUSTOMREQUEST, $method);
-        $this->setopt(CURLOPT_URL, curl_escape($url));
+
+    /**
+     * This store url to fetch.
+     * @var string
+     */
+    private string $url;
+
+    /**
+     * This create new fetch request. It is alias of constructor.
+     * 
+     * @link fetch::__construct()
+     * @static 
+     * @param string $url URL to fetch.
+     * @return fetch New fetch object.
+     */
+    public static function create(string $url): object {
+        return new self($url);
     }
 
-    public function set_json(array $content): object {
-        if ($this->has_content) {
-            throw new RuntimeException('JSON content already set.');
-        }
+    /**
+     * This initialize new fetch request by given URL. Default method for 
+     * the request is GET. If do not want to use new, see create.
+     * 
+     * @link fetch::create()
+     * @param string $url URL to work with.
+     */
+    public function __construct(string $url) {
+        $this->content = null;
+        $this->content_type = null;
+        $this->method = 'GET';
+        $this->url = $url;
+    }
+
+    /**
+     * This function could be use send array in the fetch request. Be careful
+     * because this function verify that method of the request is not GET or
+     * HEAD, which can not contain body. Property method must be set before.
+     * 
+     * @param array $content Content which must be send.
+     * @return fetch Itself to chain load.
+     */
+    public function send_array(array $content): object {
+        $content = json_encode($content);
+        $type = 'application/json';
 
+        $this->send_raw($content, $type);
+        return $this;
+        
+    }
+
+    /**
+     * This function is similar to {@link fetch::send_array()}, but work on 
+     * raw string data. Also content type must be set. Be careful, because
+     * it check that method of the request is not GET or HEAD, which can 
+     * not contain body content.
+     * 
+     * @param string $content Content of the body to send.
+     * @param string $type Type of the content, like "text/plain".
+     * @return fetch Self to chan loading.
+     */
+    public function send_raw(string $content, string $type): object {
         if ($this->method === 'GET' or $this->method === 'HEAD') {
-            throw new TypeError("JSON request can not be GET or HEAD.");
+            throw new TypeError('GET or HEAD request can not contain body.');
         }
 
-        $converted = json_encode($content);
-        $headers = [ 'Content-Type: application/json' ];
-        
-        $this->has_content = true;
-        $this->setopt(CURLOPT_POSTFIELDS, $converted);
-        $this->setopt(CURLOPT_HTTPHEADER, $headers);
+        $this->content = $content;
+        $this->content_type = $type;
+
+        return $this;
+    }
+
+    /**
+     * This function set method of the request. Be careful, because it check 
+     * that any content is not set, and when content is not null, but trying
+     * to set GET or HEAD method, it throw TypeError.
+     * 
+     * @param string $name Name of the method.
+     * @return fetch Self to chain loading.
+     */
+    public function set_method(string $name): object {
+        if ($this->content === null) {
+            $this->method = $name;
+            return $this;
+        }
+
+        if ($name === 'GET' || $name === 'HEAD') {
+            throw new TypeError('GET or HEAD request can not contain body.');
+        }
 
+        $this->method = $name;
         return $this;
     }
+    
+    /**
+     * That function make request to the server, from fetch config, which
+     * had been config previously. It return response in parsed form, as 
+     * instance of response class. Be careful, it raise an RuntimeException
+     * when server does not response correctly.
+     *
+     * @return response Response from the server.
+     */
+    public function request(): response { 
+        $request = curl_init($this->url);
+        curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($request, CURLOPT_FAILONERROR, true);
+        curl_setopt($request, CURLOPT_HEADER, true);
+        curl_setopt($request,CURLOPT_CUSTOMREQUEST, $this->method);
 
-    public function request(): string { 
-        if ($this->received !== null) {
-            return $this->received;
+        if ($this->content !== null) {
+            $type = 'Content-Type: '.$this->content_type;
+            $headers = [ $type ];
+
+            curl_setopt($request, CURLOPT_HTTPHEADER, $headers);
+            curl_setopt($request, CURLOPT_POSTFIELDS, $this->content);
         }
 
-        $result = curl_exec($this->request);
-        $error = curl_error($this->request);
-        $error_number = curl_errno($this->request);
+        $result = curl_exec($request);
+        $error = curl_error($request);
+        $error_number = curl_errno($request);
         
-        curl_close($this->received);
+        curl_close($request);
         
         if ($result !== false and $error_number === 0) {
-            return $this->received = $result;
+            return new response($result);
         }
 
         throw new RuntimeException(
             'Can not fetch request. Error code '
-            .string($error_number)
+            .strval($error_number)
             .': "'.$error.'".'
         );
     }
-
-    private function setopt(int $option, mixed $content): void {
-        curl_setopt($this->request, $option, $content);
-    }
+    
 }

+ 127 - 7
sources/response.php

@@ -1,12 +1,64 @@
 <?php
 
+/**
+ * This file contain class, which is responsible for prarsing raw content
+ * from the server, which would be provided by fetch class.
+ * 
+ * @package phpnotify
+ * @author Cixo (Cixo Electronic)
+ */
+namespace phpnotify;
+
+/**
+ * This class contain response from the server. It is responsible for
+ * parsing raw content from server. It would not being used by apps, 
+ * only {@link fetch} class would use it.
+ */
 class response {
+
+    /**
+     * This contain result code of the response.
+     * @var int
+     */
     private int $code;
+
+    /**
+     * This contain parsed respone, when it had been returned in object
+     * form, like JSON. When RAW content had been received, then that array
+     * is empty.
+     * @var array
+     */
     private array $parsed;
+
+    /**
+     * This contain headers of the response. Key in that array is header name,
+     * value for that is header content. Key must be lower case, but
+     * {@link response::get_header()} also handle it, thanks to it end user 
+     * doesn't need to remember about it.
+     * @var array
+     */
     private array $headers;
+
+    /**
+     * This contain HTTP protocol version, in which response had been 
+     * received, for example "HTTP/2".
+     * @var string
+     */
     private string $version;
+
+    /**
+     * That store RAW content of the response body.
+     * @var string
+     */
     private string $content;
 
+    /**
+     * That create new response from all content received from string. It is
+     * also responsible for starts of the parsing. That would be used only by
+     * {@link fetch} on raw content received from server.
+     * 
+     * @param string $content Content of the response from the server.
+     */
     public function __construct(string $content) {
         $parts = explode("\r\n\r\n", $content);
         $head = array_shift($parts);
@@ -16,18 +68,39 @@ class response {
         $this->parse_body($body);
     }
 
+    /**
+     * That return status code of the response.
+     * 
+     * @return int Number status of the response.
+     */
     public function get_code(): int {
         return $this->code;
     }
 
+    /**
+     * That return HTTP protocol version of the response, like "HTTP/2"
+     * for example.
+     * 
+     * @return string HTTP protocol version, like "HTTP/2".
+     */
     public function get_version(): string {
         return $this->version;
     }
 
+    /**
+     * That function return selected header of the response. When header did
+     * not being received, then it return content given to fallback parameter.
+     * 
+     * @param string $name Name of the header.
+     * @param ?string $fallback Value to return when header not exists.
+     * @return ?string Content of the header or fallback.
+     */
     public function get_header(
         string $name, 
         ?string $fallback = null
     ): ?string {
+        $name = strtolower($name);
+
         if (array_key_exists($name, $this->headers)) {
             return $this->headers[$name];
         }
@@ -35,26 +108,55 @@ class response {
         return $fallback;
     }
 
-    public function get_content(): string {
+    /**
+     * That function return RAW content of the response body.
+     * 
+     * @return string RAW content of the body.
+     */
+    public function receive_raw(): string {
         return $this->content;
     }
 
-    public function get_json_content(): array {
+    /**
+     * That return parsed content of the body, when content type of the
+     * response had been json. When content had been received in other form,
+     * then empty array had been returned.
+     * 
+     * @return array Parsed content of the body of empty array.
+     */
+    public function receive_array(): array {
         return $this->parsed;
     }
 
+    /**
+     * That function parse body section of the response.
+     * 
+     * @param string $body Body part of the response.
+     */
     private function parse_body(string $body): void {
-        $type = $this->get_header('Content-Type', 'text/plain'));
-        $is_json = strpos($type, 'json') !== -1;
+        $type = $this->get_header('Content-Type', 'text/plain');
+        $is_json = strpos($type, 'json') !== false;
 
         $this->content = $body;        
-        $this->parsed = array();
+        $result = array();
 
         if ($is_json) {
-            $this->parsed = json_decode($body);
+            $result = json_decode($body, true);
         }
+
+        /* Because json_decode could fail, when response had bad syntax. */
+        if (gettype($result) !== 'array') {
+            $result = array();
+        }
+        
+        $this->parsed = $result;   
     }
 
+    /**
+     * That function is responsible for parsing head of the response.
+     * 
+     * @param string $head RAW head content of the response.
+     */
     private function parse_head(string $head): void {
         $lines = explode("\r\n", $head);
         $first = array_shift($lines);
@@ -63,21 +165,39 @@ class response {
         $this->parse_headers($lines);
     }
 
+    /**
+     * That function parse first line, with status and HTTP version, of
+     * the response.
+     * 
+     * @param string $line First line of the response.
+     */
     private function parse_first_line(string $line): void {
         $parts = explode(' ', $line);
         $this->version = array_shift($parts);
-        $this->code = int(array_shift($parts));
+        $this->code = intval(array_shift($parts));
     }
 
+    /**
+     * That parse headers section of the response.
+     * 
+     * @param array $lines Lines from headers section.
+     */
     private function parse_headers(array $lines): void {
         $this->headers = array();
 
         foreach ($lines as $line) {
             $parts = explode(':', $line);
+
+            if (count($parts) < 2) {
+                continue;
+            }
+
             $name = array_shift($parts);
+            $name= strtolower($name);
             $content = ltrim(join(':', $parts));
 
             $this->headers[$name] = $content;
         }
     }
+    
 }

+ 23 - 0
tests/fetch.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace phpnotify;
+
+require('../sources/fetch.php');
+
+$response = fetch::create('https://jsonplaceholder.typicode.com/todos/1')->request();
+echo(var_dump($response->receive_array()));
+
+$create = fetch::create('https://jsonplaceholder.typicode.com/posts')
+->set_method('POST')
+->send_array([
+    'title' => 'test',
+    'body' => 'sample body',
+    'userId' => 1
+])
+->request();
+echo(var_dump($create->get_code()));
+echo(var_dump($create->receive_array()));
+
+$result = fetch::create('https://jsonplaceholder.typicode.com/posts/1')
+->request();
+echo(var_dump($result->receive_array()));