Skip to content

Commit

Permalink
NEXT-0000 - POC php attributes for entities
Browse files Browse the repository at this point in the history
  • Loading branch information
OliverSkroblin committed Apr 26, 2024
1 parent 4a2e40f commit fbab73c
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 1 deletion.
36 changes: 36 additions & 0 deletions custom/scripts/foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Scripts\Examples;

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Uuid\Uuid;

require_once __DIR__ . '/examples/base-script.php';

$env = 'dev'; // by default, kernel gets booted in dev

$kernel = require __DIR__ . '/boot/boot.php';

class Main extends BaseScript
{
public function run()
{
$definition = $this->getContainer()->get('my_entity.definition');

/** @var EntityRepository $repo */
$repo = $this->getContainer()->get('my_entity.repository');

$data = [
['id' => Uuid::randomHex(), 'name' => 'foo']
];

$repo->upsert($data, Context::createCLIContext());

$entities = $repo->search(new Criteria(), Context::createCLIContext());
}
}


(new Main($kernel))->run();
15 changes: 15 additions & 0 deletions src/Core/Framework/DataAbstractionLayer/Attribute/Entity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Shopware\Core\Framework\DataAbstractionLayer\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS)]
class Entity
{
/**
* @var class-string
*/
public string $class;

public function __construct(public string $name) {}
}

