Explorar el Código

Часть черновой реализации

Vladimir Tkachev hace 8 años
commit
7accaff116

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+# no idea
+/.idea/
+
+# & no vendor
+/vendor/

+ 13 - 0
bin/kitchen.php

@@ -0,0 +1,13 @@
+#!/usr/bin/env php
+<?php
+declare(strict_types = 1);
+
+use Avolver\CompanyEventsKitchen\Application;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+gc_enable();
+
+$app = new Application();
+
+$app->run();

+ 19 - 0
composer.json

@@ -0,0 +1,19 @@
+{
+    "name": "avolver/company-events-kitchen",
+    "description": "Simple events aggregation",
+    "type": "project",
+    "autoload": {
+        "psr-4": { "Avolver\\CompanyEventsKitchen\\": "src/" }
+    },
+    "require": {
+        "php":                       ">=7.1",
+        "symfony/console":           "4.0.4",
+        "fzaninotto/faker":          "1.7.1",
+        "smi2/phpclickhouse":        "0.17.12.1",
+        "myclabs/php-enum":          "1.5.2",
+        "roave/security-advisories": "dev-master"
+    },
+    "license": "BEERWARE",
+    "minimum-stability": "alpha",
+    "prefer-stable": true
+}

+ 440 - 0
composer.lock

