├── .gitignore ├── README.md ├── asynchronous-downloads ├── Downloader │ ├── Downloader.php │ └── PromiseBasedDownloader.php ├── composer.json ├── index.php └── quick_start.php ├── event-loop ├── block.php ├── composer.json ├── delay.php └── repeat.php ├── http ├── composer.json ├── posts.php └── server.php ├── logo.jpg ├── middleware ├── CustomHeader.php ├── Logging.php ├── composer.json ├── requests.http └── server.php ├── promises ├── composer.json └── promises.php ├── psr-15-middleware ├── composer.json └── server.php ├── routing ├── composer.json ├── server.php └── tasks.http ├── scraping-extract-data ├── composer.json ├── index.php ├── quick-start.php ├── quick_start.php └── src │ ├── Image.php │ └── Scraper.php ├── scraping-proxy ├── ScraperViaProxy.php ├── composer.json ├── downloads │ └── .gitkeep ├── index.php └── src │ ├── Crawler.php │ ├── Downloader.php │ ├── Image.php │ ├── Scraper.php │ ├── ScraperViaProxy.php │ └── Storage.php ├── scraping-save-files ├── composer.json ├── downloads │ └── .gitkeep ├── index.php └── src │ ├── Crawler.php │ ├── Downloader.php │ ├── Image.php │ ├── Scraper.php │ └── Storage.php ├── scraping-store-to-db ├── composer.json ├── index.php ├── quick_start.php └── src │ ├── Image.php │ ├── Scraper.php │ └── Storage.php ├── socket ├── ConnectionsPool.php ├── client.php ├── composer.json └── server.php └── streams ├── composer.json ├── readable.php └── writable.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | downloads 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code samples for ReactPHP video tutorials 2 | 3 |

4 | ReactPHP Code Tutorials 5 |