16 changes: 16 additions & 0 deletions src/Core/Framework/DataAbstractionLayer/Attribute/Field.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Shopware\Core\Framework\DataAbstractionLayer\Attribute;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Field
{
public bool $nullable;

public function __construct(
public string $type,
public bool $translated = false,
public string $name = '',
public bool $searchable = false
) {}
}
9 changes: 9 additions & 0 deletions src/Core/Framework/DataAbstractionLayer/Attribute/Primary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Shopware\Core\Framework\DataAbstractionLayer\Attribute;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Primary
{
public function __construct(public string $type, public string $name = '') {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Shopware\Core\Framework\DataAbstractionLayer;

use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;

class AttributeEntityDefinition extends EntityDefinition
{
public function __construct(private readonly array $meta = [])
{
parent::__construct();
}

public function getEntityClass(): string
{
return $this->meta['entity_class'];
}

public function getEntityName(): string
{
return $this->meta['entity_name'];
}

public function getDefaults(): array
{
// todo add defaults support
return parent::getDefaults(); // TODO: Change the autogenerated stub
}

protected function getParentDefinitionClass(): ?string
{
// todo add `entity_name` support
return parent::getParentDefinitionClass();
}

protected function defineFields(): FieldCollection
{
$fields = [];
foreach ($this->meta['fields'] as $field) {
/** @var Field $instance */
$instance = new $field['class'](...$field['args']);

foreach ($field['flags'] ?? [] as $flag) {
$instance->addFlags(new $flag['class'](...$flag['args'] ?? []));
}

$fields[] = $instance;
}

return new FieldCollection($fields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ abstract class EntityDefinition

private ?FieldVisibility $fieldVisibility = null;

final public function __construct()
public function __construct()
{
$this->className = static::class;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Shopware\Core\Framework\DependencyInjection\CompilerPass;

use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\AttributeEntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEventFactory;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ApiAware;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Read\EntityReaderInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntityAggregatorInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearcherInterface;
use Shopware\Core\Framework\DataAbstractionLayer\VersionManager;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class AttributeEntityCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$services = $container->findTaggedServiceIds('shopware.entity');

foreach ($services as $class => $_) {
$reflection = new \ReflectionClass($class);

$collection = $reflection->getAttributes(Entity::class);

/** @var Entity $instance */
$instance = $collection[0]->newInstance();

$meta = [
'entity_class' => $class,
'entity_name' => $instance->name,
'fields' => $this->parse($reflection)
];

$definition = new Definition(AttributeEntityDefinition::class);
$definition->addArgument($meta);
$definition->setPublic(true);
$container->setDefinition($instance->name . '.definition', $definition);

$repository = new Definition(
EntityRepository::class,
[
new Reference($instance->name . '.definition'),
new Reference(EntityReaderInterface::class),
new Reference(VersionManager::class),
new Reference(EntitySearcherInterface::class),
new Reference(EntityAggregatorInterface::class),
new Reference('event_dispatcher'),
new Reference(EntityLoadedEventFactory::class),
]
);
$repository->setPublic(true);

$container->setDefinition($instance->name . '.repository', $repository);

$registry = $container->getDefinition(DefinitionInstanceRegistry::class);
$registry->addMethodCall('register', [new Reference($instance->name . '.definition'), $instance->name . '.definition']);
}
}

private function parse(\ReflectionClass $reflection): array
{
$fields = [
'id' => [
'class' => IdField::class,
'args' => ['storageName' => 'id', 'propertyName' => 'id'],
'flags' => [
['class' => PrimaryKey::class],
['class' => ApiAware::class],
['class' => Required::class],
]
]
];

return $fields;
}
}
4 changes: 4 additions & 0 deletions src/Core/Framework/DependencyInjection/FrameworkExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace Shopware\Core\Framework\DependencyInjection;

use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\AttributeEntityDefinition;
use Shopware\Core\Framework\Log\Package;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;

#[Package('core')]
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Framework/Framework.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
use Shopware\Core\Framework\Adapter\Cache\ReverseProxy\ReverseProxyCompilerPass;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\AttributeEntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
use Shopware\Core\Framework\DataAbstractionLayer\ExtensionRegistry;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\AssetBundleRegistrationCompilerPass;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\AssetRegistrationCompilerPass;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\AttributeEntityCompilerPass;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\AutoconfigureCompilerPass;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\DefaultTransportCompilerPass;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\DemodataCompilerPass;
Expand Down Expand Up @@ -35,9 +38,11 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
Expand Down Expand Up @@ -107,6 +112,8 @@ public function build(ContainerBuilder $container): void
}

// make sure to remove services behind a feature flag, before some other compiler passes may reference them, therefore the high priority
$container->addCompilerPass(new AttributeEntityCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1000);

$container->addCompilerPass(new FeatureFlagCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1000);
$container->addCompilerPass(new EntityCompilerPass());
$container->addCompilerPass(new MigrationCompilerPass(), PassConfig::TYPE_AFTER_REMOVING);
Expand Down
32 changes: 32 additions & 0 deletions src/Core/Migration/V6_6/Migration1714134471MyEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);

namespace Shopware\Core\Migration\V6_6;

use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Migration\MigrationStep;

/**
* @internal
*/
#[Package('core')]
class Migration1714134471MyEntity extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1714134471;
}

public function update(Connection $connection): void
{
$connection->executeStatement(
'CREATE TABLE IF NOT EXISTS my_entity (
id BINARY(16) NOT NULL,
name VARCHAR(255) NULL,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;'
);
}
}
18 changes: 18 additions & 0 deletions src/Core/System/Currency/MyEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Shopware\Core\System\Currency;

use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Field;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Primary;
use Shopware\Core\Framework\DataAbstractionLayer\Entity as EntityStruct;

#[Entity(name: 'my_entity')]
class MyEntity extends EntityStruct
{
#[Primary(type: 'uuid')]
public string $id;

#[Field(type: 'string')]
public string $name;
}
4 changes: 4 additions & 0 deletions src/Core/System/DependencyInjection/currency.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<tag name="shopware.entity.definition"/>
</service>

<service id="Shopware\Core\System\Currency\MyEntity" public="true">
<tag name="shopware.entity" />
</service>

<service id="Shopware\Core\System\Currency\Aggregate\CurrencyCountryRounding\CurrencyCountryRoundingDefinition">
<tag name="shopware.entity.definition"/>
</service>
Expand Down

0 comments on commit fbab73c

Please sign in to comment.