Skip to content

Commit 3d2c7df

Browse files
author
ahmard
committed
Initial Commit
0 parents  commit 3d2c7df

File tree

10 files changed

+374
-0
lines changed

10 files changed

+374
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/vendor/
2+
3+
/composer.lock
4+
/.phpunit.result.cache
5+
6+
/test.php

.idea/.gitignore

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ReactPHP QueryList
2+
3+
This library brought [ReactPHP](https://github.com/reactphp/http) and [QueryList](https://github.com/jae-jae/QueryList)
4+
together.
5+
6+
## Installation
7+
8+
```
9+
composer require ahmard/reactphp-querylist
10+
```
11+
12+
## Usage
13+
14+
- Playing with **QueryList**(scraping)
15+
```php
16+
use ReactphpQuerylist\Client;
17+
use ReactphpQuerylist\Queryable;
18+
19+
require 'vendor/autoload.php';
20+
21+
Client::get('https://google.com')
22+
->then(function (Queryable $queryable){
23+
$title = $queryable->queryList()->find('head title')->text();
24+
var_dump($title);
25+
})
26+
->otherwise(function ($error){
27+
echo $error;
28+
});
29+
```
30+
31+
- Working with response object
32+
```php
33+
use ReactphpQuerylist\Client;
34+
use ReactphpQuerylist\Queryable;
35+
36+
require 'vendor/autoload.php';
37+
38+
Client::get('https://google.com')
39+
->then(function (Queryable $queryable){
40+
var_dump($queryable->response()->getReasonPhrase());
41+
})
42+
->otherwise(function ($error){
43+
echo $error;
44+
});
45+
```

composer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "ahmard/reactphp-querylist",
3+
"description": "ReactPHP QueryList Scrapper",
4+
"type": "library",
5+
"require": {
6+
"php": "^7.4 || ^8.0",
7+
"ext-json": "*",
8+
"jaeger/querylist": "^4.2",
9+
"react/http": "^1.5"
10+
},
11+
"require-dev": {
12+
"phpstan/phpstan": "^0.12.99",
13+
"phpunit/phpunit": "^9.5",
14+
"clue/block-react": "^1.4"
15+
},
16+
"license": "MIT",
17+
"autoload": {
18+
"psr-4": {
19+
"ReactphpQuerylist\\": "src/"
20+
}
21+
},
22+
"autoload-dev": {
23+
"psr-4": {
24+
"ReactphpQuerylist\\Tests\\": "tests/"
25+
}
26+
},
27+
"scripts": {
28+
"analyse": "phpstan analyse",
29+
"analyze": "@analyse",
30+
"test": "phpunit"
31+
},
32+
"authors": [
33+
{
34+
"name": "ahmard",
35+
"email": "ahmard06@gmail.com"
36+
}
37+
]
38+
}

phpstan.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
parameters:
2+
level: 6
3+
paths: ['src']
4+
checkMissingIterableValueType: false
5+
checkGenericClassInNonGenericObjectType: false
6+
ignoreErrors:
7+
- '#Unsafe usage of new static\(\)#'
8+
- '#Call to an undefined method React\\Promise\\PromiseInterface::otherwise\(\)#'

phpunit.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit backupGlobals="false"
4+
backupStaticAttributes="false"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="false"
10+
>
11+
<testsuites>
12+
<testsuite name="ReactPHP QueryList Tests">
13+
<directory>./tests</directory>
14+
</testsuite>
15+
</testsuites>
16+
</phpunit>