@@ -0,0 +1,440 @@
+{
+    "_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#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "f639da40c5d2d04164fbfc01ff9faf9f",
+    "packages": [
+        {
+            "name": "fzaninotto/faker",
+            "version": "v1.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/fzaninotto/Faker.git",
+                "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d",
+                "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "ext-intl": "*",
+                "phpunit/phpunit": "^4.0 || ^5.0",
+                "squizlabs/php_codesniffer": "^1.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Faker\\": "src/Faker/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "François Zaninotto"
+                }
+            ],
+            "description": "Faker is a PHP library that generates fake data for you.",
+            "keywords": [
+                "data",
+                "faker",
+                "fixtures"
+            ],
+            "time": "2017-08-15T16:48:10+00:00"
+        },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "3ed7088cfd0a0e06534b7f8b0eee82acea574fac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/3ed7088cfd0a0e06534b7f8b0eee82acea574fac",
+                "reference": "3ed7088cfd0a0e06534b7f8b0eee82acea574fac",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35|^5.7|^6.0",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "time": "2017-06-28T16:24:08+00:00"
+        },
+        {
+            "name": "roave/security-advisories",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Roave/SecurityAdvisories.git",
+                "reference": "0be5f7071b9007938a360d01da654b13d9caf12d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0be5f7071b9007938a360d01da654b13d9caf12d",
+                "reference": "0be5f7071b9007938a360d01da654b13d9caf12d",
+                "shasum": ""
+            },
+            "conflict": {
+                "adodb/adodb-php": "<5.20.6",
+                "amphp/artax": "<1.0.6|>=2,<2.0.6",
+                "aws/aws-sdk-php": ">=3,<3.2.1",
+                "bugsnag/bugsnag-laravel": ">=2,<2.0.2",
+                "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4",
+                "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
+                "cartalyst/sentry": "<=2.1.6",
+                "codeigniter/framework": "<=3.0.6",
+                "composer/composer": "<=1.0.0-alpha11",
+                "contao-components/mediaelement": ">=2.14.2,<2.21.1",
+                "contao/core": ">=2,<3.5.32",
+                "contao/core-bundle": ">=4,<4.4.8",
+                "contao/listing-bundle": ">=4,<4.4.8",
+                "contao/newsletter-bundle": ">=4,<4.1",
+                "doctrine/annotations": ">=1,<1.2.7",
+                "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2",
+                "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1",
+                "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2",
+                "doctrine/doctrine-bundle": "<1.5.2",
+                "doctrine/doctrine-module": "<=0.7.1",
+                "doctrine/mongodb-odm": ">=1,<1.0.2",
+                "doctrine/mongodb-odm-bundle": ">=2,<3.0.1",
+                "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1",
+                "dompdf/dompdf": ">=0.6,<0.6.2",
+                "drupal/core": ">=8,<8.3.7",
+                "drupal/drupal": ">=8,<8.3.7",
+                "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.2|>=5.4,<5.4.10.1|>=2017.8,<2017.8.1.1",
+                "firebase/php-jwt": "<2",
+                "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2",
+                "friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
+                "gree/jose": "<=2.2",
+                "gregwar/rst": "<1.0.3",
+                "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1",
+                "illuminate/auth": ">=4,<4.0.99|>=4.1,<4.1.26",
+                "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29",
+                "joomla/session": "<1.3.1",
+                "laravel/framework": ">=4,<4.0.99|>=4.1,<4.1.29",
+                "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10",
+                "magento/magento1ce": ">=1.5.0.1,<1.9.3.2",
+                "magento/magento1ee": ">=1.9,<1.14.3.2",
+                "magento/magento2ce": ">=2,<2.2",
+                "monolog/monolog": ">=1.8,<1.12",
+                "namshi/jose": "<2.2",
+                "onelogin/php-saml": "<2.10.4",
+                "oro/crm": ">=1.7,<1.7.4",
+                "oro/platform": ">=1.7,<1.7.4",
+                "phpmailer/phpmailer": ">=5,<5.2.24",
+                "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
+                "phpxmlrpc/extras": "<0.6.1",
+                "pusher/pusher-php-server": "<2.2.1",
+                "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9",
+                "shopware/shopware": "<5.3.7",
+                "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11",
+                "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
+                "silverstripe/framework": ">=3,<3.3",
+                "silverstripe/userforms": "<3",
+                "simplesamlphp/saml2": "<1.8.1|>=1.9,<1.9.1|>=1.10,<1.10.3|>=2,<2.3.3",
+                "simplesamlphp/simplesamlphp": "<1.14.16",
+                "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
+                "socalnick/scn-social-auth": "<1.15.2",
+                "squizlabs/php_codesniffer": ">=1,<2.8.1",
+                "swiftmailer/swiftmailer": ">=4,<5.4.5",
+                "symfony/dependency-injection": ">=2,<2.0.17",
+                "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+                "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2",
+                "symfony/http-foundation": ">=2,<2.3.27|>=2.4,<2.5.11|>=2.6,<2.6.6",
+                "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8",
+                "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+                "symfony/routing": ">=2,<2.0.19",
+                "symfony/security": ">=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9|>=2.3,<2.3.37|>=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8.23,<2.8.25|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5",
+                "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.6|>=2.8.23,<2.8.25|>=3,<3.0.6|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5",
+                "symfony/security-csrf": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+                "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+                "symfony/serializer": ">=2,<2.0.11",
+                "symfony/symfony": ">=2,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+                "symfony/translation": ">=2,<2.0.17",
+                "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
+                "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
+                "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7",
+                "thelia/backoffice-default-template": ">=2.1,<2.1.2",
+                "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3",
+                "twig/twig": "<1.20",
+                "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5",
+                "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
+                "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
+                "willdurand/js-translation-bundle": "<2.1.1",
+                "yiisoft/yii": ">=1.1.14,<1.1.15",
+                "yiisoft/yii2": "<2.0.5",
+                "yiisoft/yii2-bootstrap": "<2.0.4",
+                "yiisoft/yii2-dev": "<2.0.4",
+                "yiisoft/yii2-gii": "<2.0.4",
+                "yiisoft/yii2-jui": "<2.0.4",
+                "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3",
+                "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2",
+                "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2",
+                "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5",
+                "zendframework/zend-diactoros": ">=1,<1.0.4",
+                "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1",
+                "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1",
+                "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6",
+                "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3",
+                "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2",
+                "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1",
+                "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4",
+                "zendframework/zend-validator": ">=2.3,<2.3.6",
+                "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1",
+                "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6",
+                "zendframework/zendframework": ">=2,<2.4.11|>=2.5,<2.5.1",
+                "zendframework/zendframework1": "<1.12.20",
+                "zendframework/zendopenid": ">=2,<2.0.2",
+                "zendframework/zendxml": ">=1,<1.0.1",
+                "zetacomponents/mail": "<1.8.2",
+                "zf-commons/zfc-user": "<1.2.2",
+                "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
+                "zfr/zfr-oauth2-server-module": "<0.1.2"
+            },
+            "type": "metapackage",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "role": "maintainer"
+                }
+            ],
+            "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
+            "time": "2018-01-31T06:22:20+00:00"
+        },
+        {
+            "name": "smi2/phpclickhouse",
+            "version": "0.17.12.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/smi2/phpClickHouse.git",
+                "reference": "e0f6c52c7deedfd499ce17f6beac2805c7c6a565"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/smi2/phpClickHouse/zipball/e0f6c52c7deedfd499ce17f6beac2805c7c6a565",
+                "reference": "e0f6c52c7deedfd499ce17f6beac2805c7c6a565",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "5.5.*"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Igor Strykhar",
+                    "email": "isublimity@gmail.com",
+                    "homepage": "https://github.com/isublimity"
+                }
+            ],
+            "description": "php ClickHouse client library",
+            "homepage": "https://github.com/smi2/phpClickHouse",
+            "keywords": [
+                "clickhouse",
+                "client",
+                "curl",
+                "driver",
+                "http",
+                "http client"
+            ],
+            "time": "2017-12-28T07:40:46+00:00"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/36d5b41e7d4e1ccf0370f6babe966c08ef0a1488",
+                "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.4",
+                "symfony/process": "<3.3"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/event-dispatcher": "~3.4|~4.0",
+                "symfony/lock": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0"
+            },
+            "suggest": {
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/lock": "",
+                "symfony/process": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "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 Console Component",
+            "homepage": "https://symfony.com",
+            "time": "2018-01-29T09:06:29+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
+                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6-dev"
+                }
+            },
+            "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"
+            ],
+            "time": "2017-10-11T12:05:26+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "alpha",
+    "stability-flags": {
+        "roave/security-advisories": 20
+    },
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.1"
+    },
+    "platform-dev": []
+}

