Commit 70c45965 authored by Telman Mazhlumov's avatar Telman Mazhlumov

Complete alpha version IblockElement driver

parent 7ab5678a
#!/usr/bin/env php
<?php
use Defa\BxDal\Console\CodeGenerator;
$command = new Symfony\Component\Console\Application();
$command->add(new CodeGenerator());
$command->run();
\ No newline at end of file
......@@ -20,10 +20,11 @@
}
],
"require": {
"php" : "~7.0",
"php": "^7.1.3",
"ext-json": "*",
"tightenco/collect": "5.5.*",
"nette/php-generator": "^3.0",
"tightenco/collect": "5.5.*"
"symfony/console": "4.*"
},
"require-dev": {
"phpunit/phpunit" : "^5.4"
......@@ -32,5 +33,8 @@
"psr-4": {
"Defa\\BxDal\\": "src"
}
}
},
"bin": [
"bin/bxdal"
]
}
\ No newline at end of file
......@@ -12,14 +12,9 @@ use Defa\BxDal\Interfaces\IDataDriver;
abstract class Driver implements IDataDriver
{
/**
* @var string Класс-valueObject (проще говоря сама модель сущности)
* @var string модель сущности
*/
const VALUE_OBJECT_CLASS = '';
/**
* @var string $primaryField
*/
protected $primaryField = 'id';
const ENTITY_CLASS = '';
/**
* @var string последний добавленный идентификатор
......@@ -27,17 +22,19 @@ abstract class Driver implements IDataDriver
protected $lastAddedUniqueID;
/**
* Возвращает объект, который сгенерирует нужный текущему драйверу ValueObject
* Возвращает объект, который сгенерирует нужный текущему драйверу Entity
*
* @return ValueObject
* @return Entity
*/
public static function createVO(): ValueObject
public static function createEntity(): Entity
{
if (class_exists(static::VALUE_OBJECT_CLASS)) {
return new {static::VALUE_OBJECT_CLASS};
if (class_exists(static::ENTITY_CLASS)) {
return new {
static::ENTITY_CLASS
};
}
throw new \RuntimeException('VO class not find');
throw new \RuntimeException('Entity class not find');
}
/**
......
......@@ -3,13 +3,13 @@
namespace Defa\BxDal\Base;
use Defa\BxDal\Interfaces\IValueObject;
use Defa\BxDal\Interfaces\IEntity;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;
abstract class ValueObject implements IValueObject
abstract class Entity implements IEntity
{
/**
* @var array $publicProperties
......@@ -91,9 +91,9 @@ abstract class ValueObject implements IValueObject
* Инициализирует поля объекта из массива
*
* @param array $rawData
* @return IValueObject
* @return IEntity
*/
abstract public function initFromArray(array $rawData): IValueObject;
abstract public function initFromArray(array $rawData): IEntity;
/**
* Конвертирует текущий объект в массив с ключами и значениями нужного формата
......
<?php
/**
* Class ValueObjectGenerator
*/
class CodeGenerator
{
private function init(): array
{
}
private function collectModelsData(): array
{
}
private function collectVOData(): array
{
}
public function generate()
{
}
}
\ No newline at end of file
<?php
namespace Defa\BxDal;
use Defa\BxDal\Drivers\IBlockElement\CodeGenerator;
use Defa\BxDal\Drivers\IBlockElement\IBlockElement;
use Defa\BxDal\Drivers\IBlockElement\ValueObject;
use Defa\BxDal\Drivers\IBlockElement\Entity;
/**
* Class Config
......@@ -19,20 +20,21 @@ class Config
],
'generation' => [
'rootModelsPath' => __DIR__ . '/../../../../BxAbstractLayer/Models',
'rootVOPath' => __DIR__ . '/../../../../BxAbstractLayer/ValueObjects',
'rootEntityPath' => __DIR__ . '/../../../../BxAbstractLayer/Entities',
'byDrivers' => [
IBlockElement::class => [
'handler' => CodeGenerator::class,
'model' => [
'classPattern' => '%s',
'namespace' => 'Defa\BxDal\Models\IblockElement\%s\%s',
'filepath' => '/IblockElement/%s/%s',
'extends' => __CLASS__
'extends' => IBlockElement::class
],
'vo' => [
'classPattern' => '%sVO',
'namespace' => 'Defa\BxDal\ValueObjects\IblockElement\%s\%s',
'entity' => [
'classPattern' => '%sEntity',
'namespace' => 'Defa\BxDal\Entities\IblockElement\%s\%s',
'filepath' => '/IblockElement/%s/%s',
'extends' => ValueObject::class
'extends' => Entity::class
]
]
]
......
<?php
namespace Defa\BxDal\Console;
use Defa\BxDal\Config;
use Defa\BxDal\Interfaces\ICodeGenerator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CodeGenerator
*/
class CodeGenerator extends Command
{
/**
* @var array $codeGeneratorHandlers
*/
private $codeGeneratorHandlers;
/**
* @var array $localConfig
*/
private $localConfig;
/**
* Configures the current command.
*/
protected function configure()
{
$this->setDescription('Generates models, entity, etc. Uses driver handler');
$this->localConfig = Config::get('generation');
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int|null null or 0 if everything went fine, or an error code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$formatter = $this->getHelper('formatter');
$this->collectGenerators();
$output->writeln(['Collect generator handlers']);
if (!empty($this->codeGeneratorHandlers)) {
$message = ['Success', 'Found ' . count($this->codeGeneratorHandlers) . ' handlers', '=========================='];
$formatterBlock = $formatter->formatBlock($message, 'success');
$output->writeln($formatterBlock);
}
$output->writeln(['Start generate', '==========================', '']);
foreach ($this->codeGeneratorHandlers as $generatorParams) {
$output->write(["Run: {$generatorParams['handler']}"]);
try {
$this->generate($generatorParams);
$message = ['Success', '==========================', ''];
$formatterBlock = $formatter->formatBlock($message, 'success');
$output->writeln($formatterBlock);
} catch (\Throwable $err) {
$message = ['Error', $err->getMessage(), '==========================', ''];
$formatterBlock = $formatter->formatBlock($message, 'error');
$output->writeln($formatterBlock);
}
}
$output->writeln(['Finish']);
}
/**
*
*/
private function collectGenerators(): void
{
foreach ($this->localConfig['byDriver'] as $driverClass => $generatorParams) {
if (class_exists($generatorParams['handler']) && in_array(ICodeGenerator::class, class_implements($generatorParams['handler']), true)) {
$this->codeGeneratorHandlers[] = [
'handler' => $generatorParams['handler'],
'config' => $generatorParams,
'rootPath' => [
$this->localConfig['rootModelsPath'],
$this->localConfig['rootEntityPath']
]
];
}
}
}
/**
* @param array $generatorParams
*/
private function generate(array $generatorParams)
{
/** @var ICodeGenerator $generator */
$generator = new {
$generatorParams['handler']
};
$generator
->setConfig($generatorParams['config'])
->setRootPath(...$generatorParams['rootPath'])
->generate();
}
}
\ No newline at end of file
<?php
namespace Defa\BxDal\Drivers\IBlockElement;
use Defa\BxDal\Interfaces\ICodeGenerator;
use Nette\PhpGenerator\PhpNamespace;
/**
* Class CodeGenerator
* @package Defa\BxDal\Drivers\IBlockElement
*/
class CodeGenerator implements ICodeGenerator
{
/**
* @var array $modelConfig
*/
private $modelConfig;
/**
* @var array $entityConfig
*/
private $entityConfig;
/**
* @var string $modelRootPath
*/
private $modelRootPath;
/**
* @var string $entityRootPath
*/
private $entityRootPath;
/**
* @param array $config
* @return ICodeGenerator
*/
public function setConfig(array $config): ICodeGenerator
{
if (!empty($config['model'])) {
$this->modelConfig = $config['model'];
}
if (!empty($config['entity'])) {
$this->entityConfig = $config['entity'];
}
return $this;
}
/**
* @param string $modelRoot
* @param string|null $entityRoot
* @return ICodeGenerator
*/
public function setRootPath(string $modelRoot, ?string $entityRoot): ICodeGenerator
{
if ($entityRoot !== null) {
$this->modelRootPath = $modelRoot;
$this->entityRootPath = $entityRoot;
return $this;
}
throw new \RuntimeException('Не задан параметр "entityRoot"');
}
/**
* Execute generation process
*
* @return void
*/
public function generate(): void
{
if (CModule::IncludeModule('iblock')) {
$iblocks = $this->collectIblocks();
foreach ($iblocks as $iblockId => $iblockParams) {
$fields = $this->collectIblockFields($iblockId);
$props = $this->collectIblockProperties($iblockId);
$this->generateModelClass(...$this->prepareModelData($iblockParams));
$this->generateEntityClass(...$this->prepareEntityData($iblockParams, $fields, $props));
}
} else {
throw new \RuntimeException('Module "iblock" not loaded');
}
}
/**
* @return array
*/
private function collectIblocks(): array
{
$returnData = [];
$resIblocks = CIBlock::GetList([], ['ACTIVE' => 'Y', '!CODE' => false]);
while ($tmpIblock = $resIblocks->Fetch()) {
$returnData[$tmpIblock['ID']] = [
'id' => (int)$tmpIblock['ID'],
'typeCode' => $tmpIblock['IBLOCK_TYPE_ID'],
'code' => $tmpIblock['CODE'],
];
}
return $returnData;
}
/**
* @param int $iblockId
* @return array
*/
private function collectIblockFields(int $iblockId): array
{
$returnFields = [];
$resElem = CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y'], false, ['nTopCount' => 1])->Fetch();
while ($tmpFields = $resElem->Fetch()) {
if (!empty($tmpFields['ID'])) {
foreach ($tmpFields as $fieldName => $fieldVal) {
$returnFields[] = Mapper::convertAttributeFromBx($fieldName);
}
}
}
return $returnFields;
}
/**
* @param int $iblockId
* @return array
*/
private function collectIblockProperties(int $iblockId): array
{
$returnProps = [];
$resProps = CIBlock::GetProperties($iblockId);
while ($tmpProp = $resProps->Fetch()) {
if ($tmpProp['ACTIVE']) {
$propCode = Mapper::PROP_INNER_PREFIX . $tmpProp['CODE'];
$returnProps[] = Mapper::convertAttributeFromBx($propCode);
}
}
return $returnProps;
}
/**
* @param array $iblockParams
* @return array
*/
private function prepareModelData(array $iblockParams): array
{
$iblockID = $iblockParams['id'];
$iblockCode = $iblockParams['code'];
$iblockType = $iblockParams['typeCode'];
$entityClassName = sprintf($this->entityConfig['classPattern'], ucfirst(toCamelCase($iblockCode)));
$entityNamespace = sprintf($this->entityConfig['namespace'], ucfirst(toCamelCase($iblockType)), ucfirst(toCamelCase($iblockCode)));
$entityClass = "{$entityNamespace}{$entityClassName}";
return [
$entityClass,
$iblockID,
$iblockType,
$iblockCode,
];
}
/**
* @param string $entityClass
* @param int $iblockId
* @param string $iblockType
* @param string $iblockCode
*/
private function generateModelClass(string $entityClass, int $iblockId, string $iblockType, string $iblockCode): void
{
$modelClassName = sprintf($this->modelConfig['classPattern'], ucfirst(toCamelCase($iblockCode)));
$modelNamespace = sprintf($this->modelConfig['namespace'], ucfirst(toCamelCase($iblockType)), ucfirst(toCamelCase($iblockCode)));
$modelClassFilePath = sprintf($this->modelConfig['filepath'], ucfirst(toCamelCase($iblockType)), ucfirst(toCamelCase($iblockCode)));
$modelClassFilePath = $this->modelRootPath . $modelClassFilePath;
$namespace = new PhpNamespace($modelNamespace);
$modelClass = $namespace->addClass($modelClassName);
$modelClass->addExtend($this->modelConfig['extends']);
$addedProp = $modelClass->addProperty('iblockID', $iblockId);
$addedProp->setVisibility('protected');
$modelClass->addConstant('ENTITY_CLASS', $entityClass);
$this->writeToFile($modelClassFilePath, $namespace);
}
/**
* @param array $iblockParams
* @param array $entityFields
* @param array $entityProps
* @return array
*/
private function prepareEntityData(array $iblockParams, array $entityFields, array $entityProps): array
{
$iblockCode = $iblockParams['code'];
$iblockType = $iblockParams['typeCode'];
$entityClassFilePath = sprintf($this->entityConfig['filepath'], ucfirst(toCamelCase($iblockType)), ucfirst(toCamelCase($iblockCode)));
$entityClassFilePath = $this->entityRootPath . $entityClassFilePath;
$entityClassName = sprintf($this->entityConfig['classPattern'], ucfirst(toCamelCase($iblockCode)));
$entityNamespace = sprintf($this->entityConfig['namespace'], ucfirst(toCamelCase($iblockType)), ucfirst(toCamelCase($iblockCode)));
return [
$entityClassFilePath,
$entityNamespace,
$entityClassName,
$entityFields,
$entityProps
];
}
/**
* @param string $entityClassFilePath
* @param string $entityNamespace
* @param string $entityClassName
* @param array $entityFields
* @param array $entityProps
*/
private function generateEntityClass(string $entityClassFilePath, string $entityNamespace, string $entityClassName, array $entityFields, array $entityProps): void
{
$namespace = new PhpNamespace($entityNamespace);
$entityClass = $namespace->addClass($entityClassName);
$entityClass->addExtend($this->entityConfig['extends']);
foreach ($entityFields as $field) {
$addedField = $entityClass->addProperty($field);
$addedField->setVisibility('public');
}
foreach ($entityProps as $prop) {
$addedProp = $entityClass->addProperty($prop);
$addedProp->setVisibility('public');
}
$this->writeToFile($entityClassFilePath, $namespace);
}
/**
* @param string $path
* @param string $body
*/
private function writeToFile(string $path, string $body): void
{
$segments = explode('/', $path);
$fileName = array_pop($segments) . '.php';
$dirPath = implode('/', $segments);
if (!file_exists($dirPath) && !mkdir($conDirectory = $path, 0755, true) && !is_dir($conDirectory)) {
throw new \RuntimeException(sprintf('Не удалось создать директорию: "%s"', $conDirectory));
}
$fileName = realpath($dirPath) . '/' . $fileName;
if (file_put_contents($fileName, "<?php\n" . $body) === 0) {
throw new \RuntimeException('Не удалось записать контроллер ' . $fileName);
}
chmod($fileName, 0755);
}
}
\ No newline at end of file
......@@ -5,10 +5,10 @@ namespace Defa\BxDal\Drivers\IBlockElement;
use Defa\BxDal\Base\Traits\HasFields;
use Defa\BxDal\Base\Traits\HasProperties;
use \Defa\BxDal\Base\ValueObject as BaseValueObject;
use Defa\BxDal\Interfaces\IValueObject;
use \Defa\BxDal\Base\Entity as BaseEntity;
use Defa\BxDal\Interfaces\IEntity;
abstract class ValueObject extends BaseValueObject
abstract class Entity extends BaseEntity
{
use HasFields,
HasProperties;
......@@ -37,11 +37,33 @@ abstract class ValueObject extends BaseValueObject
* Инициализирует поля объекта из массива
*
* @param array $rawData
* @return IValueObject
* @return IEntity
*/
public function initFromArray(array $rawData): IValueObject
public function initFromArray(array $rawData): IEntity
{
// TODO: Implement initFromArray() method.
if (!empty($rawData)) {
$convertedFields = Mapper::convertAttributeValuesFromBx($rawData);
foreach ($convertedFields as $fieldName => $fieldVal) {
if ($fieldName === 'props') {
foreach ($fieldVal as $propName => $propVal) {
$needPropName = $propName;
if (in_array($needPropName, $this->publicProperties, true)) {
$this->{$needPropName} = $fieldVal;
}
}
} else {
$needFieldName = $fieldName;
if (in_array($needFieldName, $this->publicProperties, true)) {
$this->{$needFieldName} = $fieldVal;
}
}
}