From da7bb290599734eb528106fb74e77ffa3c41fcfb Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Tue, 7 Jul 2020 19:12:03 -0500 Subject: [PATCH 01/11] Add .idea/ to gitignote --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index fb2c776..6ad404a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,8 @@ yarn-error.log .phpunit.result.cache /phpunit.xml ###< symfony/phpunit-bridge ### + +###> env/ide ### +.idea/ +###< env/ide ### + From a11004800cd0712ec6109c2890f56f8184f30cb8 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Thu, 16 Jul 2020 21:14:33 -0500 Subject: [PATCH 02/11] Create a commend to retrieve the latest meetup event --- .env.test | 1 + composer.json | 69 +++---- composer.lock | 145 +++++++------- config/services.yaml | 8 + .../PhpmxGetLastMeetupEventCommand.php | 91 +++++++++ src/Contracts/GetLastMeetupEventInterface.php | 10 + src/Entity/MeetupEvent.php | 177 ++++++++++++++++++ src/Migrations/Version20200717075051.php | 35 ++++ src/Repository/MeetupEventRepository.php | 50 +++++ .../GetLatestMeetupEventFromCrawler.php | 120 ++++++++++++ .../GetLatestMeetupEventCommandTest.php | 51 +++++ .../GetLatestMeetupEventFromCrawlerTest.php | 42 +++++ tests/fixtures/meetup_response_example.html | 81 ++++++++ 13 files changed, 781 insertions(+), 99 deletions(-) create mode 100644 src/Command/PhpmxGetLastMeetupEventCommand.php create mode 100644 src/Contracts/GetLastMeetupEventInterface.php create mode 100644 src/Entity/MeetupEvent.php create mode 100644 src/Migrations/Version20200717075051.php create mode 100644 src/Repository/MeetupEventRepository.php create mode 100644 src/Service/GetLatestMeetupEventFromCrawler.php create mode 100644 tests/Functional/Command/GetLatestMeetupEventCommandTest.php create mode 100644 tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php create mode 100644 tests/fixtures/meetup_response_example.html diff --git a/.env.test b/.env.test index a655f69..ab94515 100644 --- a/.env.test +++ b/.env.test @@ -4,3 +4,4 @@ APP_SECRET='$ecretf0rt3st' SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther DATABASE_URL=mysql://root:toor@db:3306/phpmx +MEETUP_EVENTS_URL='tests/fixtures/meetup_response_example.html' diff --git a/composer.json b/composer.json index 108ef27..69ef812 100644 --- a/composer.json +++ b/composer.json @@ -4,40 +4,41 @@ "type": "project", "license": "proprietary", "require": { - "php": "^7.1.3", - "ext-ctype": "*", - "ext-iconv": "*", - "dms/meetup-api-client": "^2.3", - "doctrine/doctrine-migrations-bundle": "^2.0", - "easycorp/easyadmin-bundle": "^2.1", - "friendsofsymfony/ckeditor-bundle": "^2.1", - "google/recaptcha": "^1.2", - "gravatarphp/gravatar": "^2.0", - "jaybizzle/crawler-detect": "^1.2", - "mattketmo/email-checker": "^1.5", - "php-http/guzzle6-adapter": "^2.0", - "php-http/httplug-bundle": "^1.16", - "predis/predis": "^1.1", - "presta/sitemap-bundle": "^1.7", - "sensio/framework-extra-bundle": "^5.3", - "snc/redis-bundle": "^2.1", - "symfony/cache": "4.4.*", - "symfony/config": "4.4.*", - "symfony/console": "4.4.*", - "symfony/dotenv": "4.4.*", - "symfony/flex": "^1.1", - "symfony/framework-bundle": "4.4.*", - "symfony/mercure-bundle": "^0.1.1", - "symfony/monolog-bundle": "^3.4", - "symfony/security-csrf": "4.2.*", - "symfony/swiftmailer-bundle": "^3.2", - "symfony/templating": "4.4.*", - "symfony/twig-bundle": "4.4.*", - "symfony/web-link": "4.4.*", - "symfony/webpack-encore-bundle": "^1.6", - "symfony/yaml": "4.4.*", - "wrapi/slack": "^1.0" - }, + "php": "^7.1.3", + "ext-ctype": "*", + "ext-iconv": "*", + "dms/meetup-api-client": "^2.3", + "doctrine/doctrine-migrations-bundle": "^2.0", + "easycorp/easyadmin-bundle": "^2.1", + "friendsofsymfony/ckeditor-bundle": "^2.1", + "google/recaptcha": "^1.2", + "gravatarphp/gravatar": "^2.0", + "jaybizzle/crawler-detect": "^1.2", + "mattketmo/email-checker": "^1.5", + "php-http/guzzle6-adapter": "^2.0", + "php-http/httplug-bundle": "^1.16", + "predis/predis": "^1.1", + "presta/sitemap-bundle": "^1.7", + "sensio/framework-extra-bundle": "^5.3", + "snc/redis-bundle": "^2.1", + "symfony/cache": "4.4.*", + "symfony/config": "4.4.*", + "symfony/console": "4.4.*", + "symfony/dom-crawler": "4.4.*", + "symfony/dotenv": "4.4.*", + "symfony/flex": "^1.1", + "symfony/framework-bundle": "4.4.*", + "symfony/mercure-bundle": "^0.1.1", + "symfony/monolog-bundle": "^3.4", + "symfony/security-csrf": "4.2.*", + "symfony/swiftmailer-bundle": "^3.2", + "symfony/templating": "4.4.*", + "symfony/twig-bundle": "4.4.*", + "symfony/web-link": "4.4.*", + "symfony/webpack-encore-bundle": "^1.6", + "symfony/yaml": "4.4.*", + "wrapi/slack": "^1.0" + }, "config": { "preferred-install": { "*": "dist" diff --git a/composer.lock b/composer.lock index bb2efc0..e46ad40 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a5936512b2f96ffb6d43bcdf7e979d21", + "content-hash": "a99a65a24f98b480e1905752266467b6", "packages": [ { "name": "clue/stream-filter", @@ -2230,12 +2230,12 @@ "version": "v2.1.3", "source": { "type": "git", - "url": "https://github.com/whiteoctober/Pagerfanta.git", + "url": "https://github.com/BabDev/Pagerfanta.git", "reference": "a53ff01d521648d9dbca19b93ac6bc75a59b0972" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/whiteoctober/Pagerfanta/zipball/a53ff01d521648d9dbca19b93ac6bc75a59b0972", + "url": "https://api.github.com/repos/BabDev/Pagerfanta/zipball/a53ff01d521648d9dbca19b93ac6bc75a59b0972", "reference": "a53ff01d521648d9dbca19b93ac6bc75a59b0972", "shasum": "" }, @@ -4248,6 +4248,81 @@ "homepage": "https://symfony.com", "time": "2020-04-12T16:45:36+00:00" }, + { + "name": "symfony/dom-crawler", + "version": "v4.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c18354d5a0bb84c945f6257c51b971d52f10c614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c18354d5a0bb84c945f6257c51b971d52f10c614", + "reference": "c18354d5a0bb84c945f6257c51b971d52f10c614", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-23T00:03:06+00:00" + }, { "name": "symfony/dotenv", "version": "v4.4.8", @@ -7907,67 +7982,6 @@ "description": "A debug pack for Symfony projects", "time": "2020-04-07T10:08:51+00:00" }, - { - "name": "symfony/dom-crawler", - "version": "v4.4.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4d0fb3374324071ecdd94898367a3fa4b5563162", - "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "masterminds/html5": "<2.6" - }, - "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2020-03-29T19:12:22+00:00" - }, { "name": "symfony/maker-bundle", "version": "v1.16.0", @@ -8314,5 +8328,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/config/services.yaml b/config/services.yaml index 27b7b05..8ab3906 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -9,6 +9,7 @@ parameters: SLACK_TOKEN: '%env(SLACK_TOKEN)%' SLACK_TEAMID: '%env(SLACK_TEAMID)%' google_recaptcha_site_key: '%env(GOOGLE_RECAPTCHA_SITE_KEY)%' + meetup_events_url: '%env(MEETUP_EVENTS_URL)%' services: # default configuration for services in *this* file @@ -36,6 +37,13 @@ services: tags: - { name: 'console.command', command: 'phpmx:slack:sync' } + App\Service\GetLatestMeetupEventFromCrawler: + class: App\Service\GetLatestMeetupEventFromCrawler + arguments: + - '%meetup_events_url%' + + App\Contracts\GetLastMeetupEventInterface: '@App\Service\GetLatestMeetupEventFromCrawler' + App\EventListener\SendLoginEmail: tags: - { name: doctrine.event_subscriber, event: postPersist } diff --git a/src/Command/PhpmxGetLastMeetupEventCommand.php b/src/Command/PhpmxGetLastMeetupEventCommand.php new file mode 100644 index 0000000..4001d3a --- /dev/null +++ b/src/Command/PhpmxGetLastMeetupEventCommand.php @@ -0,0 +1,91 @@ +getLastMeetupEvent = $getLastMeetupEvent; + $this->em = $em; + } + + protected function configure() + { + $this->setDescription('Get the latest event from the meetup page.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $latestEvent = $this->getLastMeetupEvent->handle(); + + if (!$latestEvent) { + $io->warning('Command executed but no event was retrieved.'); + + return self::SUCCESSFUL_EXECUTED_STATUS; + } + + /** @var MeetupEvent $cachedEvent */ + $cachedEvent = $this->em->getRepository(MeetupEvent::class) + ->findOneBy([ + 'meetupId' => $latestEvent->getMeetupId(), + ]); + + if ($cachedEvent) { + $cachedEvent + ->setMeetupId($latestEvent->getMeetupId()) + ->setTitle($latestEvent->getTitle()) + ->setScheduledAt($latestEvent->getScheduledAt()) + ->setPlace($latestEvent->getPlace()) + ->setDescription($latestEvent->getDescription()) + ->setAttendingCount($latestEvent->getAttendingCount()) + ->setSpeaker($latestEvent->getSpeaker()) + ->setUrl($latestEvent->getUrl()); + + $this->em->persist($cachedEvent); + $this->em->flush(); + + $io->success( + sprintf( + 'Cached event was updated successfully, details: %s', + $cachedEvent + ) + ); + + return self::SUCCESSFUL_EXECUTED_STATUS; + } + + $this->em->persist($latestEvent); + $this->em->flush(); + + $io->success( + sprintf( + 'Event retrieved and persisted, details: %s.', + $latestEvent + ) + ); + + return self::SUCCESSFUL_EXECUTED_STATUS; + } +} diff --git a/src/Contracts/GetLastMeetupEventInterface.php b/src/Contracts/GetLastMeetupEventInterface.php new file mode 100644 index 0000000..7a4c1ae --- /dev/null +++ b/src/Contracts/GetLastMeetupEventInterface.php @@ -0,0 +1,10 @@ +id; + } + + public function getMeetupId(): ?int + { + return $this->meetupId; + } + + public function setMeetupId(int $meetupId): self + { + $this->meetupId = $meetupId; + + return $this; + } + + public function getScheduledAt(): ?\DateTimeInterface + { + return $this->scheduledAt; + } + + public function setScheduledAt(\DateTimeInterface $scheduledAt): self + { + $this->scheduledAt = $scheduledAt; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getPlace(): ?string + { + return $this->place; + } + + public function setPlace(?string $place): self + { + $this->place = $place; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } + + public function getAttendingCount(): ?int + { + return $this->attendingCount; + } + + public function setAttendingCount(?int $attendingCount): self + { + $this->attendingCount = $attendingCount; + + return $this; + } + + public function getSpeaker(): ?string + { + return $this->speaker; + } + + public function setSpeaker(?string $speaker): self + { + $this->speaker = $speaker; + + return $this; + } + + public function getUrl(): ?string + { + return $this->url; + } + + public function setUrl(string $url): self + { + $this->url = $url; + + return $this; + } + + public function __toString(): string + { + $attributes = [ + 'id: '.$this->id, + 'meetupId: '.$this->meetupId, + 'scheduledAt: '.$this->scheduledAt->format('Y-m-d H:i:s'), + 'title: '.$this->title, + 'place: '.$this->place, + 'description: '.$this->description, + 'attendingCount: '.$this->attendingCount, + 'speaker: '.$this->speaker, + 'url: '.$this->url, + ]; + + return sprintf('[%s]', implode(',', $attributes)); + } +} diff --git a/src/Migrations/Version20200717075051.php b/src/Migrations/Version20200717075051.php new file mode 100644 index 0000000..f91d349 --- /dev/null +++ b/src/Migrations/Version20200717075051.php @@ -0,0 +1,35 @@ +abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE meetup_event (id INT AUTO_INCREMENT NOT NULL, meetup_id INT NOT NULL, scheduled_at DATETIME NOT NULL, title VARCHAR(255) NOT NULL, place VARCHAR(255) DEFAULT NULL, description LONGTEXT DEFAULT NULL, attending_count INT DEFAULT NULL, speaker VARCHAR(255) DEFAULT NULL, url VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('DROP TABLE meetup_event'); + } +} diff --git a/src/Repository/MeetupEventRepository.php b/src/Repository/MeetupEventRepository.php new file mode 100644 index 0000000..4eda2aa --- /dev/null +++ b/src/Repository/MeetupEventRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('m') + ->andWhere('m.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('m.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?MeetupEvent + { + return $this->createQueryBuilder('m') + ->andWhere('m.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Service/GetLatestMeetupEventFromCrawler.php b/src/Service/GetLatestMeetupEventFromCrawler.php new file mode 100644 index 0000000..e4b4dab --- /dev/null +++ b/src/Service/GetLatestMeetupEventFromCrawler.php @@ -0,0 +1,120 @@ +meetupEventsUrl = $meetupEventsUrl; + } + + public function handle(): MeetupEvent + { + $html = $this->getHtml(); + $crawler = new Crawler($html); + $eventListNode = $crawler->filter('.eventList-list'); + + $event = new MeetupEvent(); + $event + ->setMeetupId($this->crawlId($eventListNode)) + ->setTitle($this->crawlTitle($eventListNode)) + ->setScheduledAt($this->crawlTimestamp($eventListNode)) + ->setPlace($this->crawlAddress($eventListNode)) + ->setDescription($this->crawlDescription($eventListNode)) + ->setAttendingCount($this->crawlAttendingCount($eventListNode)) + ->setSpeaker($this->crawlSpeaker($eventListNode)) + ->setUrl($this->meetupEventsUrl.$event->getMeetupId()); + + return $event; + } + + /** + * @throws Exception + */ + private function getHtml(): string + { + if (!file_exists($this->meetupEventsUrl)) { + throw new Exception(sprintf('Unable to read the provided source %s.', $this->meetupEventsUrl)); + } + + return file_get_contents($this->meetupEventsUrl); + } + + private function crawlId(Crawler $eventListNode): int + { + $composedId = $eventListNode->filter('.eventCard') + ->eq(0) + ->attr('id'); + + list(, $id) = explode('-', $composedId); + + return intval($id); + } + + private function crawlTimestamp(Crawler $eventListNode): DateTime + { + $time = $eventListNode->filter('.eventTimeDisplay') + ->eq(0) + ->filter('time') + ->eq(0) + ->attr('datetime'); + + $eventDate = new DateTime(); + + return $eventDate->setTimestamp($time / 1000); + } + + private function crawlTitle(Crawler $eventListNode): string + { + return $eventListNode->filter('.eventCardHead--title') + ->eq(0) + ->text(); + } + + private function crawlAddress(Crawler $eventListNode): string + { + return $eventListNode->filter('address') + ->eq(0) + ->filter('p') + ->eq(0) + ->text(); + } + + private function crawlDescription(Crawler $eventListNode): string + { + return $eventListNode->filter('.eventCard') + ->eq(0) + ->filter('.flex-item--shrink') + ->eq(2) + ->filter('p') + ->eq(1) + ->text(); + } + + private function crawlAttendingCount(Crawler $eventListNode): int + { + $attendingCount = $eventListNode->filter('.avatarRow--attendingCount') + ->eq(0) + ->filter('span') + ->eq(0) + ->text(); + + return intval($attendingCount); + } + + private function crawlSpeaker(Crawler $eventListNode): string + { + // TODO: Implement this method when we got the filter selector + return ''; + } +} diff --git a/tests/Functional/Command/GetLatestMeetupEventCommandTest.php b/tests/Functional/Command/GetLatestMeetupEventCommandTest.php new file mode 100644 index 0000000..c460821 --- /dev/null +++ b/tests/Functional/Command/GetLatestMeetupEventCommandTest.php @@ -0,0 +1,51 @@ +find('phpmx:meetup:last-event'); + $commandTester = new CommandTester($command); + $commandTester->execute([ + '--env' => 'test', + ]); + $output = $commandTester->getDisplay(); + $this->assertStringContainsString( + 'Event retrieved and persisted', + $output + ); + } + + /** + * @test + * @depends commandShouldCreateARecord + */ + public function commandShouldUpdateARecord(): void + { + $kernel = static::createKernel(); + $application = new Application($kernel); + + $command = $application->find('phpmx:meetup:last-event'); + $commandTester = new CommandTester($command); + $commandTester->execute([ + '--env' => 'test', + ]); + $output = $commandTester->getDisplay(); + $this->assertStringContainsString( + 'Cached event was updated successfully', + $output + ); + } +} diff --git a/tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php b/tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php new file mode 100644 index 0000000..2734fa1 --- /dev/null +++ b/tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php @@ -0,0 +1,42 @@ +handle(); + + $this->assertSame($meetupEvent->getMeetupId(), 123456); + $this->assertSame($meetupEvent->getUrl(), $source. 123456); + $this->assertSame($meetupEvent->getTitle(), 'El título del meetup'); + $this->assertSame($meetupEvent->getAttendingCount(), 50); + $this->assertSame($meetupEvent->getDescription(), 'La decripción del meetup'); + $this->assertSame( + '2020-07-22 14:00:00', + $meetupEvent->getScheduledAt()->format('Y-m-d H:i:s') + ); + } + + /** + * @test + */ + public function theServiceShouldFailIfSourceIsWrong(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Unable to read the provided source test.'); + + $getLatestEvent = new GetLatestMeetupEventFromCrawler('test'); + $getLatestEvent->handle(); + } +} diff --git a/tests/fixtures/meetup_response_example.html b/tests/fixtures/meetup_response_example.html new file mode 100644 index 0000000..2c35516 --- /dev/null +++ b/tests/fixtures/meetup_response_example.html @@ -0,0 +1,81 @@ + From 1671fbb6f3bac067d73bb7a0c012a90c1025d170 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Fri, 17 Jul 2020 07:25:20 -0500 Subject: [PATCH 03/11] Fix timezone difference between the pipelines and the current project --- .../Functional/Command/GetLatestMeetupEventCommandTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Functional/Command/GetLatestMeetupEventCommandTest.php b/tests/Functional/Command/GetLatestMeetupEventCommandTest.php index c460821..7689492 100644 --- a/tests/Functional/Command/GetLatestMeetupEventCommandTest.php +++ b/tests/Functional/Command/GetLatestMeetupEventCommandTest.php @@ -8,6 +8,13 @@ class GetLatestMeetupEventCommandTest extends KernelTestCase { + public function setUp(): void + { + parent::setUp(); + + date_default_timezone_set('America/Mexico_City'); + } + /** * @test */ From 4c7e7aac4ac0a00b04a8b9f20b4ebc6a2aa84dec Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Fri, 17 Jul 2020 11:29:37 -0500 Subject: [PATCH 04/11] Add CrawlNode service to hande crawler exceptions --- config/services.yaml | 2 + src/Entity/MeetupEvent.php | 5 +- src/Service/CrawlNode.php | 47 +++++++ .../GetLatestMeetupEventFromCrawler.php | 126 ++++++++++++------ .../GetLatestMeetupEventFromCrawlerTest.php | 31 ++--- tests/Unit/Service/CrawlNodeTest.php | 64 +++++++++ 6 files changed, 213 insertions(+), 62 deletions(-) create mode 100644 src/Service/CrawlNode.php rename tests/{Unit => Functional}/Service/GetLatestMeetupEventFromCrawlerTest.php (56%) create mode 100644 tests/Unit/Service/CrawlNodeTest.php diff --git a/config/services.yaml b/config/services.yaml index 8ab3906..94d83f2 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -38,8 +38,10 @@ services: - { name: 'console.command', command: 'phpmx:slack:sync' } App\Service\GetLatestMeetupEventFromCrawler: + public: true class: App\Service\GetLatestMeetupEventFromCrawler arguments: + - '@App\Service\CrawlNode' - '%meetup_events_url%' App\Contracts\GetLastMeetupEventInterface: '@App\Service\GetLatestMeetupEventFromCrawler' diff --git a/src/Entity/MeetupEvent.php b/src/Entity/MeetupEvent.php index 0c60753..dcf4597 100644 --- a/src/Entity/MeetupEvent.php +++ b/src/Entity/MeetupEvent.php @@ -3,6 +3,7 @@ namespace App\Entity; use App\Repository\MeetupEventRepository; +use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; /** @@ -74,12 +75,12 @@ public function setMeetupId(int $meetupId): self return $this; } - public function getScheduledAt(): ?\DateTimeInterface + public function getScheduledAt(): ?DateTimeInterface { return $this->scheduledAt; } - public function setScheduledAt(\DateTimeInterface $scheduledAt): self + public function setScheduledAt(DateTimeInterface $scheduledAt): self { $this->scheduledAt = $scheduledAt; diff --git a/src/Service/CrawlNode.php b/src/Service/CrawlNode.php new file mode 100644 index 0000000..c04c4ee --- /dev/null +++ b/src/Service/CrawlNode.php @@ -0,0 +1,47 @@ +logger = $logger; + } + + /** + * @param mixed $default + * + * @return mixed + */ + public function handle(callable $nodeFinder, $default, string $errorMessage = '') + { + try { + return call_user_func($nodeFinder); + } catch (Exception $exception) { + $this->logger->warning('CrawlNode@handle', [ + 'message' => $this->getErrorMessage($errorMessage), + 'exception' => $exception, + ]); + } + + return $default; + } + + private function getErrorMessage(string $errorMessage): string + { + if ('' !== $errorMessage) { + return $errorMessage; + } + + return self::DEFAULT_MESSAGE; + } +} diff --git a/src/Service/GetLatestMeetupEventFromCrawler.php b/src/Service/GetLatestMeetupEventFromCrawler.php index e4b4dab..4955806 100644 --- a/src/Service/GetLatestMeetupEventFromCrawler.php +++ b/src/Service/GetLatestMeetupEventFromCrawler.php @@ -10,11 +10,15 @@ class GetLatestMeetupEventFromCrawler implements GetLastMeetupEventInterface { + /** @var CrawlNode */ + private $crawlNode; + /** @var string */ private $meetupEventsUrl; - public function __construct(string $meetupEventsUrl) + public function __construct(CrawlNode $crawlNode, string $meetupEventsUrl) { + $this->crawlNode = $crawlNode; $this->meetupEventsUrl = $meetupEventsUrl; } @@ -28,8 +32,8 @@ public function handle(): MeetupEvent $event ->setMeetupId($this->crawlId($eventListNode)) ->setTitle($this->crawlTitle($eventListNode)) - ->setScheduledAt($this->crawlTimestamp($eventListNode)) - ->setPlace($this->crawlAddress($eventListNode)) + ->setScheduledAt($this->crawlScheduledAt($eventListNode)) + ->setPlace($this->crawlPlace($eventListNode)) ->setDescription($this->crawlDescription($eventListNode)) ->setAttendingCount($this->crawlAttendingCount($eventListNode)) ->setSpeaker($this->crawlSpeaker($eventListNode)) @@ -52,64 +56,100 @@ private function getHtml(): string private function crawlId(Crawler $eventListNode): int { - $composedId = $eventListNode->filter('.eventCard') - ->eq(0) - ->attr('id'); - - list(, $id) = explode('-', $composedId); - - return intval($id); + return $this->crawlNode->handle( + function () use ($eventListNode) { + $composedId = $eventListNode->filter('.eventCard') + ->eq(0) + ->attr('id'); + + list(, $id) = explode('-', $composedId); + + return intval($id); + }, + 0, + 'Unable to find the meetupId element.' + ); } - private function crawlTimestamp(Crawler $eventListNode): DateTime + private function crawlScheduledAt(Crawler $eventListNode): DateTime { - $time = $eventListNode->filter('.eventTimeDisplay') - ->eq(0) - ->filter('time') - ->eq(0) - ->attr('datetime'); - - $eventDate = new DateTime(); - - return $eventDate->setTimestamp($time / 1000); + return $this->crawlNode->handle( + function () use ($eventListNode) { + $time = $eventListNode->filter('.eventTimeDisplay') + ->eq(0) + ->filter('time') + ->eq(0) + ->attr('datetime'); + + $eventDate = new DateTime(); + + return $eventDate->setTimestamp($time / 1000); + }, + new DateTime(), + 'Unable to find the scheduled at element.' + ); } private function crawlTitle(Crawler $eventListNode): string { - return $eventListNode->filter('.eventCardHead--title') - ->eq(0) - ->text(); + return $this->crawlNode->handle( + function () use ($eventListNode) { + return $eventListNode->filter('.eventCardHead--title') + ->eq(0) + ->text(); + }, + '', + 'Unable to find the title element.' + ); } - private function crawlAddress(Crawler $eventListNode): string + private function crawlPlace(Crawler $eventListNode): string { - return $eventListNode->filter('address') - ->eq(0) - ->filter('p') - ->eq(0) - ->text(); + return $this->crawlNode->handle( + function () use ($eventListNode) { + return $eventListNode->filter('address') + ->eq(0) + ->filter('p') + ->eq(0) + ->text(); + }, + '', + 'Unable to find the place element.' + ); } private function crawlDescription(Crawler $eventListNode): string { - return $eventListNode->filter('.eventCard') - ->eq(0) - ->filter('.flex-item--shrink') - ->eq(2) - ->filter('p') - ->eq(1) - ->text(); + return $this->crawlNode->handle( + function () use ($eventListNode) { + return $eventListNode->filter('.eventCard') + ->eq(0) + ->filter('.flex-item--shrink') + ->eq(2) + ->filter('p') + ->eq(1) + ->text(); + }, + '', + 'Unable to find the description element.' + ); } private function crawlAttendingCount(Crawler $eventListNode): int { - $attendingCount = $eventListNode->filter('.avatarRow--attendingCount') - ->eq(0) - ->filter('span') - ->eq(0) - ->text(); - - return intval($attendingCount); + return $this->crawlNode->handle( + function () use ($eventListNode) { + $attendingCount = $eventListNode->filter('.avatarRow--attendingCount') + ->eq(0) + ->filter('span') + ->eq(0) + ->text(); + + return intval($attendingCount); + }, + 0, + 'Unable to find the attending count element.' + ); } private function crawlSpeaker(Crawler $eventListNode): string diff --git a/tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php b/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php similarity index 56% rename from tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php rename to tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php index 2734fa1..4a146ef 100644 --- a/tests/Unit/Service/GetLatestMeetupEventFromCrawlerTest.php +++ b/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php @@ -3,18 +3,27 @@ namespace App\Tests\Unit\Service; use App\Service\GetLatestMeetupEventFromCrawler; -use Exception; -use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -class GetLatestMeetupEventFromCrawlerTest extends TestCase +class GetLatestMeetupEventFromCrawlerTest extends WebTestCase { + protected function setUp(): void + { + parent::setUp(); + + $kernel = static::createKernel(); + $kernel->boot(); + + self::$container = $kernel->getContainer(); + } + /** * @test */ public function theServiceShouldCrawlTheEvent(): void { - $source = __DIR__.'/../../fixtures/meetup_response_example.html'; - $getLatestEvent = new GetLatestMeetupEventFromCrawler($source); + $source = self::$container->getParameter('meetup_events_url'); + $getLatestEvent = self::$container->get(GetLatestMeetupEventFromCrawler::class); $meetupEvent = $getLatestEvent->handle(); $this->assertSame($meetupEvent->getMeetupId(), 123456); @@ -27,16 +36,4 @@ public function theServiceShouldCrawlTheEvent(): void $meetupEvent->getScheduledAt()->format('Y-m-d H:i:s') ); } - - /** - * @test - */ - public function theServiceShouldFailIfSourceIsWrong(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Unable to read the provided source test.'); - - $getLatestEvent = new GetLatestMeetupEventFromCrawler('test'); - $getLatestEvent->handle(); - } } diff --git a/tests/Unit/Service/CrawlNodeTest.php b/tests/Unit/Service/CrawlNodeTest.php new file mode 100644 index 0000000..d07d2c0 --- /dev/null +++ b/tests/Unit/Service/CrawlNodeTest.php @@ -0,0 +1,64 @@ +createMock(LoggerInterface::class); + $this->crawlNode = new CrawlNode($loggerMockup); + + $html = file_get_contents(__DIR__.'/../../fixtures/meetup_response_example.html'); + $this->node = new Crawler($html); + } + + /** + * @test + */ + public function handleShouldGetTheCrawledData(): void + { + $nodeContent = $this->crawlNode->handle( + function () { + return $this-> + node + ->filter('.eventCard--link') + ->eq(0) + ->attr('href'); + }, + 'default' + ); + + $this->assertSame('/PHP-The-Right-Way/events/123456/', $nodeContent); + } + + /** + * @test + */ + public function handleShouldReturnTheDefaultIfTheNodeDoesNotExists(): void + { + $nodeContent = $this->crawlNode->handle( + function () { + $this->node->filter('eventCard') + ->eq(0) + ->attr('id'); + }, + 'default' + ); + + $this->assertSame('default', $nodeContent); + } +} From 950b2a63c2e9d6d054c1a096d8a5ec952f0e466e Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Fri, 17 Jul 2020 11:33:15 -0500 Subject: [PATCH 05/11] Remove unused MeetupEvent repository methods --- src/Repository/MeetupEventRepository.php | 29 ------------------------ 1 file changed, 29 deletions(-) diff --git a/src/Repository/MeetupEventRepository.php b/src/Repository/MeetupEventRepository.php index 4eda2aa..2bb709a 100644 --- a/src/Repository/MeetupEventRepository.php +++ b/src/Repository/MeetupEventRepository.php @@ -18,33 +18,4 @@ public function __construct(ManagerRegistry $registry) { parent::__construct($registry, MeetupEvent::class); } - - // /** - // * @return MeetupEvent[] Returns an array of MeetupEvent objects - // */ - /* - public function findByExampleField($value) - { - return $this->createQueryBuilder('m') - ->andWhere('m.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('m.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; - } - */ - - /* - public function findOneBySomeField($value): ?MeetupEvent - { - return $this->createQueryBuilder('m') - ->andWhere('m.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; - } - */ } From 350ca873e00bd68be3f3a504fe8159f07b2a077b Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Mon, 27 Jul 2020 18:24:17 -0500 Subject: [PATCH 06/11] Fix event description typo on mock/test --- .../Functional/Service/GetLatestMeetupEventFromCrawlerTest.php | 2 +- tests/fixtures/meetup_response_example.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php b/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php index 4a146ef..5242ce0 100644 --- a/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php +++ b/tests/Functional/Service/GetLatestMeetupEventFromCrawlerTest.php @@ -30,7 +30,7 @@ public function theServiceShouldCrawlTheEvent(): void $this->assertSame($meetupEvent->getUrl(), $source. 123456); $this->assertSame($meetupEvent->getTitle(), 'El título del meetup'); $this->assertSame($meetupEvent->getAttendingCount(), 50); - $this->assertSame($meetupEvent->getDescription(), 'La decripción del meetup'); + $this->assertSame($meetupEvent->getDescription(), 'La descripción del meetup'); $this->assertSame( '2020-07-22 14:00:00', $meetupEvent->getScheduledAt()->format('Y-m-d H:i:s') diff --git a/tests/fixtures/meetup_response_example.html b/tests/fixtures/meetup_response_example.html index 2c35516..9657e8c 100644 --- a/tests/fixtures/meetup_response_example.html +++ b/tests/fixtures/meetup_response_example.html @@ -42,7 +42,7 @@

 
 
 

