├── scraping-proxy ├── downloads │ └── .gitkeep ├── composer.json ├── index.php ├── src │ ├── Image.php │ ├── Scraper.php │ ├── Crawler.php │ ├── Downloader.php │ ├── Storage.php │ └── ScraperViaProxy.php └── ScraperViaProxy.php ├── .gitignore ├── scraping-save-files ├── downloads │ └── .gitkeep ├── composer.json ├── index.php └── src │ ├── Scraper.php │ ├── Image.php │ ├── Crawler.php │ ├── Downloader.php │ └── Storage.php ├── middleware ├── requests.http ├── composer.json ├── Logging.php ├── CustomHeader.php └── server.php ├── logo.jpg ├── http ├── composer.json ├── server.php └── posts.php ├── promises ├── composer.json └── promises.php ├── streams ├── composer.json ├── writable.php └── readable.php ├── event-loop ├── composer.json ├── delay.php ├── block.php └── repeat.php ├── routing ├── composer.json ├── tasks.http └── server.php ├── asynchronous-downloads ├── composer.json ├── quick_start.php ├── index.php └── Downloader │ ├── Downloader.php │ └── PromiseBasedDownloader.php ├── socket ├── composer.json ├── server.php ├── client.php └── ConnectionsPool.php ├── psr-15-middleware ├── composer.json └── server.php ├── scraping-extract-data ├── composer.json ├── index.php ├── src │ ├── Image.php │ └── Scraper.php ├── quick-start.php └── quick_start.php ├── scraping-store-to-db ├── composer.json ├── index.php ├── src │ ├── Image.php │ ├── Scraper.php │ └── Storage.php └── quick_start.php └── README.md /scraping-proxy/downloads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | downloads 3 | -------------------------------------------------------------------------------- /scraping-save-files/downloads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /middleware/requests.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8000 2 | 3 | ### 4 | 5 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregazhuk/reactphp-video-tutorials/HEAD/logo.jpg -------------------------------------------------------------------------------- /http/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /middleware/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /promises/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/promise": "^2.5.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /streams/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/stream": "^0.7.7" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /event-loop/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/event-loop": "^0.5.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /routing/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3", 4 | "ext-json": "*", 5 | "nikic/fast-route": "^1.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /asynchronous-downloads/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "clue/buzz-react": "^2.4", 4 | "react/filesystem": "^0.1.1", 5 | "react/promise-stream": "^1.1.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /socket/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/socket": "^0.8.11", 4 | "symfony/console": "^4.0", 5 | "clue/stdio-react": "^2.1", 6 | "kevinlebrun/colors.php": "^1.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /event-loop/delay.php: -------------------------------------------------------------------------------- 1 | addTimer(1, function () { 8 | echo "After timer\n"; 9 | }); 10 | 11 | echo "Before timer\n"; 12 | 13 | $loop->run(); 14 | -------------------------------------------------------------------------------- /psr-15-middleware/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3", 4 | "middlewares/client-ip": "^1.1", 5 | "for/http-middleware-psr15-adapter": "^1.0", 6 | "middlewares/redirect": "^1.1", 7 | "guzzlehttp/psr7": "^1.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /event-loop/block.php: -------------------------------------------------------------------------------- 1 | addPeriodicTimer(1, function () { 8 | echo "Hello\n"; 9 | }); 10 | 11 | $loop->addTimer(1, function () { 12 | sleep(5); 13 | }); 14 | 15 | $loop->run(); 16 | -------------------------------------------------------------------------------- /scraping-extract-data/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "clue/buzz-react": "^2.4", 4 | "symfony/dom-crawler": "^4.1", 5 | "symfony/css-selector": "^4.1" 6 | }, 7 | "autoload": { 8 | "psr-4": { 9 | "AsyncScraper\\" : "src/" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scraping-store-to-db/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "clue/buzz-react": "^2.5", 4 | "symfony/dom-crawler": "^4.1", 5 | "symfony/css-selector": "^4.1", 6 | "react/mysql": "^0.5" 7 | }, 8 | "autoload": { 9 | "psr-4": { 10 | "AsyncScraper\\": "src/" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scraping-save-files/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "clue/buzz-react": "^2.5", 4 | "symfony/dom-crawler": "^4.1", 5 | "symfony/css-selector": "^4.1", 6 | "react/mysql": "^0.5", 7 | "react/filesystem": "^0.1.2" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "AsyncScraper\\": "src/" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /middleware/Logging.php: -------------------------------------------------------------------------------- 1 | getMethod() . PHP_EOL; 10 | echo 'Path: ' . $request->getUri()->getPath() . PHP_EOL; 11 | 12 | return $next($request); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scraping-proxy/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "clue/buzz-react": "^2.5", 4 | "symfony/dom-crawler": "^4.1", 5 | "symfony/css-selector": "^4.1", 6 | "react/mysql": "^0.5", 7 | "react/filesystem": "^0.1.2", 8 | "clue/socks-react": "^1.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "AsyncScraper\\": "src/" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /event-loop/repeat.php: -------------------------------------------------------------------------------- 1 | addPeriodicTimer(1, function (TimerInterface $timer) use (&$counter, $loop) { 11 | $counter ++; 12 | 13 | if ($counter === 5) { 14 | $loop->cancelTimer($timer); 15 | } 16 | 17 | echo "Hello\n"; 18 | }); 19 | 20 | $loop->run(); 21 | -------------------------------------------------------------------------------- /asynchronous-downloads/quick_start.php: -------------------------------------------------------------------------------- 1 | get('http://www.google.com/')->then(function (ResponseInterface $response) { 12 | var_dump($response->getHeaders(), (string)$response->getBody()); 13 | }); 14 | 15 | $loop->run(); 16 | -------------------------------------------------------------------------------- /routing/tasks.http: -------------------------------------------------------------------------------- 1 | ### List all tasks 2 | GET 127.0.0.1:8000/tasks 3 | 4 | ### 5 | 6 | ### View task 7 | GET 127.0.0.1:8000/tasks/1 8 | 9 | ### 10 | 11 | ### Create a new task (success) 12 | POST 127.0.0.1:8000/tasks 13 | Content-Type: application/x-www-form-urlencoded 14 | 15 | task=new task 16 | 17 | ### 18 | 19 | ### Create a new task (fail) 20 | POST 127.0.0.1:8000/tasks 21 | 22 | ### 23 | 24 | ### Not found 25 | GET 127.0.0.1:8000/about 26 | 27 | ### 28 | -------------------------------------------------------------------------------- /scraping-proxy/index.php: -------------------------------------------------------------------------------- 1 | scrape(...$urls); 14 | $loop->run(); 15 | -------------------------------------------------------------------------------- /socket/server.php: -------------------------------------------------------------------------------- 1 | on('connection', function(ConnectionInterface $connection) use ($pool){ 12 | $pool->add($connection); 13 | }); 14 | echo "Listening on {$socket->getAddress()}\n"; 15 | $loop->run(); 16 | -------------------------------------------------------------------------------- /scraping-extract-data/index.php: -------------------------------------------------------------------------------- 1 | scrape(...$urls)->then('print_r'); 15 | 16 | $loop->run(); 17 | -------------------------------------------------------------------------------- /streams/writable.php: -------------------------------------------------------------------------------- 1 | pipe($toUpper)->pipe($writable); 16 | 17 | $loop->run(); 18 | 19 | -------------------------------------------------------------------------------- /streams/readable.php: -------------------------------------------------------------------------------- 1 | on('data', function ($chunk) use ($readable, $loop) { 10 | echo $chunk . PHP_EOL; 11 | $readable->pause(); 12 | 13 | $loop->addTimer(1, function () use ($readable) { 14 | $readable->resume(); 15 | }); 16 | }); 17 | 18 | $readable->on('end', function () { 19 | echo 'Finished' . PHP_EOL; 20 | }); 21 | 22 | $loop->run(); 23 | -------------------------------------------------------------------------------- /middleware/CustomHeader.php: -------------------------------------------------------------------------------- 1 | title = $title; 12 | $this->value = $value; 13 | } 14 | 15 | public function __invoke(\Psr\Http\Message\ServerRequestInterface $request, callable $next) 16 | { 17 | /** @var \React\Http\Response $response */ 18 | $response = $next($request); 19 | 20 | return $response->withHeader($this->title, $this->value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scraping-store-to-db/index.php: -------------------------------------------------------------------------------- 1 | scrape(...$urls) 18 | ->then(function (array $images) use ($storage) { 19 | $storage->saveIfNotExist(...$images); 20 | }); 21 | 22 | $loop->run(); 23 | -------------------------------------------------------------------------------- /http/server.php: -------------------------------------------------------------------------------- 1 | getUri()->getPath(); 13 | if ($path === '/store') { 14 | $posts[] = json_decode((string)$request->getBody(), true); 15 | 16 | return new Response(201); 17 | } 18 | 19 | return new Response(200, ['Content-Type' => 'application/json'], json_encode($posts)); 20 | }); 21 | $socket = new \React\Socket\Server('127.0.0.1:8000', $loop); 22 | 23 | $server->listen($socket); 24 | $loop->run(); 25 | -------------------------------------------------------------------------------- /socket/client.php: -------------------------------------------------------------------------------- 1 | connect('127.0.0.1:8000') 15 | ->then( 16 | function (ConnectionInterface $connection) use ($input, $output) { 17 | $input->pipe($connection)->pipe($output); 18 | }, 19 | function (Exception $exception) { 20 | echo $exception->getMessage() . PHP_EOL; 21 | } 22 | ); 23 | 24 | $loop->run(); 25 | 26 | -------------------------------------------------------------------------------- /asynchronous-downloads/index.php: -------------------------------------------------------------------------------- 1 | withOptions(['streaming' => true]), 13 | Filesystem::create($loop), 14 | __DIR__ . '/downloads' 15 | ); 16 | 17 | $downloader->download(...[ 18 | 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4', 19 | 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_2mb.mp4', 20 | 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_5mb.mp4', 21 | ]); 22 | $loop->run(); 23 | echo 'done'; 24 | -------------------------------------------------------------------------------- /scraping-extract-data/src/Image.php: -------------------------------------------------------------------------------- 1 | id = $id; 31 | $this->title = $title; 32 | $this->resolution = $resolution; 33 | $this->source = $source; 34 | $this->tags = $tags; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /promises/promises.php: -------------------------------------------------------------------------------- 1 | resolve($response); 11 | } else { 12 | $deferred->reject(new Exception('No response')); 13 | } 14 | 15 | return $deferred->promise(); 16 | } 17 | 18 | http('http://google.com', 'GET') 19 | ->then(function ($response) { 20 | throw new Exception('error'); 21 | return strtoupper($response); 22 | }) 23 | ->then( 24 | function ($response) { 25 | echo $response . PHP_EOL; 26 | }) 27 | ->otherwise( 28 | function (Exception $exception) { 29 | echo $exception->getMessage() . PHP_EOL; 30 | }); 31 | -------------------------------------------------------------------------------- /scraping-save-files/index.php: -------------------------------------------------------------------------------- 1 | withOptions(['streaming' => true]), 14 | Filesystem::create($loop), 15 | __DIR__ . '/downloads' 16 | ); 17 | 18 | $storage = new \AsyncScraper\Storage($loop, 'root:@localhost/scraping?idle=0'); 19 | $crawler = new \AsyncScraper\Crawler($client); 20 | $scraper = new \AsyncScraper\Scraper($crawler, $downloader, $storage); 21 | 22 | $urls = [ 23 | 'https://www.pexels.com/photo/adorable-animal-cat-cat-s-eyes-236603/', 24 | 'https://www.pexels.com/photo/adorable-animal-baby-blur-177809/', 25 | ]; 26 | 27 | $scraper->scrape(...$urls); 28 | $loop->run(); 29 | -------------------------------------------------------------------------------- /middleware/server.php: -------------------------------------------------------------------------------- 1 | getUri()->getPath() === '/admin') { 14 | return new Response(301, ['Location' => '/']); 15 | } 16 | 17 | return $next($request); 18 | }; 19 | 20 | $hello = function () { 21 | return new Response(200, ['Content-Type' => 'text/plain'], 'Hello, world'); 22 | }; 23 | $server = new \React\Http\Server([ 24 | new Logging(), 25 | new CustomHeader('X-Custom', 'foo'), 26 | $redirect, 27 | $hello 28 | ]); 29 | $socket = new \React\Socket\Server('127.0.0.1:8000', $loop); 30 | 31 | $server->listen($socket); 32 | $loop->run(); 33 | -------------------------------------------------------------------------------- /psr-15-middleware/server.php: -------------------------------------------------------------------------------- 1 | withMiddleware(\Middlewares\ClientIp::class) 14 | ->withMiddleware(\Middlewares\Redirect::class, [['/secret' => '/']]), 15 | function (ServerRequestInterface $request, callable $next) { 16 | echo 'Client IP: ' . $request->getAttribute('client-ip') . PHP_EOL; 17 | return $next($request); 18 | }, 19 | function () { 20 | return new Response(200, ['Content-Type' => 'text/plain'], 'Hello, world'); 21 | } 22 | ]); 23 | $socket = new \React\Socket\Server('127.0.0.1:8000', $loop); 24 | 25 | $server->listen($socket); 26 | $loop->run(); 27 | -------------------------------------------------------------------------------- /scraping-save-files/src/Scraper.php: -------------------------------------------------------------------------------- 1 | crawler = $crawler; 16 | $this->downloader = $downloader; 17 | $this->storage = $storage; 18 | } 19 | 20 | public function scrape(string ...$urls): void 21 | { 22 | foreach ($urls as $url) { 23 | $this->crawler->extractImageFromUrl($url) 24 | ->then(function (Image $image) { 25 | $this->downloader->download($image->source); 26 | return $image; 27 | }) 28 | ->then(function (Image $image) { 29 | $this->storage->saveIfNotExist($image); 30 | }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scraping-proxy/src/Image.php: -------------------------------------------------------------------------------- 1 | id = $id; 31 | $this->title = $title; 32 | $this->resolution = $resolution; 33 | $this->source = $source; 34 | $this->tags = $tags; 35 | } 36 | 37 | public function toArray(): array 38 | { 39 | return [ 40 | 'id' => $this->id, 41 | 'title' => $this->title, 42 | 'tags' => json_encode($this->tags), 43 | 'resolution' => $this->resolution, 44 | 'source' => $this->source, 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scraping-save-files/src/Image.php: -------------------------------------------------------------------------------- 1 | id = $id; 31 | $this->title = $title; 32 | $this->resolution = $resolution; 33 | $this->source = $source; 34 | $this->tags = $tags; 35 | } 36 | 37 | public function toArray(): array 38 | { 39 | return [ 40 | 'id' => $this->id, 41 | 'title' => $this->title, 42 | 'tags' => json_encode($this->tags), 43 | 'resolution' => $this->resolution, 44 | 'source' => $this->source, 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scraping-store-to-db/src/Image.php: -------------------------------------------------------------------------------- 1 | id = $id; 31 | $this->title = $title; 32 | $this->resolution = $resolution; 33 | $this->source = $source; 34 | $this->tags = $tags; 35 | } 36 | 37 | public function toArray(): array 38 | { 39 | return [ 40 | 'id' => $this->id, 41 | 'title' => $this->title, 42 | 'tags' => json_encode($this->tags), 43 | 'resolution' => $this->resolution, 44 | 'source' => $this->source, 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scraping-proxy/src/Scraper.php: -------------------------------------------------------------------------------- 1 | crawler = $crawler; 16 | $this->downloader = $downloader; 17 | $this->storage = $storage; 18 | } 19 | 20 | public function scrape(string ...$urls): void 21 | { 22 | foreach ($urls as $url) { 23 | $this->crawler 24 | ->extractImageFromUrl($url) 25 | ->then(function (Image $image) { 26 | $this->downloader->download($image->source); 27 | return $image; 28 | }) 29 | ->then(function (Image $image) { 30 | $this->storage->saveIfNotExist($image); 31 | }) 32 | ->otherwise(function (\Exception $exception) { 33 | echo $exception->getMessage() . PHP_EOL; 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scraping-extract-data/quick-start.php: -------------------------------------------------------------------------------- 1 | get('https://www.pexels.com/photo/adorable-animal-baby-blur-177809/')->then( 14 | function (ResponseInterface $response) { 15 | $crawler = new Crawler((string)$response->getBody()); 16 | 17 | $title = $crawler->filter('h1.box__title')->text(); 18 | $tags = $crawler->filter('.list-inline.list-padding li a')->extract(['_text']); 19 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 20 | $source = $crawler->filter('.btn-primary.btn--lg.btn--splitted a')->attr('href'); 21 | $id = $crawler->filter('.btn-primary.btn--lg.btn--splitted a')->attr('data-id'); 22 | 23 | print_r([ 24 | 'id' => $id, 25 | 'title' => $title, 26 | 'tags' => $tags, 27 | 'resolution' => $resolution, 28 | 'source' => $source 29 | ]); 30 | } 31 | ); 32 | 33 | $loop->run(); 34 | -------------------------------------------------------------------------------- /asynchronous-downloads/Downloader/Downloader.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | $this->filesystem = $filesystem; 21 | $this->directory = $directory; 22 | } 23 | 24 | public function download(string ...$urls): void 25 | { 26 | foreach ($urls as $url) { 27 | $file = $this->openFileFor($url); 28 | 29 | $this->client->get($url)->then( 30 | function (ResponseInterface $response) use ($file) { 31 | $response->getBody()->pipe($file); 32 | } 33 | ); 34 | } 35 | } 36 | 37 | private function openFileFor(string $url): WritableStreamInterface 38 | { 39 | $path = $this->directory . DIRECTORY_SEPARATOR . basename($url); 40 | 41 | return UnwrapWritable($this->filesystem->file($path)->open('cw')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scraping-extract-data/quick_start.php: -------------------------------------------------------------------------------- 1 | get('https://www.pexels.com/photo/adorable-animal-blur-cat-617278/') 14 | ->then(function (ResponseInterface $response) { 15 | $crawler = new Crawler((string) $response->getBody()); 16 | // title - h1.box__title 17 | // tags - .list-inline.list-padding a 18 | // resolution - .icon-list .icon-list__title 19 | // source - .btn-primary.btn--lg.btn--splitted a 20 | $title = $crawler->filter('h1.box__title')->text(); 21 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 22 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 23 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 24 | $source = $link->attr('href'); 25 | $id = $link->attr('data-id'); 26 | 27 | print_r([ 28 | $title, 29 | $tags, 30 | $resolution, 31 | $source, 32 | $id 33 | ]); 34 | }); 35 | 36 | $loop->run(); 37 | 38 | -------------------------------------------------------------------------------- /scraping-store-to-db/quick_start.php: -------------------------------------------------------------------------------- 1 | get('https://www.pexels.com/photo/adorable-animal-blur-cat-617278/') 14 | ->then(function (ResponseInterface $response) { 15 | $crawler = new Crawler((string) $response->getBody()); 16 | // title - h1.box__title 17 | // tags - .list-inline.list-padding a 18 | // resolution - .icon-list .icon-list__title 19 | // source - .btn-primary.btn--lg.btn--splitted a 20 | $title = $crawler->filter('h1.box__title')->text(); 21 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 22 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 23 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 24 | $source = $link->attr('href'); 25 | $id = $link->attr('data-id'); 26 | 27 | print_r([ 28 | $title, 29 | $tags, 30 | $resolution, 31 | $source, 32 | $id 33 | ]); 34 | }); 35 | 36 | $loop->run(); 37 | 38 | -------------------------------------------------------------------------------- /scraping-proxy/src/Crawler.php: -------------------------------------------------------------------------------- 1 | browser = $browser; 17 | } 18 | 19 | public function extractImageFromUrl($url): PromiseInterface 20 | { 21 | return $this->browser->get($url)->then(function (ResponseInterface $response) { 22 | return $this->extractFromHtml((string)$response->getBody()); 23 | }); 24 | } 25 | 26 | private function extractFromHtml(string $responseBody): Image 27 | { 28 | $crawler = new SymfonyCrawler($responseBody); 29 | $title = $crawler->filter('h1.box__title')->text(); 30 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 31 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 32 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 33 | $rawSource = $link->attr('href'); 34 | $source = substr($rawSource, 0, strpos($rawSource, '?')); 35 | $id = $link->attr('data-id'); 36 | 37 | return new Image($id, $title, $resolution, $source, ...$tags); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scraping-save-files/src/Crawler.php: -------------------------------------------------------------------------------- 1 | browser = $browser; 17 | } 18 | 19 | public function extractImageFromUrl($url): PromiseInterface 20 | { 21 | return $this->browser->get($url)->then(function (ResponseInterface $response) { 22 | return $this->extractFromHtml((string)$response->getBody()); 23 | }); 24 | } 25 | 26 | private function extractFromHtml(string $responseBody): Image 27 | { 28 | $crawler = new SymfonyCrawler($responseBody); 29 | $title = $crawler->filter('h1.box__title')->text(); 30 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 31 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 32 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 33 | $rawSource = $link->attr('href'); 34 | $source = substr($rawSource, 0, strpos($rawSource, '?')); 35 | $id = $link->attr('data-id'); 36 | 37 | return new Image($id, $title, $resolution, $source, ...$tags); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /asynchronous-downloads/Downloader/PromiseBasedDownloader.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | $this->filesystem = $filesystem; 21 | $this->directory = $directory; 22 | } 23 | 24 | public function download(string ...$urls): void 25 | { 26 | foreach ($urls as $url) { 27 | $this->openFileFor($url)->then( 28 | function (WritableStreamInterface $file) use ($url) { 29 | $this->client->get($url)->then( 30 | function (ResponseInterface $response) use ($file) { 31 | $response->getBody()->pipe($file); 32 | } 33 | ); 34 | } 35 | ); 36 | } 37 | } 38 | 39 | private function openFileFor(string $url): PromiseInterface 40 | { 41 | $path = $this->directory . DIRECTORY_SEPARATOR . basename($url); 42 | 43 | return $this->filesystem->file($path)->open('cw'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scraping-proxy/src/Downloader.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | $this->filesystem = $filesystem; 25 | $this->directory = $directory; 26 | } 27 | 28 | public function download(string $url): PromiseInterface 29 | { 30 | $file = $this->openFileFor($url); 31 | $deferred = new Deferred(); 32 | $file->on('close', function () use ($deferred) { 33 | $deferred->resolve(); 34 | }); 35 | 36 | $this->client->get($url)->then( 37 | function (ResponseInterface $response) use ($file) { 38 | $response->getBody()->pipe($file); 39 | } 40 | ); 41 | 42 | return $deferred->promise(); 43 | } 44 | 45 | private function openFileFor(string $url): WritableStreamInterface 46 | { 47 | $path = $this->directory . DIRECTORY_SEPARATOR . basename($url); 48 | 49 | return UnwrapWritable($this->filesystem->file($path)->open('cw')); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scraping-save-files/src/Downloader.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | $this->filesystem = $filesystem; 25 | $this->directory = $directory; 26 | } 27 | 28 | public function download(string $url): PromiseInterface 29 | { 30 | $file = $this->openFileFor($url); 31 | $deferred = new Deferred(); 32 | $file->on('close', function () use ($deferred) { 33 | $deferred->resolve(); 34 | }); 35 | 36 | $this->client->get($url)->then( 37 | function (ResponseInterface $response) use ($file) { 38 | $response->getBody()->pipe($file); 39 | } 40 | ); 41 | 42 | return $deferred->promise(); 43 | } 44 | 45 | private function openFileFor(string $url): WritableStreamInterface 46 | { 47 | $path = $this->directory . DIRECTORY_SEPARATOR . basename($url); 48 | 49 | return UnwrapWritable($this->filesystem->file($path)->open('cw')); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scraping-extract-data/src/Scraper.php: -------------------------------------------------------------------------------- 1 | browser = $browser; 18 | } 19 | 20 | public function scrape(string ...$urls): PromiseInterface 21 | { 22 | $promises = array_map(function ($url) { 23 | return $this->extractFromUrl($url); 24 | }, $urls); 25 | 26 | return all($promises); 27 | } 28 | 29 | private function extract(string $responseBody): Image 30 | { 31 | $crawler = new Crawler($responseBody); 32 | $title = $crawler->filter('h1.box__title')->text(); 33 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 34 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 35 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 36 | $source = $link->attr('href'); 37 | $id = $link->attr('data-id'); 38 | 39 | return new Image($id, $title, $resolution, $source, ...$tags); 40 | } 41 | 42 | private function extractFromUrl($url): PromiseInterface 43 | { 44 | return $this->browser->get($url)->then(function (ResponseInterface $response) { 45 | return $this->extract((string) $response->getBody()); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scraping-store-to-db/src/Scraper.php: -------------------------------------------------------------------------------- 1 | browser = $browser; 18 | } 19 | 20 | public function scrape(string ...$urls): PromiseInterface 21 | { 22 | $promises = array_map(function ($url) { 23 | return $this->extractFromUrl($url); 24 | }, $urls); 25 | 26 | return all($promises); 27 | } 28 | 29 | private function extract(string $responseBody): Image 30 | { 31 | $crawler = new Crawler($responseBody); 32 | $title = $crawler->filter('h1.box__title')->text(); 33 | $tags = $crawler->filter('.list-inline.list-padding a')->extract(['_text']); 34 | $resolution = $crawler->filter('.icon-list .icon-list__title')->text(); 35 | $link = $crawler->filter('.btn-primary.btn--lg.btn--splitted a'); 36 | $source = $link->attr('href'); 37 | $id = $link->attr('data-id'); 38 | 39 | return new Image($id, $title, $resolution, $source, ...$tags); 40 | } 41 | 42 | private function extractFromUrl($url): PromiseInterface 43 | { 44 | return $this->browser->get($url)->then(function (ResponseInterface $response) { 45 | return $this->extract((string) $response->getBody()); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scraping-proxy/src/Storage.php: -------------------------------------------------------------------------------- 1 | connection = (new Factory($loop))->createLazyConnection($uri); 20 | } 21 | 22 | public function saveIfNotExist(Image $image): void 23 | { 24 | $this->isNotStored($image->id) 25 | ->then(function () use ($image) { 26 | $this->save($image); 27 | }); 28 | } 29 | 30 | private function save(Image $image): void 31 | { 32 | $sql = 'INSERT INTO images (id, title, tags, resolution, source) VALUES (?, ?, ?, ?, ?)'; 33 | $this->connection->query($sql, $image->toArray()) 34 | ->then(null, function (Exception $exception) { 35 | echo $exception->getMessage() . PHP_EOL; 36 | }); 37 | } 38 | 39 | private function isNotStored(int $id): PromiseInterface 40 | { 41 | $sql = 'SELECT 1 FROM images WHERE id = ?'; 42 | return $this->connection->query($sql, [$id]) 43 | ->then( 44 | function (QueryResult $result) { 45 | return count($result->resultRows) ? reject() : resolve(); 46 | }, 47 | function (Exception $exception) { 48 | echo 'Error: ' . $exception->getMessage() . PHP_EOL; 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scraping-save-files/src/Storage.php: -------------------------------------------------------------------------------- 1 | connection = (new Factory($loop))->createLazyConnection($uri); 20 | } 21 | 22 | public function saveIfNotExist(Image $image): void 23 | { 24 | $this->isNotStored($image->id) 25 | ->then(function () use ($image) { 26 | $this->save($image); 27 | }); 28 | } 29 | 30 | private function save(Image $image): void 31 | { 32 | $sql = 'INSERT INTO images (id, title, tags, resolution, source) VALUES (?, ?, ?, ?, ?)'; 33 | $this->connection->query($sql, $image->toArray()) 34 | ->then(null, function (Exception $exception) { 35 | echo $exception->getMessage() . PHP_EOL; 36 | }); 37 | } 38 | 39 | private function isNotStored(int $id): PromiseInterface 40 | { 41 | $sql = 'SELECT 1 FROM images WHERE id = ?'; 42 | return $this->connection->query($sql, [$id]) 43 | ->then( 44 | function (QueryResult $result) { 45 | return count($result->resultRows) ? reject() : resolve(); 46 | }, 47 | function (Exception $exception) { 48 | echo 'Error: ' . $exception->getMessage() . PHP_EOL; 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scraping-proxy/ScraperViaProxy.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 29 | $this->directory = $directory; 30 | $this->proxies = $proxies; 31 | } 32 | 33 | public function scrape(string ...$urls): void 34 | { 35 | foreach ($urls as $url) { 36 | $this->scraperForRandomProxy()->scrape($url); 37 | } 38 | } 39 | 40 | private function scraperForRandomProxy(): Scraper 41 | { 42 | $client = new Browser($this->loop, new Connector($this->loop, ['tcp' => $this->randomProxyClient()])); 43 | $downloader = new Downloader( 44 | $client->withOptions(['streaming' => true]), 45 | Filesystem::create($this->loop), 46 | $this->directory 47 | ); 48 | 49 | $storage = new \AsyncScraper\Storage($this->loop, 'root:@localhost/scraping?idle=0'); 50 | $crawler = new \AsyncScraper\Crawler($client); 51 | return new \AsyncScraper\Scraper($crawler, $downloader, $storage); 52 | } 53 | 54 | private function randomProxyClient(): SocksClient 55 | { 56 | $server = $this->proxies[array_rand($this->proxies)]; 57 | return new SocksClient($server, new Connector($this->loop)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scraping-proxy/src/ScraperViaProxy.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 29 | $this->directory = $directory; 30 | $this->proxies = $proxies; 31 | } 32 | 33 | public function scrape(string ...$urls): void 34 | { 35 | foreach ($urls as $url) { 36 | $this->scraperForRandomProxy()->scrape($url); 37 | } 38 | } 39 | 40 | private function scraperForRandomProxy(): Scraper 41 | { 42 | $client = new Browser($this->loop, new Connector($this->loop, ['tcp' => $this->randomProxyClient()])); 43 | $downloader = new Downloader( 44 | $client->withOptions(['streaming' => true]), 45 | Filesystem::create($this->loop), 46 | $this->directory 47 | ); 48 | 49 | $storage = new \AsyncScraper\Storage($this->loop, 'root:@localhost/scraping?idle=0'); 50 | $crawler = new \AsyncScraper\Crawler($client); 51 | return new \AsyncScraper\Scraper($crawler, $downloader, $storage); 52 | } 53 | 54 | private function randomProxyClient(): SocksClient 55 | { 56 | $server = $this->proxies[array_rand($this->proxies)]; 57 | return new SocksClient($server, new Connector($this->loop)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scraping-store-to-db/src/Storage.php: -------------------------------------------------------------------------------- 1 | connection = (new Factory($loop))->createLazyConnection($uri); 22 | } 23 | 24 | public function saveIfNotExist(Image ...$images): void 25 | { 26 | foreach ($images as $image) { 27 | $this->isNotStored($image->id) 28 | ->then(function () use ($image) { 29 | $this->save($image); 30 | }); 31 | } 32 | } 33 | 34 | private function save(Image $image): void 35 | { 36 | $sql = 'INSERT INTO images (id, title, tags, resolution, source) VALUES (?, ?, ?, ?, ?)'; 37 | $this->connection->query($sql, $image->toArray()) 38 | ->then(function (QueryResult $result) { 39 | var_dump($result); 40 | }, function (Exception $exception) { 41 | echo $exception->getMessage() . PHP_EOL; 42 | }); 43 | } 44 | 45 | private function isNotStored(int $id): PromiseInterface 46 | { 47 | $sql = 'SELECT 1 FROM images WHERE id = ?'; 48 | return $this->connection->query($sql, [$id]) 49 | ->then( 50 | function (QueryResult $result) { 51 | return count($result->resultRows) ? reject() : resolve(); 52 | }, 53 | function (Exception $exception) { 54 | echo 'Error: ' . $exception->getMessage() . PHP_EOL; 55 | } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /routing/server.php: -------------------------------------------------------------------------------- 1 | 'application/json'], json_encode($tasks)); 15 | }; 16 | 17 | $addNewTask = function (ServerRequestInterface $request) use (&$tasks) { 18 | $newTask = $request->getParsedBody()['task'] ?? null; 19 | if ($newTask) { 20 | $tasks[] = $newTask; 21 | return new Response(201); 22 | } 23 | 24 | return new Response( 25 | 400, ['Content-Type' => 'application/json'], json_encode(['error' => 'task field is required']) 26 | ); 27 | }; 28 | 29 | $viewTask = function (ServerRequestInterface $request, $id) use (&$tasks) { 30 | return isset($tasks[$id]) 31 | ? new Response(200, ['Content-Type' => 'application/json'], json_encode($tasks[$id])) 32 | : new Response(404); 33 | }; 34 | 35 | $dispatcher = \FastRoute\simpleDispatcher( 36 | function (\FastRoute\RouteCollector $routes) use ($listTasks, $addNewTask, $viewTask) { 37 | $routes->get('/tasks/{id:\d+}', $viewTask); 38 | $routes->get('/tasks', $listTasks); 39 | $routes->post('/tasks', $addNewTask); 40 | } 41 | ); 42 | 43 | $server = new \React\Http\Server( 44 | function (ServerRequestInterface $request) use ($dispatcher) { 45 | $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); 46 | 47 | switch ($routeInfo[0]) { 48 | case \FastRoute\Dispatcher::NOT_FOUND: 49 | return new Response(404); 50 | case \FastRoute\Dispatcher::FOUND: 51 | return $routeInfo[1]($request); 52 | } 53 | } 54 | ); 55 | 56 | $socket = new \React\Socket\Server('127.0.0.1:8000', $loop); 57 | 58 | $server->listen($socket); 59 | echo 'Listening on ' . str_replace('tcp', 'http', $socket->getAddress()) . PHP_EOL; 60 | $loop->run(); 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code samples for ReactPHP video tutorials 2 | 3 |
4 |
5 |