+ 65 - 0
src/Application.php

@@ -0,0 +1,65 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen;
+
+use Avolver\CompanyEventsKitchen\Client\ClickHouseClient;
+use Avolver\CompanyEventsKitchen\Client\MongoDBClient;
+use Avolver\CompanyEventsKitchen\Command\CompaniesFillerCommand;
+use Avolver\CompanyEventsKitchen\Command\EventsRandomizerCommand;
+use Avolver\CompanyEventsKitchen\Config\Config;
+use Symfony\Component\Console\Application as ConsoleApplication;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Класс консольного приложения
+ * @package Avolver\CompanyEventsKitchen
+ */
+class Application extends ConsoleApplication
+{
+    /**
+     * @var MongoDBClient
+     */
+    private $mongoClient;
+
+    /**
+     * @var ClickHouseClient
+     */
+    private $clickhouseClient;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run(InputInterface $input = null, OutputInterface $output = null): ?int
+    {
+        $this->add(new CompaniesFillerCommand());
+        $this->add(new EventsRandomizerCommand());
+
+        return parent::run($input, $output);
+    }
+
+    /**
+     * @return ClickHouseClient
+     */
+    public function getClickhouseClient(): ClickHouseClient
+    {
+        if (!$this->clickhouseClient) {
+            $this->clickhouseClient = new ClickHouseClient(Config::clickhouse());
+        }
+
+        return $this->clickhouseClient;
+    }
+
+    /**
+     * @return MongoDBClient
+     */
+    public function getMongoClient(): MongoDBClient
+    {
+        if (!$this->mongoClient) {
+            $this->mongoClient = new MongoDBClient(Config::mongo());
+        }
+
+        return $this->mongoClient;
+    }
+}

+ 46 - 0
src/Client/ClickHouseClient.php

@@ -0,0 +1,46 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Client;
+
+use Avolver\CompanyEventsKitchen\Config\ClickHouseConfig;
+use ClickHouseDB\Client as BaseClient;
+
+/**
+ * Клиент для ClickHouse
+ *
+ * @package Avolver\CompanyEventsKitchen\Client
+ */
+class ClickHouseClient extends BaseClient
+{
+    /**
+     * @var ClickHouseConfig
+     */
+    private $config;
+
+    /**
+     * ClickHouseClient constructor.
+     * @param ClickHouseConfig $config
+     */
+    public function __construct(ClickHouseConfig $config)
+    {
+        $configParams = [
+            'host'     => $config->getHost(),
+            'port'     => (string) $config->getPort(),
+            'username' => $config->getUsername(),
+            'password' => $config->getPassoword()
+        ];
+
+        parent::__construct($configParams);
+
+        $this->config = $config;
+    }
+
+    /**
+     * @return ClickHouseConfig
+     */
+    public function getConfig(): ClickHouseConfig
+    {
+        return $this->config;
+    }
+}

+ 95 - 0
src/Client/MongoDBClient.php