6 | 7 | Contents: 8 | 9 | - [Basics](#basics) 10 | - [Sockets](#sockets) 11 | - [HTTP Server](#http-server) 12 | 13 | ## Basics 14 | 1. Event Loop And Timers [Video](https://youtu.be/mJFbYHYxSDg) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/event-loop) 15 | 2. Streams [Video](https://youtu.be/k3Jqg3UNWrk) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/streams) 16 | 3. Promises [Video](https://youtu.be/_R3tHmhCAbw) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/promises) 17 | 18 | ## Sockets 19 | 4. Simple Chat With Sockets - Server [Video](https://youtu.be/xv1_IhT-kiM) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/socket) 20 | 5. Simple Chat With Sockets - Client [Video](https://youtu.be/zv4YcIo-lYk) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/socket) 21 | 6. Simple Chat With Sockets - Making it user-friendly [Video](https://youtu.be/VyUIVxdf7BU) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/socket) 22 | 23 | ## HTTP Server 24 | 7. HTTP Server - Simple HTTP Server [Video](https://youtu.be/iNH3CPZQ_Ms) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/http) 25 | 8. Query Strings [Video](https://youtu.be/NaMmxGJLaB4) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/http) 26 | 9. POST Requests [Video](https://youtu.be/q01-wCZukDk) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/http) 27 | 10. Middleware [Video](https://youtu.be/nRMlOrW2rwE) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/middleware) 28 | 11. Using PSR-15 Middleware [Video](https://youtu.be/nCys2W8wWcc) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/psr-15-middleware) 29 | 12. Routing [Video](https://youtu.be/qHV0GATf2zY) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/routing) 30 | 31 | ## Asynchronous HTTP requests 32 | 13. Asynchronous Downloads [Video](https://youtu.be/6Fw0IxK5h54) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/asynchronous-downloads) 33 | 34 | ## Fast Web Scraping With ReactPHP 35 | 1. Extracting Data From HTML [Video](https://youtu.be/_XYVP5fdt_0) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/scraping-extract-data) 36 | 2. Saving to MySQL [Video](https://youtu.be/rSexLp-subY) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/scraping-store-to-db) 37 | 3. Downloading Images [Video](https://youtu.be/t7iI8WLjirc) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/scraping-save-files) 38 | 4. Using proxy [Video](https://youtu.be/BYU_dHKBqdA) | [Code](https://github.com/seregazhuk/reactphp-video-tutorials/tree/master/scraping-proxy) 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /event-loop/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/event-loop": "^0.5.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /http/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /http/posts.php: -------------------------------------------------------------------------------- 1 | 'Post 1', 6 | 'body' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non dignissim massa. Donec massa tortor, consequat sed dolor ac, ullamcorper condimentum ipsum. Pellentesque euismod sed lectus convallis viverra. Phasellus eget blandit quam. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec risus ac ante malesuada feugiat at at felis. Vivamus justo massa, tincidunt fermentum nulla a, tempor porta ante. Quisque aliquam fermentum nisi in vestibulum. Sed lacinia placerat imperdiet. Aliquam erat volutpat. Aliquam id purus consequat, semper lacus vitae, maximus felis. Aliquam accumsan suscipit consequat. Curabitur in massa at urna condimentum sodales.', 7 | 'tags' => ['php', 'solid'], 8 | ], 9 | [ 10 | 'title' => 'Post 2', 11 | 'body' => 'Fusce id mauris tempor, rhoncus est sit amet, blandit ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce consequat enim quis augue condimentum condimentum. In eu libero diam. Praesent rhoncus orci eros, ut aliquam neque vulputate a. Curabitur malesuada suscipit placerat. In dignissim non arcu vitae condimentum. Proin vitae ipsum in est ullamcorper maximus placerat vitae quam. Fusce ante purus, lacinia vitae purus quis, semper ornare ligula. Sed purus nibh, posuere vel metus nec, scelerisque rutrum orci. Duis accumsan a sapien ac vulputate. Donec ultricies est vitae dui finibus, et semper lacus consequat. In tempus justo dui, ac tempor velit luctus tempus. Etiam non sodales ante. Fusce aliquet neque sit amet lectus tristique ultrices. Proin aliquet tempor gravida.', 12 | 'tags' => ['js', 'async'], 13 | ], 14 | [ 15 | 'title' => 'Post 3', 16 | 'body' => 'Praesent nulla sem, placerat commodo magna eget, luctus lacinia nunc. Vivamus ut neque nunc. Donec nunc urna, interdum eget justo et, fringilla tincidunt magna. Proin vel turpis ex. Integer sed diam id ex aliquet porta at ut ligula. Nullam euismod ultrices mi vitae laoreet. Donec urna leo, molestie quis pellentesque eu, ullamcorper et nunc. Vivamus quis facilisis enim, eget placerat ligula. In id libero nec leo malesuada commodo. Aenean auctor nibh et erat pulvinar porta.', 17 | 'tags' => ['mysql', 'database'], 18 | ], 19 | [ 20 | 'title' => 'Post 4', 21 | 'body' => 'Vivamus consectetur lacus in eros dictum, sit amet porttitor ante gravida. Duis risus nisl, vestibulum vel eros egestas, euismod rutrum libero. Nullam consequat dapibus finibus. Aenean eu justo nec tortor pulvinar suscipit eu quis libero. Morbi consectetur sodales odio, sit amet fringilla augue accumsan in. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam eu tempus ex. Nunc suscipit quam sit amet luctus scelerisque. In ullamcorper gravida urna, in interdum quam vulputate a. Donec elementum dui eu dignissim eleifend.', 22 | 'tags' => ['php', 'api'], 23 | ], 24 | [ 25 | 'title' => 'Post 5', 26 | 'body' => 'Quisque dapibus at nisi eu aliquam. Donec vitae feugiat leo. In accumsan ullamcorper ex interdum porta. Donec eros purus, bibendum ut consectetur fermentum, aliquam nec dolor. Mauris et finibus eros. Nam vitae ex in turpis pretium semper ut nec quam. Donec lobortis consequat fringilla. Ut arcu ligula, laoreet quis elementum vitae, interdum non augue. Pellentesque condimentum felis efficitur sem condimentum semper.', 27 | 'tags' => ['php', 'solid', 'oop'], 28 | ], 29 | [ 30 | 'title' => 'Post 6', 31 | 'body' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec iaculis pharetra tellus, at malesuada erat eleifend eget. Donec sed turpis at lorem posuere faucibus. Fusce porttitor felis eget lacinia dictum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ex libero, euismod quis risus quis, aliquet consequat turpis. In dictum diam in fringilla pellentesque. Quisque aliquet turpis quis neque fermentum, vitae efficitur neque fringilla. Nulla blandit lectus vel justo rutrum aliquet. Fusce odio ipsum, pulvinar quis vulputate eu, aliquet volutpat velit. Sed quis orci at sapien lobortis maximus sit amet vitae magna. Maecenas dapibus eget mi a tristique. Aenean consectetur fermentum massa, vel dapibus tellus imperdiet a.', 32 | 'tags' => ['js', 'angular'], 33 | ], 34 | [ 35 | 'title' => 'Post 7', 36 | 'body' => 'Vivamus molestie consequat laoreet. Sed sed purus ligula. Vivamus venenatis commodo erat eget scelerisque. Sed mollis quis justo at porttitor. Duis semper magna erat, at pharetra nunc tincidunt non. Proin egestas est et lorem venenatis viverra. Cras molestie nunc sed nulla malesuada, sed interdum libero aliquet. In hac habitasse platea dictumst. Aliquam quis magna at dui lacinia commodo sit amet id eros. Pellentesque nec lorem sem. Sed ac metus risus.', 37 | 'tags' => ['js', 'react'], 38 | ], 39 | [ 40 | 'title' => 'Post 8', 41 | 'body' => 'Phasellus sed efficitur ex. Aliquam id pharetra felis. Fusce facilisis lectus dui, eu convallis mauris sollicitudin in. Cras commodo, ex vitae finibus consequat, dolor massa tincidunt augue, sit amet hendrerit diam augue sit amet metus. Duis varius eleifend mi, nec dictum felis rhoncus ut. Integer dignissim rutrum porta. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin sed velit quis est tempor condimentum sed non massa. Morbi a eros sit amet ex rutrum iaculis. Quisque quis ullamcorper nisl, sit amet bibendum dolor. Fusce blandit tincidunt porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;', 42 | 'tags' => ['php', 'unit-testing', 'phpunit'], 43 | ], 44 | [ 45 | 'title' => 'Post 9', 46 | 'body' => 'Nulla vestibulum velit nec ullamcorper ultricies. Aenean volutpat dolor eu posuere molestie. Vestibulum feugiat, tortor at congue ultrices, mauris purus consequat est, sed ultricies nibh elit a elit. Suspendisse a eleifend diam. Nulla ut imperdiet felis, ac ultrices urna. Vestibulum pellentesque elit vitae augue luctus, sed congue mauris accumsan. Ut ultricies diam nunc, nec iaculis leo viverra in. Vivamus bibendum, lorem sit amet hendrerit faucibus, ex urna placerat ex, id tristique ligula leo nec magna. Quisque tempus consequat porta. Donec nec urna eu nulla pellentesque condimentum eget sed diam. Donec mauris libero, consectetur id velit a, interdum tempor sem.', 47 | 'tags' => ['php', 'oop', 'design patterns'], 48 | ], 49 | [ 50 | 'title' => 'Post 10', 51 | 'body' => 'Curabitur fermentum imperdiet euismod. Suspendisse bibendum consequat metus, et dignissim nisi. Nullam feugiat, odio in mattis ullamcorper, urna lacus vulputate ligula, ac suscipit ipsum metus quis nisl. Integer tempor imperdiet lobortis. Sed pulvinar lectus nec velit efficitur volutpat. Donec lacinia consequat diam. Suspendisse aliquet leo ac maximus sodales. Morbi a sapien et nisi mollis imperdiet.', 52 | 'tags' => ['php', 'oop', 'design patterns'], 53 | ], 54 | ]; 55 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregazhuk/reactphp-video-tutorials/4abb541b6261af72a85c869ff6e2b73a7a9f3d81/logo.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /middleware/Logging.php: -------------------------------------------------------------------------------- 1 | getMethod() . PHP_EOL; 10 | echo 'Path: ' . $request->getUri()->getPath() . PHP_EOL; 11 | 12 | return $next($request); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /middleware/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /middleware/requests.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8000 2 | 3 | ### 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /promises/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/promise": "^2.5.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /routing/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/http": "^0.8.3", 4 | "ext-json": "*", 5 | "nikic/fast-route": "^1.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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-extract-data/index.php: -------------------------------------------------------------------------------- 1 | scrape(...$urls)->then('print_r'); 15 | 16 | $loop->run(); 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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-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/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 | -------------------------------------------------------------------------------- /scraping-proxy/downloads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregazhuk/reactphp-video-tutorials/4abb541b6261af72a85c869ff6e2b73a7a9f3d81/scraping-proxy/downloads/.gitkeep -------------------------------------------------------------------------------- /scraping-proxy/index.php: -------------------------------------------------------------------------------- 1 | scrape(...$urls); 14 | $loop->run(); 15 | -------------------------------------------------------------------------------- /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-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-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-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-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-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/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 | -------------------------------------------------------------------------------- /scraping-save-files/downloads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregazhuk/reactphp-video-tutorials/4abb541b6261af72a85c869ff6e2b73a7a9f3d81/scraping-save-files/downloads/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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-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-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-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-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 | -------------------------------------------------------------------------------- /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-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-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-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 | -------------------------------------------------------------------------------- /socket/ConnectionsPool.php: -------------------------------------------------------------------------------- 1 | connections = new SplObjectStorage(); 13 | } 14 | 15 | public function add(ConnectionInterface $connection) 16 | { 17 | $connection->write((new Color("Welcome to chat\n"))->fg('green')); 18 | $connection->write('Enter your name: '); 19 | $this->initEvents($connection); 20 | $this->setConnectionData($connection, []); 21 | } 22 | 23 | /** 24 | * @param ConnectionInterface $connection 25 | */ 26 | private function initEvents(ConnectionInterface $connection) 27 | { 28 | // On receiving the data we loop through other connections 29 | // from the pool and write this data to them 30 | $connection->on('data', function ($data) use ($connection) { 31 | $connectionData = $this->getConnectionData($connection); 32 | // It is the first data received, so we consider it as 33 | // a users name. 34 | if(empty($connectionData)) { 35 | $this->addNewMember($data, $connection); 36 | return; 37 | } 38 | $name = $connectionData['name']; 39 | $this->sendAll((new Color("$name:"))->bold() ." $data", $connection); 40 | }); 41 | // When connection closes detach it from the pool 42 | $connection->on('close', function () use ($connection){ 43 | $data = $this->getConnectionData($connection); 44 | $name = $data['name'] ?? ''; 45 | $this->connections->offsetUnset($connection); 46 | $this->sendAll((new Color("User $name leaves the chat\n"))->fg('red'), $connection); 47 | }); 48 | } 49 | 50 | private function addNewMember(string $name, ConnectionInterface $connection) 51 | { 52 | $name = str_replace(["\n", "\r"], '', $name); 53 | $this->setConnectionData($connection, ['name' => $name]); 54 | $this->sendAll((new Color("User $name joins the chat\n"))->fg('blue'), $connection); 55 | } 56 | 57 | private function setConnectionData(ConnectionInterface $connection, $data) 58 | { 59 | $this->connections->offsetSet($connection, $data); 60 | } 61 | 62 | private function getConnectionData(ConnectionInterface $connection) 63 | { 64 | return $this->connections->offsetGet($connection); 65 | } 66 | 67 | /** 68 | * Send data to all connections from the pool except 69 | * the specified one. 70 | * 71 | * @param mixed $data 72 | * @param ConnectionInterface $except 73 | */ 74 | private function sendAll($data, ConnectionInterface $except) { 75 | foreach ($this->connections as $conn) { 76 | if ($conn != $except) { 77 | $conn->write($data); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /streams/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "react/stream": "^0.7.7" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /streams/writable.php: -------------------------------------------------------------------------------- 1 | pipe($toUpper)->pipe($writable); 16 | 17 | $loop->run(); 18 | 19 | --------------------------------------------------------------------------------