feat: add validation and use opencart logger

This commit is contained in:
2025-08-16 16:59:21 +03:00
parent 9bcf32841e
commit 9f35acf399
28 changed files with 2416 additions and 143 deletions

View File

@@ -8,6 +8,7 @@ use Cart\Currency;
use Cart\Tax; use Cart\Tax;
use Openguru\OpenCartFramework\ImageTool\ImageTool; use Openguru\OpenCartFramework\ImageTool\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface; use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop'; $sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
$basePath = rtrim(DIR_APPLICATION, '/') . '/..'; $basePath = rtrim(DIR_APPLICATION, '/') . '/..';
@@ -26,6 +27,9 @@ class Controllerextensiontgshophandle extends Controller
{ {
public function index(): void public function index(): void
{ {
$this->load->model('checkout/order');
$this->session->data['language'] = $this->config->get('config_language');
$app = ApplicationFactory::create([ $app = ApplicationFactory::create([
'app_enabled' => filter_var($this->config->get('module_tgshop_status'), FILTER_VALIDATE_BOOLEAN), 'app_enabled' => filter_var($this->config->get('module_tgshop_status'), FILTER_VALIDATE_BOOLEAN),
'oc_config_tax' => $this->config->get('config_tax'), 'oc_config_tax' => $this->config->get('config_tax'),
@@ -69,40 +73,22 @@ class Controllerextensiontgshophandle extends Controller
'cache_products_main' => 60 * 10, 'cache_products_main' => 60 * 10,
]); ]);
$app->bind(Url::class, function () {
return $this->url;
});
$app->bind(Currency::class, function () {
return $this->currency;
});
$app->bind(Tax::class, function () {
return $this->tax;
});
$app->bind(OcModelCatalogProductAdapter::class, function () { $app->bind(OcModelCatalogProductAdapter::class, function () {
$this->load->model('catalog/product'); $this->load->model('catalog/product');
return new OcModelCatalogProductAdapter($this->model_catalog_product); return new OcModelCatalogProductAdapter($this->model_catalog_product);
}); });
$app->bind(ImageToolInterface::class, function () { $app->bind(Url::class, fn () => $this->url);
return new ImageTool(DIR_IMAGE, HTTPS_SERVER); $app->bind(Currency::class, fn () => $this->currency);
}); $app->bind(Tax::class, fn () => $this->tax);
$app->bind(ImageToolInterface::class, fn () => new ImageTool(DIR_IMAGE, HTTPS_SERVER));
$app->bind(Cart::class, fn () => $this->cart);
$app->bind(OcRegistryDecorator::class, fn () => new OcRegistryDecorator($this->registry));
$app->singleton(Log::class, fn () => $this->log);
$app->bind(Cart::class, function () { $app
return $this->cart; ->withLogger(fn () => new OpenCartLogAdapter($this->log, 'TeleCart'))
}); ->bootAndHandleRequest();
$app->bind(OcRegistryDecorator::class, function () {
return new OcRegistryDecorator($this->registry);
});
$this->load->model('checkout/order');
$this->session->data['language'] = $this->config->get('config_language');
$app->bootAndHandleRequest();
} }
function extractPureJs($input) function extractPureJs($input)

View File

@@ -3,7 +3,8 @@
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Openguru\\OpenCartFramework\\": "framework/", "Openguru\\OpenCartFramework\\": "framework/",
"App\\": "src/" "App\\": "src/",
"Tests\\": "tests/"
}, },
"files": [ "files": [
"framework/Support/helpers.php" "framework/Support/helpers.php"
@@ -21,13 +22,13 @@
"psr/container": "^2.0", "psr/container": "^2.0",
"ext-json": "*", "ext-json": "*",
"intervention/image": "^2.7", "intervention/image": "^2.7",
"rakit/validation": "^1.4",
"vlucas/phpdotenv": "^5.6", "vlucas/phpdotenv": "^5.6",
"guzzlehttp/guzzle": "^7.9", "guzzlehttp/guzzle": "^7.9",
"symfony/cache": "^5.4" "symfony/cache": "^5.4"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-latest", "roave/security-advisories": "dev-latest",
"phpstan/phpstan": "^2.1" "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^9.6"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
namespace Openguru\OpenCartFramework; namespace Openguru\OpenCartFramework;
use Closure;
use Dotenv\Dotenv; use Dotenv\Dotenv;
use InvalidArgumentException; use InvalidArgumentException;
use Openguru\OpenCartFramework\Config\Settings; use Openguru\OpenCartFramework\Config\Settings;
@@ -9,6 +10,7 @@ use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request; use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Logger\Logger; use Openguru\OpenCartFramework\Logger\Logger;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\Router\Router; use Openguru\OpenCartFramework\Router\Router;
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler; use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
@@ -18,6 +20,16 @@ class Application extends Container
private static array $events = []; private static array $events = [];
private array $serviceProviders = []; private array $serviceProviders = [];
private array $middlewareStack = []; private array $middlewareStack = [];
private LoggerInterface $logger;
public function __construct(array $config)
{
parent::__construct($config);
// Fallback logger
$path = rtrim($this->getConfigValue('logs.path'), '/') . '/oc_telegram_shop.log';
$this->logger = new Logger($path);
}
/** /**
* @var ExecutionTimeProfiler * @var ExecutionTimeProfiler
@@ -42,10 +54,7 @@ class Application extends Container
return $container; return $container;
}); });
$this->singleton(Logger::class, function (Container $container) { $this->singleton(LoggerInterface::class, fn () => $this->logger);
$path = $container->getConfigValue('logs.path') . '/oc_telegram_shop.log';
return new Logger($path, Logger::LEVEL_INFO);
});
$this->singleton(Settings::class, function (Container $container) { $this->singleton(Settings::class, function (Container $container) {
return new Settings($container->getConfigValue()); return new Settings($container->getConfigValue());
@@ -55,7 +64,7 @@ class Application extends Container
$dotenv->load(); $dotenv->load();
$errorHandler = new ErrorHandler( $errorHandler = new ErrorHandler(
$this->get(Logger::class), $this->get(LoggerInterface::class),
$this, $this,
); );
@@ -89,17 +98,17 @@ class Application extends Container
[$controller, $method] = $action; [$controller, $method] = $action;
if (!class_exists($controller) || !method_exists($controller, $method)) { if (! class_exists($controller) || ! method_exists($controller, $method)) {
throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method); throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method);
} }
$this->profiler->addCheckpoint('Handle Middlewares.'); $this->profiler->addCheckpoint('Handle Middlewares.');
$next = fn($req) => $this->call($controller, $method); $next = fn ($req) => $this->call($controller, $method);
foreach (array_reverse($this->middlewareStack) as $class) { foreach (array_reverse($this->middlewareStack) as $class) {
$instance = $this->get($class); $instance = $this->get($class);
$next = static fn($req) => $instance->handle($req, $next); $next = static fn ($req) => $instance->handle($req, $next);
} }
$response = $next($request); $response = $next($request);
@@ -130,4 +139,11 @@ class Application extends Container
return $this; return $this;
} }
public function withLogger(Closure $closure): Application
{
$this->logger = $closure();
return $this;
}
} }

View File

@@ -7,7 +7,7 @@ use Openguru\OpenCartFramework\Contracts\ExceptionHandlerInterface;
use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface; use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Response; use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\Logger\Logger; use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Throwable; use Throwable;
/** /**
@@ -15,10 +15,10 @@ use Throwable;
*/ */
class ErrorHandler class ErrorHandler
{ {
private $logger; private LoggerInterface $logger;
private Application $app; private Application $app;
public function __construct(Logger $logger, Application $application) public function __construct(LoggerInterface $logger, Application $application)
{ {
$this->logger = $logger; $this->logger = $logger;
$this->app = $application; $this->app = $application;

View File

@@ -4,24 +4,20 @@ namespace Openguru\OpenCartFramework\Logger;
use Throwable; use Throwable;
class Logger class Logger implements LoggerInterface
{ {
private $logFile; private $logFile;
private $logLevel; private $logLevel;
private $maxFileSize; // Максимальный размер файла в байтах private $maxFileSize; // Максимальный размер файла в байтах
public const LEVEL_INFO = 1; public function __construct($logFile, $logLevel = LoggerInterface::LEVEL_INFO, $maxFileSize = 1048576)
public const LEVEL_WARNING = 2;
public const LEVEL_ERROR = 3;
public function __construct($logFile, $logLevel = self::LEVEL_INFO, $maxFileSize = 1048576)
{ {
$this->logFile = $logFile; $this->logFile = $logFile;
$this->logLevel = $logLevel; $this->logLevel = $logLevel;
$this->maxFileSize = $maxFileSize; $this->maxFileSize = $maxFileSize;
} }
public function log($message, $level = self::LEVEL_INFO) public function log($message, $level = LoggerInterface::LEVEL_INFO)
{ {
if ($level < $this->logLevel) { if ($level < $this->logLevel) {
return; // Не логируем, если уровень ниже установленного return; // Не логируем, если уровень ниже установленного
@@ -39,11 +35,11 @@ class Logger
private function getLevelString($level): string private function getLevelString($level): string
{ {
switch ($level) { switch ($level) {
case self::LEVEL_INFO: case LoggerInterface::LEVEL_INFO:
return 'INFO'; return 'INFO';
case self::LEVEL_WARNING: case LoggerInterface::LEVEL_WARNING:
return 'WARNING'; return 'WARNING';
case self::LEVEL_ERROR: case LoggerInterface::LEVEL_ERROR:
return 'ERROR'; return 'ERROR';
default: default:
return 'UNKNOWN'; return 'UNKNOWN';
@@ -52,17 +48,17 @@ class Logger
public function info(string $message): void public function info(string $message): void
{ {
$this->log($message, self::LEVEL_INFO); $this->log($message, LoggerInterface::LEVEL_INFO);
} }
public function warning(string $message): void public function warning(string $message): void
{ {
$this->log($message, self::LEVEL_WARNING); $this->log($message, LoggerInterface::LEVEL_WARNING);
} }
public function error(string $message): void public function error(string $message): void
{ {
$this->log($message, self::LEVEL_ERROR); $this->log($message, LoggerInterface::LEVEL_ERROR);
} }
private function rotateLogs(): void private function rotateLogs(): void

View File

@@ -0,0 +1,22 @@
<?php
namespace Openguru\OpenCartFramework\Logger;
use Throwable;
interface LoggerInterface
{
public const LEVEL_INFO = 1;
public const LEVEL_WARNING = 2;
public const LEVEL_ERROR = 3;
public function log($message, $level = self::LEVEL_INFO);
public function info(string $message): void;
public function warning(string $message): void;
public function error(string $message): void;
public function logException(Throwable $exception): void;
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Openguru\OpenCartFramework\Logger;
use Log;
use Throwable;
class OpenCartLogAdapter implements LoggerInterface
{
private Log $ocLogger;
private string $namespace;
public function __construct(Log $ocLogger, string $namespace)
{
$this->ocLogger = $ocLogger;
$this->namespace = $namespace;
}
public function log($message, $level = self::LEVEL_INFO): void
{
$this->ocLogger->write(
sprintf(
"[%s] [%s] %s\n",
$this->namespace,
$this->getLevelString($level),
$message
)
);
}
public function info(string $message): void
{
$this->log($message);
}
public function warning(string $message): void
{
$this->log($message, self::LEVEL_WARNING);
}
public function error(string $message): void
{
$this->log($message, self::LEVEL_ERROR);
}
public function logException(Throwable $exception): void
{
$this->error(
sprintf(
"Fatal error %s in %s on line %d\n%s",
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
)
);
}
private function getLevelString($level): string
{
switch ($level) {
case LoggerInterface::LEVEL_INFO:
return 'INFO';
case LoggerInterface::LEVEL_WARNING:
return 'WARNING';
case LoggerInterface::LEVEL_ERROR:
return 'ERROR';
default:
return 'UNKNOWN';
}
}
}

View File

@@ -3,17 +3,14 @@
namespace Openguru\OpenCartFramework\Telegram; namespace Openguru\OpenCartFramework\Telegram;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Openguru\OpenCartFramework\Logger\Logger;
class TelegramService class TelegramService
{ {
private Logger $logger;
private Client $client; private Client $client;
private ?string $botToken; private ?string $botToken;
public function __construct(Logger $logger, ?string $botToken = null) public function __construct(?string $botToken = null)
{ {
$this->logger = $logger;
$this->botToken = $botToken; $this->botToken = $botToken;
$this->client = $this->createGuzzleClient("https://api.telegram.org/bot{$botToken}/"); $this->client = $this->createGuzzleClient("https://api.telegram.org/bot{$botToken}/");
} }

View File

@@ -4,7 +4,6 @@ namespace Openguru\OpenCartFramework\Telegram;
use Openguru\OpenCartFramework\Application; use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Container\ServiceProvider; use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Logger\Logger;
class TelegramServiceProvider extends ServiceProvider class TelegramServiceProvider extends ServiceProvider
{ {
@@ -12,10 +11,7 @@ class TelegramServiceProvider extends ServiceProvider
{ {
$this->container->singleton(TelegramService::class, function (Application $app) { $this->container->singleton(TelegramService::class, function (Application $app) {
$botToken = $app->getConfigValue('telegram.bot_token'); $botToken = $app->getConfigValue('telegram.bot_token');
return new TelegramService( return new TelegramService($botToken);
$app->get(Logger::class),
$botToken,
);
}); });
$this->container->singleton(SignatureValidator::class, function (Application $app) { $this->container->singleton(SignatureValidator::class, function (Application $app) {

View File

@@ -0,0 +1,43 @@
<?php
namespace Openguru\OpenCartFramework\Validator;
class ErrorBag
{
private array $errors;
public function __construct(array $errors = [])
{
$this->errors = $errors;
}
public function count(): int
{
return count($this->errors);
}
public function put(string $field, string $message): void
{
$this->errors[$field][] = $message;
}
public function first(): array
{
foreach ($this->errors as $error) {
return $error;
}
}
public function firstOfAll(): array
{
$result = [];
foreach ($this->errors as $field => $errors) {
if (count($errors) > 0) {
$result[$field] = $errors[0];
}
}
return $result;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Openguru\OpenCartFramework\Validator;
use Exception;
class ValidationRuleNotFoundException extends Exception
{
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Openguru\OpenCartFramework\Validator\ValidationRules;
use Closure;
class Email implements ValidationRuleInterface
{
public function validate(string $field, array $input, Closure $fail): void
{
$email = $input[$field] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$fail('Email is not valid');
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Openguru\OpenCartFramework\Validator\ValidationRules;
use Closure;
class Required implements ValidationRuleInterface
{
public function validateRequired($value): bool
{
if (is_null($value)) {
return false;
}
if (is_string($value) && trim($value) === '') {
return false;
}
if (is_countable($value) && count($value) < 1) {
return false;
}
return true;
}
public function validate(string $field, array $input, Closure $fail): void
{
$value = $input[$field] ?? null;
if ($this->validateRequired($value) === false) {
$fail(':field is required');
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Openguru\OpenCartFramework\Validator\ValidationRules;
use Closure;
interface ValidationRuleInterface
{
public function validate(string $field, array $input, Closure $fail): void;
}

View File

@@ -2,23 +2,89 @@
namespace Openguru\OpenCartFramework\Validator; namespace Openguru\OpenCartFramework\Validator;
class Validator use Openguru\OpenCartFramework\Validator\ValidationRules\ValidationRuleInterface;
{
private $input;
private $rules;
public function __construct(array $input, array $rules) class Validator implements ValidatorInterface
{
private array $input;
private array $rules;
private ErrorBag $errors;
private array $customMessages;
private array $fieldNames;
public function __construct(array $validationRules = [], array $customMessages = [])
{
$this->validationRules = $validationRules;
$this->customMessages = $customMessages;
}
public function make(array $input, array $rules, array $fieldNames = []): Validator
{ {
$this->input = $input; $this->input = $input;
$this->rules = $rules; $this->rules = $rules;
$this->errors = new ErrorBag();
$this->fieldNames = $fieldNames;
return $this;
} }
public function validate(): bool /**
* @throws ValidationRuleNotFoundException
*/
private function validate(): void
{ {
foreach ($this->rules as $name => $rule) { foreach ($this->rules as $field => $rule) {
$components = explode('|', $rule); $parts = $this->extractParts($rule);
foreach ($parts as $part) {
$validationRule = $this->resolveRuleClass($part);
$validationRule->validate($field, $this->input, function ($message) use ($part, $field) {
$this->errors->put($field, $this->formatMessage($message, $part, $field));
});
}
}
} }
return true; /**
* @throws ValidationRuleNotFoundException
*/
public function fails(): bool
{
$this->validate();
return $this->errors->count() > 0;
}
private function extractParts($rule): array
{
return explode('|', $rule);
}
/**
* @throws ValidationRuleNotFoundException
*/
private function resolveRuleClass($part): ValidationRuleInterface
{
$lazyClass = $this->validationRules[$part] ?? null;
if ($lazyClass === null) {
throw new ValidationRuleNotFoundException('Unknown rule "' . $part);
}
return $lazyClass();
}
public function getErrors(): ErrorBag
{
return $this->errors;
}
private function formatMessage(string $message, string $rule, string $field): string
{
$message = $this->customMessages[$rule] ?? $message;
$field = $this->fieldNames[$field] ?? $field;
return str_replace(':field', $field, $message);
} }
} }

View File

@@ -0,0 +1,15 @@
<?php
namespace Openguru\OpenCartFramework\Validator;
interface ValidatorInterface
{
public function make(array $input, array $rules, array $fieldNames = []): ValidatorInterface;
/**
* @throws ValidationRuleNotFoundException
*/
public function fails(): bool;
public function getErrors(): ErrorBag;
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Openguru\OpenCartFramework\Validator;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Validator\ValidationRules\Email;
use Openguru\OpenCartFramework\Validator\ValidationRules\Required;
class ValidatorServiceProvider extends ServiceProvider
{
protected function rules(): array
{
return [
'required' => fn () => $this->container->get(Required::class),
'email' => fn () => $this->container->get(Email::class),
];
}
public function register(): void
{
$this->container->bind(ValidatorInterface::class, function () {
$langCode = $this->container->getConfigValue('', 'ru');
$translationsFile = __DIR__ . "/translations/$langCode.php";
if (! file_exists($translationsFile)) {
$translationsFile = __DIR__ . "/lang/en.php";
}
return new Validator($this->rules(), require $translationsFile);
});
}
}

View File

@@ -0,0 +1,6 @@
<?php
return [
'required' => ':field обязательно для заполнения.',
'email' => 'Неверный e-mail.',
];

View File

@@ -10,6 +10,7 @@ use Openguru\OpenCartFramework\Router\RouteServiceProvider;
use Openguru\OpenCartFramework\Support\Arr; use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider; use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramValidateInitDataMiddleware; use Openguru\OpenCartFramework\Telegram\TelegramValidateInitDataMiddleware;
use Openguru\OpenCartFramework\Validator\ValidatorServiceProvider;
class ApplicationFactory class ApplicationFactory
{ {
@@ -24,6 +25,7 @@ class ApplicationFactory
RouteServiceProvider::class, RouteServiceProvider::class,
AppServiceProvider::class, AppServiceProvider::class,
TelegramServiceProvider::class, TelegramServiceProvider::class,
ValidatorServiceProvider::class,
]) ])
->withMiddlewares([ ->withMiddlewares([
TelegramValidateInitDataMiddleware::class, TelegramValidateInitDataMiddleware::class,

View File

@@ -3,12 +3,18 @@
namespace App\Decorators; namespace App\Decorators;
use Cart\Cart; use Cart\Cart;
use Cart\Currency;
use Config;
use Loader; use Loader;
use Registry; use Registry;
use Session;
/** /**
* @property Loader $load * @property Loader $load
* @property Cart $cart * @property Cart $cart
* @property Session $session
* @property Currency $currency
* @property Config $config
* @property \ModelCatalogProduct $model_catalog_product * @property \ModelCatalogProduct $model_catalog_product
*/ */
class OcRegistryDecorator class OcRegistryDecorator

View File

@@ -2,7 +2,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Rakit\Validation\ErrorBag; use Openguru\OpenCartFramework\Validator\ErrorBag;
use RuntimeException; use RuntimeException;
use Throwable; use Throwable;

View File

@@ -9,16 +9,16 @@ use Exception;
use Openguru\OpenCartFramework\Config\Settings; use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request; use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Logger\Logger; use Openguru\OpenCartFramework\Logger\LoggerInterface;
use RuntimeException; use RuntimeException;
class ProductsHandler class ProductsHandler
{ {
private Settings $settings; private Settings $settings;
private ProductsService $productsService; private ProductsService $productsService;
private Logger $logger; private LoggerInterface $logger;
public function __construct(Settings $settings, ProductsService $productsService, Logger $logger) public function __construct(Settings $settings, ProductsService $productsService, LoggerInterface $logger)
{ {
$this->settings = $settings; $this->settings = $settings;
$this->productsService = $productsService; $this->productsService = $productsService;

View File

@@ -1,16 +1,19 @@
<?php <?php
declare(strict_types=1);
namespace App\Services; namespace App\Services;
use App\Decorators\OcRegistryDecorator; use App\Decorators\OcRegistryDecorator;
use App\Exceptions\OrderValidationFailedException; use App\Exceptions\OrderValidationFailedException;
use Exception; use Exception;
use Openguru\OpenCartFramework\Config\Settings; use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Logger\Logger; use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface; use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Openguru\OpenCartFramework\Support\Arr; use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\TelegramService; use Openguru\OpenCartFramework\Telegram\TelegramService;
use Rakit\Validation\Validator; use Openguru\OpenCartFramework\Validator\ValidationRuleNotFoundException;
use Openguru\OpenCartFramework\Validator\ValidatorInterface;
use RuntimeException; use RuntimeException;
class OrderCreateService class OrderCreateService
@@ -20,7 +23,8 @@ class OrderCreateService
private OcRegistryDecorator $oc; private OcRegistryDecorator $oc;
private Settings $settings; private Settings $settings;
private TelegramService $telegramService; private TelegramService $telegramService;
private Logger $logger; private LoggerInterface $logger;
private ValidatorInterface $validator;
public function __construct( public function __construct(
ConnectionInterface $database, ConnectionInterface $database,
@@ -28,7 +32,8 @@ class OrderCreateService
OcRegistryDecorator $registry, OcRegistryDecorator $registry,
Settings $settings, Settings $settings,
TelegramService $telegramService, TelegramService $telegramService,
Logger $logger LoggerInterface $logger,
ValidatorInterface $validator
) { ) {
$this->database = $database; $this->database = $database;
$this->cartService = $cartService; $this->cartService = $cartService;
@@ -36,11 +41,16 @@ class OrderCreateService
$this->settings = $settings; $this->settings = $settings;
$this->telegramService = $telegramService; $this->telegramService = $telegramService;
$this->logger = $logger; $this->logger = $logger;
$this->validator = $validator;
} }
public function create(array $data, array $meta = []): void public function create(array $data, array $meta = []): void
{ {
try {
$this->validate($data); $this->validate($data);
} catch (ValidationRuleNotFoundException $e) {
throw new RuntimeException($e->getMessage());
}
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$storeId = $this->settings->get('oc_store_id'); $storeId = $this->settings->get('oc_store_id');
@@ -170,23 +180,22 @@ class OrderCreateService
$this->sendNotifications($orderData, $data['tgData']); $this->sendNotifications($orderData, $data['tgData']);
} }
/**
* @throws ValidationRuleNotFoundException
*/
private function validate(array $data): void private function validate(array $data): void
{ {
$validator = new Validator(); $v = $this->validator->make($data, $this->makeValidationRulesFromSettings(), [
'firstName' => 'Имя',
$validation = $validator->make($data, [ 'lastName' => 'Фамилия',
'firstName' => 'required', 'email' => 'E-mail',
'lastName' => 'required', 'phone' => 'Номер телефона',
'email' => 'required|email', 'address' => 'Адрес доставки',
'phone' => 'required', 'comment' => 'Комментарий',
'address' => 'required',
'comment' => 'required',
]); ]);
$validation->validate(); if ($v->fails()) {
throw new OrderValidationFailedException($v->getErrors());
if ($validation->fails()) {
throw new OrderValidationFailedException($validation->errors());
} }
} }
@@ -232,4 +241,16 @@ class OrderCreateService
} }
} }
} }
private function makeValidationRulesFromSettings(): array
{
return [
'firstName' => 'required',
'lastName' => 'required',
'email' => 'required|email',
'phone' => 'required',
'address' => 'required',
'comment' => 'required',
];
}
} }

View File

@@ -1,7 +1,6 @@
<?php <?php
return [ return [
'config_timezone' => 'UTC', 'config_timezone' => 'UTC',
'lang' => 'en-gb', 'lang' => 'en-gb',

View File

@@ -0,0 +1,19 @@
<?php
namespace Tests;
use Openguru\OpenCartFramework\Application;
class TestCase extends \PHPUnit\Framework\TestCase
{
protected Application $app;
protected function setUp(): void
{
parent::setUp();
$config = [];
$this->app = new Application($config);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Tests\Validator;
use Openguru\OpenCartFramework\Validator\ErrorBag;
use Tests\TestCase;
class ErrorBagTest extends TestCase
{
public function testFirstOfAll(): void
{
$errorBag = new ErrorBag([
'foo' => ['one', 'two'],
'bar' => ['three', 'four'],
]);
$expected = [
'foo' => 'one',
'bar' => 'three',
];
$this->assertEquals($expected, $errorBag->firstOfAll());
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Tests\Validator;
use Openguru\OpenCartFramework\Validator\ValidationRuleNotFoundException;
use Openguru\OpenCartFramework\Validator\ValidationRules\Required;
use Openguru\OpenCartFramework\Validator\Validator;
use Tests\TestCase;
/**
* @coversDefaultClass Validator
*/
class ValidatorTest extends TestCase
{
/**
* @covers Validator::fails()
* @covers Validator::make()
*/
public function testValidateBasic(): void
{
$validator = new Validator();
$this->assertFalse($validator->make([], [])->fails());
}
/**
* @covers Validator::fails()
*/
public function testThrowExceptionIfRuleNotFound(): void
{
$validator = new Validator();
$v = $validator->make([], [
'field' => 'not_exists_rule',
]);
$this->expectException(ValidationRuleNotFoundException::class);
$v->fails();
}
public function testRequiredForNonExistentField(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['xyz' => 'abc'], ['foo' => 'required']);
$this->assertTrue($v->fails());
$this->assertEquals(1, $v->getErrors()->count());
$this->assertEquals(['foo is required'], $v->getErrors()->first());
}
public function testRequiredForEmptyField(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => ''], ['foo' => 'required']);
$this->assertTrue($v->fails());
$this->assertEquals(1, $v->getErrors()->count());
$this->assertEquals(['foo is required'], $v->getErrors()->first());
}
public function testRequiredForFalseValue(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => false], ['foo' => 'required']);
$this->assertFalse($v->fails());
}
public function testRequiredForNullValue(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => null], ['foo' => 'required']);
$this->assertTrue($v->fails());
$this->assertEquals(1, $v->getErrors()->count());
$this->assertEquals(['foo is required'], $v->getErrors()->first());
}
public function testRequiredForZero(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => 0], ['foo' => 'required']);
$this->assertFalse($v->fails());
}
public function testRequiredForZeroString(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => "0"], ['foo' => 'required']);
$this->assertFalse($v->fails());
}
public function testCustomValidationMessages(): void
{
$customMessages = [
'required' => ':field is very required',
];
$validator = new Validator([
'required' => fn () => new Required(),
], $customMessages);
$v = $validator->make(['foo' => ''], ['foo' => 'required']);
$this->assertTrue($v->fails());
$this->assertEquals(1, $v->getErrors()->count());
$this->assertEquals(['foo is very required'], $v->getErrors()->first());
}
public function testCustomFieldNames(): void
{
$validator = new Validator([
'required' => fn () => new Required(),
]);
$v = $validator->make(['foo' => ''], [
'foo' => 'required'
], [
'foo' => 'My Field'
]);
$this->assertTrue($v->fails());
$this->assertEquals(1, $v->getErrors()->count());
$this->assertEquals(['My Field is required'], $v->getErrors()->first());
}
}