@@ -0,0 +1,95 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Client;
+
+use Avolver\CompanyEventsKitchen\Config\MongoConfig;
+use MongoDB\Driver\Manager as MongoManager;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\Exception as MongoException;
+use MongoDB\Driver\Exception\BulkWriteException;
+
+/**
+ * Простой MongoDB клиент
+ */
+class MongoDBClient
+{
+
+    /**
+     * @var MongoConfig
+     */
+    private $config;
+
+    /**
+     * @var MongoManager
+     */
+    private $manager;
+
+    /**
+     * @var WriteConcern
+     */
+    private $writeConcern;
+
+    /**
+     * MongoDBClient constructor.
+     * @param MongoConfig $config
+     */
+    public function __construct(MongoConfig $config)
+    {
+        $this->config       = $config;
+        $this->manager      = new MongoManager('mongodb://127.0.0.1/');
+        $this->writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000);
+    }
+
+    /**
+     * Выполнение пакетной вставки
+     *
+     * @param BulkWrite $bulk
+     *
+     * @throws BulkWriteException
+     * @throws MongoException
+     */
+    public function executeBulkWrite(BulkWrite $bulk): void
+    {
+        $this->manager->executeBulkWrite(
+            $this->getCompaniesNs(),
+            $bulk,
+            $this->writeConcern
+        );
+    }
+
+    /**
+     * Получение mongo-менеджера
+     *
+     * @return MongoManager
+     */
+    public function getManager(): MongoManager
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Получение экземпляра класса для пакетной вставки
+     *
+     * @return BulkWrite
+     */
+    public function getBulkWrite(): BulkWrite
+    {
+        return new BulkWrite(['ordered' => true]);
+    }
+
+    /**
+     * @return string
+     */
+    public function getCompaniesNs(): string
+    {
+        $namespace = sprintf(
+            '%s.%s',
+            $this->config->getDatabase(),
+            $this->config->getCompaniesCollectionName()
+        );
+
+        return $namespace;
+    }
+}

+ 48 - 0
src/Command/CompaniesFillerCommand.php

@@ -0,0 +1,48 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Command;
+
+use Avolver\CompanyEventsKitchen\Application;
+use Avolver\CompanyEventsKitchen\Repository\CompanyRepository;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Команда для создания компаний со случайными названиями
+ *
+ * @method Application getApplication
+ *
+ * @package Avolver\CompanyEventsKitchen\Command
+ */
+class CompaniesFillerCommand extends Command
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure(): void
+    {
+        $this
+            ->setName('fill-companies')
+            ->setDescription('Создание компаний со случайными названиями')
+            ->addOption('count', null, InputOption::VALUE_OPTIONAL, 'количество компаний', 1500000);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $count = $input->getOption('count');
+        $repo  = new CompanyRepository($this->getApplication()->getMongoClient());
+
+        if ($output->isVerbose()) {
+            $output->writeln('Создание компаний со случайными названиями...');
+            $repo->setOutput($output);
+        }
+
+        $repo->createRandomCompanies($count);
+    }
+}

+ 54 - 0
src/Command/EventsRandomizerCommand.php

@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Command;
+
+use Avolver\CompanyEventsKitchen\Application;
+use Avolver\CompanyEventsKitchen\Repository\CompanyRepository;
+use Avolver\CompanyEventsKitchen\Repository\EventsRepository;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Команда для создания случайных событий для каждой компании
+ *
+ * @method Application getApplication
+ *
+ * @package Avolver\CompanyEventsKitchen\Command
+ */
+class EventsRandomizerCommand extends Command
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure(): void
+    {
+        $this
+            ->setName('generate-random-events')
+            ->setDescription('Создание случайных событий для каждой компании')
+            ->addOption('count', null, InputOption::VALUE_OPTIONAL, 'количество событий', 65535);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $count = $input->getOption('count');
+        $today = (new \DateTime())->setTime(0, 0);
+
+        $companyRepo = new CompanyRepository($this->getApplication()->getMongoClient());
+        $eventsRepo  = new EventsRepository(
+            $this->getApplication()->getClickhouseClient(),
+            $today,
+            (clone $today)->add(new \DateInterval('P1M'))
+        );
+
+        $eventsRepo->generateEventsForCompanies(
+            $companyRepo->findAll(),
+            $count
+        );
+    }
+}

+ 78 - 0
src/Config/ClickHouseConfig.php