src/Client.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
namespace ReactphpQuerylist;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use React\Http\Browser;
7+
use React\Promise\Deferred;
8+
use React\Promise\PromiseInterface;
9+
use React\Stream\ReadableStreamInterface;
10+
11+
class Client
12+
{
13+
/**
14+
* @param string $requestMethod
15+
* @param string $url
16+
* @param array $headers
17+
* @param ReadableStreamInterface|string $body
18+
* @return PromiseInterface
19+
*/
20+
public function execute(
21+
string $requestMethod,
22+
string $url,
23+
array $headers = [],
24+
$body = ''
25+
): PromiseInterface
26+
{
27+
return $this->prepareQueryable(
28+
(new Browser())->request($requestMethod, $url, $headers, $body)
29+
);
30+
}
31+
32+
/**
33+
* Construct QueryList object from response body
34+
*
35+
* @param PromiseInterface $promise
36+
* @return PromiseInterface
37+
*/
38+
protected function prepareQueryable(PromiseInterface $promise): PromiseInterface
39+
{
40+
$deferred = new Deferred();
41+
42+
// Construct queryable object when request is success
43+
$promise->then(function (ResponseInterface $response) use ($deferred) {
44+
$deferred->resolve(new Queryable($response));
45+
});
46+
47+
// Forward any error to child promise
48+
$promise->otherwise(fn($reason) => $deferred->reject($reason));
49+
50+
return $deferred->promise();
51+
}
52+
53+
/**
54+
* Send http GET request
55+
*
56+
* @param string $url
57+
* @param array $headers
58+
* @return PromiseInterface
59+
*/
60+
public static function get(string $url, array $headers = []): PromiseInterface
61+
{
62+
return (new static())->execute('GET', $url, $headers);
63+
}
64+
65+
/**
66+
* Send http POST request
67+
*
68+
* @param string $url
69+
* @param array $headers
70+
* @param ReadableStreamInterface|string $body
71+
* @return PromiseInterface
72+
*/
73+
public static function post(string $url, array $headers = [], $body = ''): PromiseInterface
74+
{
75+
return (new static())->execute('POST', $url, $headers, $body);
76+
}
77+
78+
/**
79+
* Send http HEAD request
80+
*
81+
* @param string $url
82+
* @param array $headers
83+
* @return PromiseInterface
84+
*/
85+
public static function head(string $url, array $headers = []): PromiseInterface
86+
{
87+
return (new static())->execute('HEAD', $url, $headers);
88+
}
89+
90+
/**
91+
* Send http PATCH request
92+
*
93+
* @param string $url
94+
* @param array $headers
95+
* @param ReadableStreamInterface|string $body
96+
* @return PromiseInterface
97+
*/
98+
public static function patch(string $url, array $headers = [], $body = ''): PromiseInterface
99+
{
100+
return (new static())->execute('PATCH', $url, $headers, $body);
101+
}
102+
103+
/**
104+
* Send http PUT request
105+
*
106+
* @param string $url
107+
* @param array $headers
108+
* @param ReadableStreamInterface|string $body
109+
* @return PromiseInterface
110+
*/
111+
public static function put(string $url, array $headers = [], $body = ''): PromiseInterface
112+
{
113+
return (new static())->execute('PUT', $url, $headers, $body);
114+
}
115+
116+
/**
117+
* Send http DELETE request
118+
*
119+
* @param string $url
120+
* @param array $headers
121+
* @return PromiseInterface
122+
*/
123+
public static function delete(string $url, array $headers = []): PromiseInterface
124+
{
125+
return (new static())->execute('DELETE', $url, $headers);
126+
}
127+
128+
/**
129+
* Submit post request with json body
130+
*
131+
* @param string $url
132+
* @param array $payload ['key' => 'value']
133+
* @param array $headers
134+
* @return PromiseInterface
135+
*/
136+
public static function postJson(string $url, array $payload, array $headers = []): PromiseInterface
137+
{
138+
return self::post(
139+
$url,
140+
array_merge(['Content-Type' => 'application/json'], $headers),
141+
json_encode($payload)
142+
);
143+
}
144+
145+
/**
146+
* Submit form
147+
*
148+
* @param string $url
149+
* @param array $fields ['name' => 'John Doe']
150+
* @param array $headers
151+
* @return PromiseInterface
152+
*/
153+
public static function postForm(string $url, array $fields, array $headers = []): PromiseInterface
154+
{
155+
return self::post(
156+
$url,
157+
array_merge(['Content-Type' => 'application/x-www-form-urlencoded'], $headers),
158+
http_build_query($fields)
159+
);
160+
}
161+
}

src/Queryable.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace ReactphpQuerylist;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use QL\QueryList;
7+
8+
class Queryable
9+
{
10+
protected ResponseInterface $response;
11+
protected QueryList $queryList;
12+
13+
14+
public function __construct(ResponseInterface $response)
15+
{
16+
$this->response = $response;
17+
$this->queryList = QueryList::getInstance()->setHtml($response->getBody()->getContents());
18+
}
19+
20+
/**
21+
* @return QueryList
22+
*/
23+
public function queryList(): QueryList
24+
{
25+
return $this->queryList;
26+
}
27+
28+
/**
29+
* @return ResponseInterface
30+
*/
31+
public function response(): ResponseInterface
32+
{
33+
return $this->response;
34+
}
35+
}

tests/ClientTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace ReactphpQuerylist\Tests;
4+
5+
use ReactphpQuerylist\Queryable;
6+
use Exception;
7+
use PHPUnit\Framework\TestCase;
8+
use Psr\Http\Message\ResponseInterface;
9+
use QL\QueryList;
10+
use React\EventLoop\Loop;
11+
use React\Promise\PromiseInterface;
12+
use function Clue\React\Block\await;
13+
14+
class ClientTest extends TestCase
15+
{
16+
public function testRequestMethods(): void
17+
{
18+
self::assertInstanceOf(PromiseInterface::class, TestableClient::get('/'));
19+
self::assertInstanceOf(PromiseInterface::class, TestableClient::post('/'));
20+
self::assertInstanceOf(PromiseInterface::class, TestableClient::head('/'));
21+
self::assertInstanceOf(PromiseInterface::class, TestableClient::delete('/'));
22+
self::assertInstanceOf(PromiseInterface::class, TestableClient::put('/'));
23+
self::assertInstanceOf(PromiseInterface::class, TestableClient::patch('/'));
24+
}
25+
26+
/**
27+
* @throws Exception
28+
*/
29+
public function testQueryAble(): void
30+
{
31+
$queryAble = await(TestableClient::get('/'), Loop::get());
32+
33+
self::assertInstanceOf(Queryable::class, $queryAble);
34+
self::assertInstanceOf(ResponseInterface::class, $queryAble->response());
35+
self::assertInstanceOf(QueryList::class, $queryAble->queryList());
36+
}
37+
}

tests/TestableClient.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace ReactphpQuerylist\Tests;
4+
5+
use ReactphpQuerylist\Client;
6+
use React\Http\Message\Response;
7+
use React\Promise\Deferred;
8+
use React\Promise\PromiseInterface;
9+
10+
class TestableClient extends Client
11+
{
12+
public function execute(string $requestMethod, string $url, array $headers = [], $body = ''): PromiseInterface
13+
{
14+
$deferred = new Deferred();
15+
16+
$deferred->resolve(new Response(200, [], uniqid()));
17+
18+
return $this->prepareQueryable($deferred->promise());
19+
}
20+
}

0 commit comments

Comments
 (0)