-

La decripción del meetup

+

La descripción del meetup

From a6e46b6e1f2825f241a4c78df5c47a1ddca47afc Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Mon, 27 Jul 2020 21:35:56 -0500 Subject: [PATCH 07/11] Use constant as default value and remove getMessage method --- src/Service/CrawlNode.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Service/CrawlNode.php b/src/Service/CrawlNode.php index c04c4ee..5999925 100644 --- a/src/Service/CrawlNode.php +++ b/src/Service/CrawlNode.php @@ -22,26 +22,17 @@ public function __construct(LoggerInterface $logger) * * @return mixed */ - public function handle(callable $nodeFinder, $default, string $errorMessage = '') + public function handle(callable $nodeFinder, $default, string $errorMessage = self::DEFAULT_MESSAGE) { try { return call_user_func($nodeFinder); } catch (Exception $exception) { $this->logger->warning('CrawlNode@handle', [ - 'message' => $this->getErrorMessage($errorMessage), + 'message' => $errorMessage, 'exception' => $exception, ]); } return $default; } - - private function getErrorMessage(string $errorMessage): string - { - if ('' !== $errorMessage) { - return $errorMessage; - } - - return self::DEFAULT_MESSAGE; - } } From da12d07fa726f10ba7087f431acf489688a76fa7 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Mon, 27 Jul 2020 21:49:01 -0500 Subject: [PATCH 08/11] Add unique constraint to the meetupId --- src/Entity/MeetupEvent.php | 2 +- src/Migrations/Version20200728024807.php | 35 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/Migrations/Version20200728024807.php diff --git a/src/Entity/MeetupEvent.php b/src/Entity/MeetupEvent.php index dcf4597..b1c9b52 100644 --- a/src/Entity/MeetupEvent.php +++ b/src/Entity/MeetupEvent.php @@ -19,7 +19,7 @@ class MeetupEvent private $id; /** - * @ORM\Column(type="integer") + * @ORM\Column(type="integer", unique=true) */ private $meetupId; diff --git a/src/Migrations/Version20200728024807.php b/src/Migrations/Version20200728024807.php new file mode 100644 index 0000000..5ebf871 --- /dev/null +++ b/src/Migrations/Version20200728024807.php @@ -0,0 +1,35 @@ +abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE UNIQUE INDEX UNIQ_7EA6C459591E2316 ON meetup_event (meetup_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('DROP INDEX UNIQ_7EA6C459591E2316 ON meetup_event'); + } +} From 92807360bf7eed00d7e43137c57adfc091d24816 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Tue, 28 Jul 2020 19:08:53 -0500 Subject: [PATCH 09/11] Add missing image field and fix description for all events --- .../PhpmxGetLastMeetupEventCommand.php | 1 + src/Entity/MeetupEvent.php | 18 ++++++++++ src/Migrations/Version20200728231947.php | 35 +++++++++++++++++++ .../GetLatestMeetupEventFromCrawler.php | 28 ++++++++++++--- tests/fixtures/meetup_response_example.html | 1 + 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/Migrations/Version20200728231947.php diff --git a/src/Command/PhpmxGetLastMeetupEventCommand.php b/src/Command/PhpmxGetLastMeetupEventCommand.php index 4001d3a..d5c4e35 100644 --- a/src/Command/PhpmxGetLastMeetupEventCommand.php +++ b/src/Command/PhpmxGetLastMeetupEventCommand.php @@ -59,6 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ->setScheduledAt($latestEvent->getScheduledAt()) ->setPlace($latestEvent->getPlace()) ->setDescription($latestEvent->getDescription()) + ->setImage($latestEvent->getImage()) ->setAttendingCount($latestEvent->getAttendingCount()) ->setSpeaker($latestEvent->getSpeaker()) ->setUrl($latestEvent->getUrl()); diff --git a/src/Entity/MeetupEvent.php b/src/Entity/MeetupEvent.php index b1c9b52..be11188 100644 --- a/src/Entity/MeetupEvent.php +++ b/src/Entity/MeetupEvent.php @@ -58,6 +58,11 @@ class MeetupEvent */ private $url; + /** + * @ORM\Column(type="string", length=255, nullable=true) + */ + private $image; + public function getId(): ?int { return $this->id; @@ -164,6 +169,7 @@ public function __toString(): string $attributes = [ 'id: '.$this->id, 'meetupId: '.$this->meetupId, + 'image: '.$this->image, 'scheduledAt: '.$this->scheduledAt->format('Y-m-d H:i:s'), 'title: '.$this->title, 'place: '.$this->place, @@ -175,4 +181,16 @@ public function __toString(): string return sprintf('[%s]', implode(',', $attributes)); } + + public function getImage(): ?string + { + return $this->image; + } + + public function setImage(?string $image): self + { + $this->image = $image; + + return $this; + } } diff --git a/src/Migrations/Version20200728231947.php b/src/Migrations/Version20200728231947.php new file mode 100644 index 0000000..2385169 --- /dev/null +++ b/src/Migrations/Version20200728231947.php @@ -0,0 +1,35 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE meetup_event ADD image VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE meetup_event DROP image'); + } +} diff --git a/src/Service/GetLatestMeetupEventFromCrawler.php b/src/Service/GetLatestMeetupEventFromCrawler.php index 4955806..cc09c6f 100644 --- a/src/Service/GetLatestMeetupEventFromCrawler.php +++ b/src/Service/GetLatestMeetupEventFromCrawler.php @@ -32,6 +32,7 @@ public function handle(): MeetupEvent $event ->setMeetupId($this->crawlId($eventListNode)) ->setTitle($this->crawlTitle($eventListNode)) + ->setImage($this->crawlImage($eventListNode)) ->setScheduledAt($this->crawlScheduledAt($eventListNode)) ->setPlace($this->crawlPlace($eventListNode)) ->setDescription($this->crawlDescription($eventListNode)) @@ -47,7 +48,9 @@ public function handle(): MeetupEvent */ private function getHtml(): string { - if (!file_exists($this->meetupEventsUrl)) { + $isRequest = strpos($this->meetupEventsUrl, 'http') !== false; + + if (!$isRequest && !file_exists($this->meetupEventsUrl)) { throw new Exception(sprintf('Unable to read the provided source %s.', $this->meetupEventsUrl)); } @@ -103,6 +106,25 @@ function () use ($eventListNode) { ); } + private function crawlImage(Crawler $eventListNode): string + { + return $this->crawlNode->handle( + function () use ($eventListNode) { + $style = $eventListNode->filter('.eventCardHead--photo') + ->eq(0) + ->attr('style'); + + + $matches = []; + preg_match('/("(.*?)")|(\'(.*?)\')|("es;(.*?)"es;)/', $style, $matches); + + return $matches[count($matches) - 1]; + }, + '', + 'Unable to find the image element.' + ); + } + private function crawlPlace(Crawler $eventListNode): string { return $this->crawlNode->handle( @@ -124,10 +146,8 @@ private function crawlDescription(Crawler $eventListNode): string function () use ($eventListNode) { return $eventListNode->filter('.eventCard') ->eq(0) - ->filter('.flex-item--shrink') - ->eq(2) ->filter('p') - ->eq(1) + ->eq(2) ->text(); }, '', diff --git a/tests/fixtures/meetup_response_example.html b/tests/fixtures/meetup_response_example.html index 9657e8c..4e2b265 100644 --- a/tests/fixtures/meetup_response_example.html +++ b/tests/fixtures/meetup_response_example.html @@ -34,6 +34,7 @@ +
From d9fe8f6195afa9a8bb36a26296dcb01151fa8447 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Tue, 28 Jul 2020 19:22:47 -0500 Subject: [PATCH 10/11] Fix image path to accept quotes, double quotes, "es; and no string wrapper --- .env | 2 ++ src/Service/GetLatestMeetupEventFromCrawler.php | 11 ++++++++--- tests/fixtures/meetup_response_example.html | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.env b/.env index 53186a4..18b3a33 100644 --- a/.env +++ b/.env @@ -47,3 +47,5 @@ REDIS_URL=redis://redis MEETUP_KEY= SLACK_TOKEN= SLACK_TEAMID= +#MEETUP_EVENTS_URL='https://www.meetup.com/es/PHP-The-Right-Way/events/' +MEETUP_EVENTS_URL='tests/fixtures/meetup_response_example.html' diff --git a/src/Service/GetLatestMeetupEventFromCrawler.php b/src/Service/GetLatestMeetupEventFromCrawler.php index cc09c6f..2fb657b 100644 --- a/src/Service/GetLatestMeetupEventFromCrawler.php +++ b/src/Service/GetLatestMeetupEventFromCrawler.php @@ -114,11 +114,16 @@ function () use ($eventListNode) { ->eq(0) ->attr('style'); - $matches = []; - preg_match('/("(.*?)")|(\'(.*?)\')|("es;(.*?)"es;)/', $style, $matches); + preg_match('/url\((.*?)\)/', $style, $matches); + + $url = $matches[count($matches) - 1]; + $url = str_replace('"es;', '', $url); + $url = str_replace('"', '', $url); + $url = str_replace('\'', '', $url); + - return $matches[count($matches) - 1]; + return $url; }, '', 'Unable to find the image element.' diff --git a/tests/fixtures/meetup_response_example.html b/tests/fixtures/meetup_response_example.html index 4e2b265..36cb167 100644 --- a/tests/fixtures/meetup_response_example.html +++ b/tests/fixtures/meetup_response_example.html @@ -34,7 +34,7 @@ -
+
From bf466d2cc2b913a27767fc6fda04980d87a53398 Mon Sep 17 00:00:00 2001 From: Javier Ledezma Date: Tue, 28 Jul 2020 19:27:11 -0500 Subject: [PATCH 11/11] Add missing styles to migration and latest crawl image method --- src/Migrations/Version20200728231947.php | 10 +++++----- src/Service/GetLatestMeetupEventFromCrawler.php | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Migrations/Version20200728231947.php b/src/Migrations/Version20200728231947.php index 2385169..c4fd49f 100644 --- a/src/Migrations/Version20200728231947.php +++ b/src/Migrations/Version20200728231947.php @@ -12,23 +12,23 @@ */ final class Version20200728231947 extends AbstractMigration { - public function getDescription() : string + public function getDescription(): string { return ''; } - public function up(Schema $schema) : void + public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('ALTER TABLE meetup_event ADD image VARCHAR(255) DEFAULT NULL'); } - public function down(Schema $schema) : void + public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('ALTER TABLE meetup_event DROP image'); } diff --git a/src/Service/GetLatestMeetupEventFromCrawler.php b/src/Service/GetLatestMeetupEventFromCrawler.php index 2fb657b..14e878d 100644 --- a/src/Service/GetLatestMeetupEventFromCrawler.php +++ b/src/Service/GetLatestMeetupEventFromCrawler.php @@ -48,7 +48,7 @@ public function handle(): MeetupEvent */ private function getHtml(): string { - $isRequest = strpos($this->meetupEventsUrl, 'http') !== false; + $isRequest = false !== strpos($this->meetupEventsUrl, 'http'); if (!$isRequest && !file_exists($this->meetupEventsUrl)) { throw new Exception(sprintf('Unable to read the provided source %s.', $this->meetupEventsUrl)); @@ -122,7 +122,6 @@ function () use ($eventListNode) { $url = str_replace('"', '', $url); $url = str_replace('\'', '', $url); - return $url; }, '',