@@ -0,0 +1,78 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Config;
+
+use Avolver\CompanyEventsKitchen\Traits\ConfigSelfTrait;
+
+/**
+ * Простая конфигурация подключения к ClickHouse
+ *
+ * @method static self getConfig
+ *
+ * @package Avolver\CompanyEventsKitchen\Config
+ */
+class ClickHouseConfig
+{
+    use ConfigSelfTrait;
+
+    /**
+     * Получение хоста, на котором работает clickhouse
+     *
+     * @return string
+     */
+    public function getHost(): string
+    {
+        return '127.0.0.1';
+    }
+
+    /**
+     * Получение порта, на котором работает clickhouse
+     *
+     * @return int
+     */
+    public function getPort(): int
+    {
+        return 8123;
+    }
+
+    /**
+     * Получение названия БД clickhouse
+     *
+     * @return string
+     */
+    public function getDatabase(): string
+    {
+        return 'kitchen';
+    }
+
+    /**
+     * Получение названия БД clickhouse
+     *
+     * @return string
+     */
+    public function getEventsTable(): string
+    {
+        return 'company_events';
+    }
+
+    /**
+     * Получение имени пользователя clickhouse
+     *
+     * @return string
+     */
+    public function getUsername(): string
+    {
+        return 'default';
+    }
+
+    /**
+     * Получение пароля пользователя clickhouse
+     *
+     * @return string
+     */
+    public function getPassoword(): string
+    {
+        return '';
+    }
+}

+ 32 - 0
src/Config/Config.php

@@ -0,0 +1,32 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Config;
+
+/**
+ * Простая конфигурация
+ *
+ * @package Avolver\CompanyEventsKitchen
+ */
+class Config
+{
+    /**
+     * Получение конфигурации MongoDB
+     *
+     * @return MongoConfig
+     */
+    public static function mongo(): MongoConfig
+    {
+        return MongoConfig::getConfig();
+    }
+
+    /**
+     * Получение конфигурации ClickHouse
+     *
+     * @return ClickHouseConfig
+     */
+    public static function clickhouse(): ClickHouseConfig
+    {
+        return ClickHouseConfig::getConfig();
+    }
+}

+ 48 - 0
src/Config/MongoConfig.php

@@ -0,0 +1,48 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Config;
+
+use Avolver\CompanyEventsKitchen\Traits\ConfigSelfTrait;
+
+/**
+ * Простая конфигурация подключения к MongoDB
+ *
+ * @method static self getConfig
+ *
+ * @package Avolver\CompanyEventsKitchen\Config
+ */
+class MongoConfig
+{
+    use ConfigSelfTrait;
+
+    /**
+     * Получение mongo-uri
+     *
+     * @return string
+     */
+    public function getUri(): string
+    {
+        return '';
+    }
+
+    /**
+     * Получение имени БД, с которой необходимо работать
+     *
+     * @return string
+     */
+    public function getDatabase(): string
+    {
+        return 'kitchen';
+    }
+
+    /**
+     * Получение имени коллекции, в которой хранятся компании
+     *
+     * @return string
+     */
+    public function getCompaniesCollectionName(): string
+    {
+        return 'companies';
+    }
+}

+ 19 - 0
src/Event/CompanyEvent.php

@@ -0,0 +1,19 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Event;
+
+use MyCLabs\Enum\Enum;
+
+/**
+ * События компании
+ * @package Avolver\CompanyEventsKitchen\Event
+ */
+class CompanyEvent extends Enum
+{
+    public const FOO      = 1;
+    public const BAR      = 2;
+    public const RELOC    = 3;
+    public const DEADBEEF = 4;
+    public const COFFEE   = 5;
+}

+ 126 - 0
src/Helper/RandomIterator.php

