From d673f56c827184a80e2e4de03bd038976a6e4606 Mon Sep 17 00:00:00 2001 From: Joop Schilder Date: Thu, 22 Apr 2021 19:26:09 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 + bin/app.php | 31 + composer.json | 31 + composer.lock | 737 ++++++++++++++++++ src/Domain/Model/ASRockMemoryQVL.php | 28 + src/Domain/Model/MemoryConfiguration.php | 53 ++ .../Decorator/NormalizerDecorator.php | 34 + .../Decorator/PricewatchDecorator.php | 34 + .../MemoryConfigurationNormalizer.php | 54 ++ src/Domain/Normalizer/Normalizer.php | 13 + src/Encoder/CsvEncoder.php | 48 ++ src/Encoder/EncoderFactory.php | 24 + src/Encoder/JsonEncoder.php | 30 + src/Encoder/StringEncoder.php | 10 + src/IO/Downloader.php | 55 ++ src/MemoryQVLScraper.php | 46 ++ var/.gitignore | 0 var/cache/.gitignore | 0 var/doc/.gitignore | 0 19 files changed, 1232 insertions(+) create mode 100644 .gitignore create mode 100644 bin/app.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Domain/Model/ASRockMemoryQVL.php create mode 100644 src/Domain/Model/MemoryConfiguration.php create mode 100644 src/Domain/Normalizer/Decorator/NormalizerDecorator.php create mode 100644 src/Domain/Normalizer/Decorator/PricewatchDecorator.php create mode 100644 src/Domain/Normalizer/MemoryConfigurationNormalizer.php create mode 100644 src/Domain/Normalizer/Normalizer.php create mode 100644 src/Encoder/CsvEncoder.php create mode 100644 src/Encoder/EncoderFactory.php create mode 100644 src/Encoder/JsonEncoder.php create mode 100644 src/Encoder/StringEncoder.php create mode 100644 src/IO/Downloader.php create mode 100644 src/MemoryQVLScraper.php create mode 100644 var/.gitignore create mode 100644 var/cache/.gitignore create mode 100644 var/doc/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0345db --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/.idea/ +/var/doc/* +/var/cache/* diff --git a/bin/app.php b/bin/app.php new file mode 100644 index 0000000..eb499ab --- /dev/null +++ b/bin/app.php @@ -0,0 +1,31 @@ +download($list); +$configurations = $scraper->scrape($page); +$selection = $configurations->filter(fn(MemoryConfiguration $memory) => in_array($memory->numberOfModules, [2, 4]) + && $memory->totalSize >= 16 + && $memory->speed >= 3600 + && $memory->overclockingVerified +); + +// data presentation +$normalizer = new MemoryConfigurationNormalizer(); +$normalizer = PricewatchDecorator::decorate($normalizer); +$encoderFactory = new EncoderFactory($normalizer); +$encoder = $encoderFactory->getEncoder($argv[1] ?? 'csv'); +print($encoder->encode($selection)); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ff9d7c4 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "vendor_name/asrock-memory-listing", + "description": "description", + "minimum-stability": "stable", + "license": "proprietary", + "authors": [ + { + "name": "joop", + "email": "email@example.com" + } + ], + "config": { + "platform": { + "php": "^8.0" + } + }, + "autoload": { + "psr-0": { + "": ["src"] + } + }, + "require-dev": { + "symfony/var-dumper": "^5.2" + }, + "require": { + "symfony/dom-crawler": "^5.2", + "symfony/css-selector": "^5.2", + "ext-curl": "*", + "illuminate/collections": "^8.38" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..10728d1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,737 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "26034160d32c56d1d1d217b4cc155139", + "packages": [ + { + "name": "illuminate/collections", + "version": "v8.38.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "21690cd5591f2d42d792e5e4a687f9beba829f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/21690cd5591f2d42d792e5e4a687f9beba829f1d", + "reference": "21690cd5591f2d42d792e5e4a687f9beba829f1d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.1.4)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-04-14T11:48:08+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v8.38.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "5764f703ea8f74ced163125d810951cd5ef2b7e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5764f703ea8f74ced163125d810951cd5ef2b7e1", + "reference": "5764f703ea8f74ced163125d810951cd5ef2b7e1", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-04-01T13:09:31+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v8.38.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "300aa13c086f25116b5f3cde3ca54ff5c822fb05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/300aa13c086f25116b5f3cde3ca54ff5c822fb05", + "reference": "300aa13c086f25116b5f3cde3ca54ff5c822fb05", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2020-10-27T15:20:30+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f", + "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.2.4" + }, + "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": "2021-01-27T10:01:46+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "400e265163f65aceee7e904ef532e15228de674b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/400e265163f65aceee7e904ef532e15228de674b", + "reference": "400e265163f65aceee7e904ef532e15228de674b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "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": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v5.2.4" + }, + "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": "2021-02-15T18:55:04+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "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": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "89412a68ea2e675b4e44f260a5666729f77f668e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/89412a68ea2e675b4e44f260a5666729f77f668e", + "reference": "89412a68ea2e675b4e44f260a5666729f77f668e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.2.6" + }, + "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": "2021-03-28T09:42:18+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-curl": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/src/Domain/Model/ASRockMemoryQVL.php b/src/Domain/Model/ASRockMemoryQVL.php new file mode 100644 index 0000000..e0f62bc --- /dev/null +++ b/src/Domain/Model/ASRockMemoryQVL.php @@ -0,0 +1,28 @@ +url = sprintf('https://www.asrock.com/mb/%s/%s/Memory-%s.asp', + strtoupper(trim($cpuManufacturer)), + rawurlencode(trim($motherboard)), + strtoupper(trim($cpuFamily)) + ); + } + + public function __toString(): string + { + return $this->url; + } +} diff --git a/src/Domain/Model/MemoryConfiguration.php b/src/Domain/Model/MemoryConfiguration.php new file mode 100644 index 0000000..bdb9d1f --- /dev/null +++ b/src/Domain/Model/MemoryConfiguration.php @@ -0,0 +1,53 @@ +moduleSize = filter_var($this->moduleSizeHr, FILTER_SANITIZE_NUMBER_INT); + $this->overclockingVerified = in_array($this->overclockingSupport, ['v', '2'], true); + $this->dualChannelOverclockingVerified = $this->overclockingSupport === '2'; + + $this->deriveAdditionalFields(); + } + + private function deriveAdditionalFields(): void + { + $this->findTotalSize(); + $this->numberOfModules = $this->totalSize / $this->moduleSize; + } + + private function findTotalSize(): void + { + // Initially 8 times the module size + $this->totalSize = 8 * $this->moduleSize; + + // Work our way down to the module size... + while (!str_contains($this->module, $this->totalSize) && $this->totalSize > $this->moduleSize) { + $this->totalSize /= 2; + } + + $this->totalSizeHr = sprintf('%dGB', $this->totalSize); + } +} diff --git a/src/Domain/Normalizer/Decorator/NormalizerDecorator.php b/src/Domain/Normalizer/Decorator/NormalizerDecorator.php new file mode 100644 index 0000000..7cb7405 --- /dev/null +++ b/src/Domain/Normalizer/Decorator/NormalizerDecorator.php @@ -0,0 +1,34 @@ +parent->getHeaders(), $this->getAdditionalHeaders()); + } + + final public function normalize(object $object): array + { + $normalized = $this->parent->normalize($object); + return $this->makePass($normalized); + } +} diff --git a/src/Domain/Normalizer/Decorator/PricewatchDecorator.php b/src/Domain/Normalizer/Decorator/PricewatchDecorator.php new file mode 100644 index 0000000..6cfd8db --- /dev/null +++ b/src/Domain/Normalizer/Decorator/PricewatchDecorator.php @@ -0,0 +1,34 @@ +selectSignificantTerms($module); + + $normalized['pricewatch_url'] = sprintf( + 'https://tweakers.net/pricewatch/zoeken/?keyword=%s', + rawurlencode($module) + ); + + return $normalized; + } + + protected function getAdditionalHeaders(): array + { + return ['pricewatch_url']; + } + + private function selectSignificantTerms(string $module): string + { + $parts = explode(' ', trim($module), 2); + if ((count($parts) === 2) && stripos($parts[1], 'ver') !== false) { + return $parts[0]; + } + + return $module; + } +} diff --git a/src/Domain/Normalizer/MemoryConfigurationNormalizer.php b/src/Domain/Normalizer/MemoryConfigurationNormalizer.php new file mode 100644 index 0000000..5b11c3d --- /dev/null +++ b/src/Domain/Normalizer/MemoryConfigurationNormalizer.php @@ -0,0 +1,54 @@ + $object->vendor, + 'module' => $object->module, + 'type' => $object->type, + 'speed' => $object->speed, + 'supported_speed' => $object->supportedSpeed, + 'number_of_modules' => $object->numberOfModules, + 'module_size' => $object->moduleSizeHr, + 'total_size' => $object->totalSizeHr, + 'chip' => $object->chip, + 'dual_sided' => $object->isDualSided, + 'dimm_socket_support' => $object->dimmSocketSupport, + 'overclocking_support' => $object->overclockingSupport, + 'overclocking_verified' => $object->overclockingVerified, + 'dc_overclocking_verified' => $object->dualChannelOverclockingVerified, + 'note' => $object->note, + ]; + } +} diff --git a/src/Domain/Normalizer/Normalizer.php b/src/Domain/Normalizer/Normalizer.php new file mode 100644 index 0000000..64054ce --- /dev/null +++ b/src/Domain/Normalizer/Normalizer.php @@ -0,0 +1,13 @@ +values(); + $buffer = implode($this->columnSeparator, $this->normalizer->getHeaders()); + $buffer .= $this->rowSeparator; + + $items->each(function (object $object) use (&$buffer) { + $normalized = $this->normalizer->normalize($object); + $buffer .= collect($normalized) + ->map([CsvEncoder::class, 'formatBoolean']) + ->map([CsvEncoder::class, 'formatEmpty']) + ->implode($this->columnSeparator); + $buffer .= $this->rowSeparator; + }); + + return $buffer; + } + + public static function formatBoolean(mixed $value): mixed + { + if (!is_bool($value)) { + return $value; + } + return true === $value ? 'yes' : 'no'; + } + + public static function formatEmpty(mixed $value): mixed + { + return empty($value) ? '-' : $value; + } +} diff --git a/src/Encoder/EncoderFactory.php b/src/Encoder/EncoderFactory.php new file mode 100644 index 0000000..5ae8521 --- /dev/null +++ b/src/Encoder/EncoderFactory.php @@ -0,0 +1,24 @@ + new CsvEncoder($this->normalizer, columnSeparator: ',', rowSeparator: "\n"), + 'json', 'JSON' => new JsonEncoder($this->normalizer), + default => throw new InvalidArgumentException('Invalid encoder: ' . $type) + }; + } +} diff --git a/src/Encoder/JsonEncoder.php b/src/Encoder/JsonEncoder.php new file mode 100644 index 0000000..30f72f7 --- /dev/null +++ b/src/Encoder/JsonEncoder.php @@ -0,0 +1,30 @@ + $items->count(), + 'items' => $items->map([$this->normalizer, 'normalize'])->values()->toArray(), + ], + JSON_UNESCAPED_SLASHES + | JSON_UNESCAPED_UNICODE + | JSON_UNESCAPED_LINE_TERMINATORS + | JSON_BIGINT_AS_STRING + | JSON_THROW_ON_ERROR + ); + } +} diff --git a/src/Encoder/StringEncoder.php b/src/Encoder/StringEncoder.php new file mode 100644 index 0000000..167788e --- /dev/null +++ b/src/Encoder/StringEncoder.php @@ -0,0 +1,10 @@ +cacheDirectory ??= dirname(__DIR__, 2) . '/var/cache'; + $this->cacheDirectory = rtrim($this->cacheDirectory, DIRECTORY_SEPARATOR); + + $this->curl = curl_init(); + curl_setopt_array($this->curl, [ + CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT x.y; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => false, + CURLOPT_FORBID_REUSE => false, + CURLOPT_AUTOREFERER => true, + ]); + } + + public function __destruct() + { + if (is_resource($this->curl)) { + curl_close($this->curl); + } + } + + public function download(string $url, bool $addHtmlTagsToResponse = false): string + { + $file = $this->getCacheFile($url); + if (file_exists($file)) { + return file_get_contents($file); + } + + curl_setopt($this->curl, CURLOPT_URL, $url); + $response = curl_exec($this->curl); + if ($addHtmlTagsToResponse) { + $response = sprintf('%s', $response); + } + + file_put_contents($file, $response); + return $response; + } + + private function getCacheFile(string $url): string + { + return $this->cacheDirectory . DIRECTORY_SEPARATOR . md5($url) . '.html'; + } +} diff --git a/src/MemoryQVLScraper.php b/src/MemoryQVLScraper.php new file mode 100644 index 0000000..14f3fca --- /dev/null +++ b/src/MemoryQVLScraper.php @@ -0,0 +1,46 @@ +addHtmlContent($html); + $crawler->filterXPath('//tr')->each(function (Crawler $tr) use (&$configurations) { + $tableData = $tr->filterXPath('//td'); + if ($tableData->count() === 0) { + return; + } + $configurations[] = $this->fromTableData($tableData); + }); + + return collect($configurations); + } + + private function fromTableData(Crawler $tableData): MemoryConfiguration + { + return new MemoryConfiguration( + type: $tableData->eq(0)->text('DDR4'), + vendor: $tableData->eq(1)->text(), + speed: (int)$tableData->eq(2)->text(0), + supportedSpeed: (int)$tableData->eq(3)->text(0), + moduleSizeHr: $tableData->eq(4)->text(), + module: $tableData->eq(5)->text(), + chip: $tableData->eq(6)->text(), + isDualSided: $tableData->eq(7)->text() === 'DS', + dimmSocketSupport: $tableData->eq(8)->text(), + overclockingSupport: strtolower($tableData->eq(9)->text('')), + note: $tableData->eq(10)->text() + ); + } +} diff --git a/var/.gitignore b/var/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/var/cache/.gitignore b/var/cache/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/var/doc/.gitignore b/var/doc/.gitignore new file mode 100644 index 0000000..e69de29