@@ -0,0 +1,126 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Helper;
+
+use ArrayIterator;
+use InvalidArgumentException;
+
+/**
+ * Случайный итератор
+ *
+ * @author hakre https://github.com/hakre
+ */
+class RandomIterator implements \IteratorAggregate
+{
+    private $innerIterator;
+    private $randomIterations;
+    private $count;
+
+    /**
+     * RandomIterator constructor.
+     * @param \Traversable $iterator
+     * @param int          $randomIterations
+     */
+    public function __construct(\Traversable $iterator, $randomIterations = 1)
+    {
+
+        $this->setRandomIterations($randomIterations);
+        $this->innerIterator = $iterator;
+    }
+
+    /**
+     * @param $randomIterations
+     */
+    private function setRandomIterations($randomIterations)
+    {
+
+        $number = (int) $randomIterations;
+
+        if ($number < 1) {
+            throw new InvalidArgumentException(
+                sprintf('Number of iterations must be larger than 0, %d (%s) given.', $number, $randomIterations)
+            );
+        }
+
+        $this->randomIterations = $number;
+    }
+
+    /**
+     * @return ArrayIterator|\Traversable
+     * @throws \Exception
+     */
+    public function getIterator()
+    {
+
+        return $this->randomIterations === 1
+            ? $this->getRandomSingle()
+            : $this->getRandomMultiple();
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getCount()
+    {
+
+        return $this->count;
+    }
+
+    /**
+     * SOLID This belongs into it's own type
+     *
+     * @return ArrayIterator
+     *
+     * @throws \Exception
+     */
+    private function getRandomSingle(): ArrayIterator
+    {
+
+        $result = null;
+        $count  = 0;
+        foreach ($this->innerIterator as $current) {
+            random_int(0, $count++) || $result = $current;
+        }
+
+        $this->count = $count;
+
+        return new ArrayIterator([$result]);
+    }
+
+    /**
+     * SOLID This belongs into it's own type
+     *
+     * @return ArrayIterator
+     * @throws \Exception
+     */
+    private function getRandomMultiple(): ArrayIterator
+    {
+
+        $iterator = new \IteratorIterator($this->innerIterator);
+        $iterator->rewind();
+        $it = new \NoRewindIterator($iterator);
+
+        $result    = [];
+        $pickCount = $this->randomIterations;
+        $count     = $pickCount;
+
+        while ($count-- && $it->valid()) {
+            $result[$count] = $it->current();
+            $it->next();
+        }
+
+        shuffle($result);
+
+        $count = $pickCount;
+
+        foreach ($it as $current) {
+            $random = random_int(0, $count++);
+            ($random < $pickCount) && $result[$random] = $current;
+        }
+
+        $this->count = $count;
+
+        return new ArrayIterator($result);
+    }
+}

+ 157 - 0
src/Repository/CompanyRepository.php

@@ -0,0 +1,157 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Repository;
+
+use Avolver\CompanyEventsKitchen\Traits\OutputTrait;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Output\OutputInterface;
+use MongoDB\Driver\Exception\Exception as MongoException;
+use MongoDB\Driver\Query as MongoQuery;
+use MongoDB\Driver\Cursor as MongoCursor;
+use Faker\Factory as FakerFactory;
+use Faker\Generator as FakerGenerator;
+use Avolver\CompanyEventsKitchen\Client\MongoDBClient;
+
+/**
+ * Репозиторий для работы с компаниями
+ *
+ * @package Avolver\CompanyEventsKitchen\Repository
+ */
+class CompanyRepository
+{
+
+    use OutputTrait;
+
+    /**
+     * @var MongoDBClient
+     */
+    private $mongo;
+
+    /**
+     * @var FakerGenerator
+     */
+    private $faker;
+
+    /**
+     * Порог для пакетной вставки
+     *
+     * @var int
+     */
+    private $threshold = 100000;
+
+    /**
+     * Конструктор репозитория компаний
+     * @param MongoDBClient $mongo
+     */
+    public function __construct(MongoDBClient $mongo)
+    {
+        $this->mongo = $mongo;
+        $this->faker = FakerFactory::create('ru_RU');
+    }
+
+    /**
+     * Создание компаний со случайными названиями
+     *
+     * @param int $count
+     *
+     * @return bool
+     *
+     * @throws \Exception
+     * @throws MongoException
+     */
+    public function createRandomCompanies(int $count = 1500000): bool
+    {
+        $bulk     = $this->mongo->getBulkWrite();
+        $progress = $this->createProgressBar($count);
+
+        for ($current = 1; $current <= $count; $current++) {
+            $bulk->insert(['name' => $this->faker->company . ' №' . random_int(1, 65535)]);
+
+            if ($current % $this->threshold === 0) {
+                $this->advanceProgressBar($progress, $this->threshold);
+                try {
+                    $this->mongo->executeBulkWrite($bulk);
+                } catch (MongoException $e) {
+                    $this->output->writeln('Error: ' . $e->getMessage());
+                }
+                gc_collect_cycles();
+                unset($bulk);
+                $bulk = $this->mongo->getBulkWrite();
+            }
+        }
+
+        if ($bulk->count()) {
+            $this->mongo->executeBulkWrite($bulk);
+        }
+
+        $this->finishProgressBar($progress);
+
+        return true;
+    }
+
+    /**
+     * Получение указателя на все компании
+     *
+     * @return MongoCursor
+     * @throws MongoException
+     */
+    public function findAll(): MongoCursor
+    {
+        $query  = new MongoQuery([]);
+        return $this->mongo->getManager()->executeQuery(
+            $this->mongo->getCompaniesNs(),
+            $query
+        );
+    }
+
+    /**
+     * @param int $threshold
+     */
+    public function setThreshold(int $threshold): void
+    {
+        $this->threshold = $threshold;
+    }
+
+    /**
+     * Создание полосы прогресса
+     *
+     * @param int $count
+     *
+     * @return ProgressBar
+     */
+    private function createProgressBar(int $count): ?ProgressBar
+    {
+        if (!$this->output) {
+            return null;
+        }
+
+        return new ProgressBar($this->output, $count);
+    }
+
+    /**
+     * Увеличение прогресса
+     *
+     * @param null|ProgressBar $progressBar
+     * @param int              $threshold
+     */
+    private function advanceProgressBar(?ProgressBar $progressBar, int $threshold): void
+    {
+        if ($progressBar) {
+            $progressBar->advance($threshold);
+        }
+    }
+
+    /**
+     * Завершение прогресса
+     *
+     * @param null|ProgressBar $progressBar
+     */
+    private function finishProgressBar(?ProgressBar $progressBar): void
+    {
+        if ($progressBar) {
+            $progressBar->finish();
+            $this->output->writeln('');
+        }
+    }
+}

+ 196 - 0
src/Repository/EventsRepository.php

@@ -0,0 +1,196 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Repository;
+
+use Avolver\CompanyEventsKitchen\Client\ClickHouseClient;
+use Avolver\CompanyEventsKitchen\Event\CompanyEvent;
+use Avolver\CompanyEventsKitchen\Helper\RandomIterator;
+use Avolver\CompanyEventsKitchen\Traits\OutputTrait;
+use MongoDB\BSON\ObjectId;
+use MongoDB\Driver\Cursor as MongoCursor;
+
+/**
+ * Репозиторий для работы с событиями компании
+ *
+ * @package Avolver\CompanyEventsKitchen\Repository
+ */
+class EventsRepository
+{
+
+    use OutputTrait;
+
+    /**
+     * @var ClickHouseClient
+     */
+    private $clickhouse;
+
+    /**
+     * @var \DateTime
+     */
+    private $startDate;
+
+    /**
+     * @var \DateTime
+     */
+    private $endDate;
+
+    /**
+     * Имя БД
+     *
+     * @var string
+     */
+    private $databaseName;
+
+    /**
+     * Имя БД
+     *
+     * @var string
+     */
+    private $tableName;
+
+    /**
+     * EventsRepository constructor.
+     *
+     * @param ClickHouseClient $clickhouse
+     * @param \DateTime        $startDate
+     * @param \DateTime        $endDate
+     */
+    public function __construct(ClickHouseClient $clickhouse, \DateTime $startDate, \DateTime $endDate)
+    {
+        $this->clickhouse   = $clickhouse;
+        $this->startDate    = $startDate;
+        $this->endDate      = $endDate;
+        $this->databaseName = $this->clickhouse->getConfig()->getDatabase();
+        $this->tableName    = $this->clickhouse->getConfig()->getEventsTable();
+    }
+
+    /**
+     * Создание таблицы и БД, если это необходимо
+     */
+    public function createTableIfNeeded(): void
+    {
+        // Создание БД, если это необходимо
+        $this->clickhouse->write(
+            sprintf('CREATE DATABASE IF NOT EXISTS %s', $this->databaseName)
+        );
+
+        $eventTypesMap = [];
+        foreach (CompanyEvent::toArray() as $key => $value) {
+            $eventTypesMap[] = sprintf('\'%s\' = %s', $key, $value);
+        }
+
+        $createTableQuery = <<<QUERY
+            CREATE TABLE IF NOT EXISTS %s.%s (
+                event_date  Date,
+                event_time  DateTime,
+                event_type  Enum8(%s),
+                id_1        Int32, 
+                id_2        Int32, 
+                id_3        Int32
+            )
+            ENGINE = MergeTree(event_date, (id_1, id_2, id_3), 8192)
+QUERY;
+
+        $this->clickhouse->write(sprintf(
+            $createTableQuery,
+            $this->databaseName,
+            $this->tableName,
+            implode(', ', $eventTypesMap)
+        ));
+    }
+
+    /**
+     * Создание случайных событий для компаний
+     *
+     * @param MongoCursor $cursor
+     * @param int         $needCount
+     *
+     * @throws \Exception
+     */
+    public function generateEventsForCompanies(MongoCursor $cursor, int $needCount = 256): void
+    {
+        $this->createTableIfNeeded();
+        $eventTypeCount = \count(CompanyEvent::keys());
+        $this->clickhouse->database($this->databaseName);
+
+        foreach ($cursor as $company) {
+            $eventCount = random_int($needCount - 10, $needCount + 10);
+            $eventCount = $eventCount > 0 ? $eventCount : 10;
+            for ($count = 1; $count <= $eventCount; $count++) {
+                $randomDate  = $this->getRandomDate($this->startDate, $this->endDate);
+                $randomEventKey = random_int(1, $eventTypeCount);
+                $this->saveEvent($company->_id, $randomDate, new CompanyEvent($randomEventKey));
+            }
+        }
+    }
+
+    /**
+     * Сохранение события
+     *
+     * @param ObjectId     $companyId
+     * @param \DateTime    $datetime
+     * @param CompanyEvent $event
+     */
+    public function saveEvent(ObjectId $companyId, \DateTime $datetime, CompanyEvent $event): void
+    {
+        $idChunks = $this->convertObjectIdToInts($companyId);
+
+        $this->clickhouse->insert(
+            $this->tableName,
+            [
+                [
+                    $datetime->format('Y-m-d'),
+                    $datetime->getTimestamp(),
+                    $event->getKey(),
+                    $idChunks[0],
+                    $idChunks[1],
+                    $idChunks[2]
+                ]
+            ],
+            [
+                'event_date', 'event_time', 'event_type', 'id_1', 'id_2', 'id_3'
+            ]
+        );
+    }
+
+    /**
+     * Получение случайной даты из диапозона
+     *
+     * @param \DateTime $startDate
+     * @param \DateTime $endDate
+     *
+     * @return \DateTime
+     *
+     * @throws \Exception
+     */
+    public function getRandomDate(\DateTime $startDate, \DateTime $endDate): \DateTime
+    {
+        $interval = new \DateInterval('P1D');
+        $period   = new \DatePeriod($startDate, $interval, $endDate);
+        $random   = new RandomIterator($period);
+
+        [$result] = iterator_to_array($random, false) ? : [null];
+
+        return $result;
+    }
+
+    /**
+     * Преобразует идентификатор mongo в три целых числа
+     *
+     * @param ObjectId $objectId
+     *
+     * @return array[int, int, int]
+     */
+    private function convertObjectIdToInts(ObjectId $objectId): array
+    {
+        $idString = (string) $objectId;
+        $idChunks = [
+            hexdec(mb_substr($idString, 0, 8)),
+            hexdec(mb_substr($idString, 8, 8)),
+            hexdec(mb_substr($idString, 16))
+        ];
+
+        return $idChunks;
+    }
+}

+ 26 - 0
src/Traits/ConfigSelfTrait.php

@@ -0,0 +1,26 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Traits;
+
+/**
+ * Трейт для хранения конфигурации в singleton-объекте
+ * @package Avolver\CompanyEventsKitchen\Traits
+ */
+trait ConfigSelfTrait
+{
+    /**
+     * @var self
+     */
+    protected static $self;
+
+    /**
+     * @return self
+     */
+    public static function getConfig(): self
+    {
+        self::$self = self::$self ?? new self();
+
+        return self::$self;
+    }
+}

+ 27 - 0
src/Traits/OutputTrait.php

@@ -0,0 +1,27 @@
+<?php
+declare(strict_types = 1);
+
+namespace Avolver\CompanyEventsKitchen\Traits;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Подмешивание объекта консольного вывода в класс
+ *
+ * @package Avolver\CompanyEventsKitchen\Traits
+ */
+trait OutputTrait
+{
+    /**
+     * @var OutputInterface
+     */
+    protected $output;
+
+    /**
+     * @param OutputInterface $output
+     */
+    public function setOutput(OutputInterface $output): void
+    {
+        $this->output = $output;
+    }
+}