Skip to content
Branch: master
Find file History
yaroslavbr and manowark BAP-19102: Replace Symfony\Component\Process\ProcessBuilder (#24535)
 - got rid from ProcessBuilder usage replaced by directly creating Process
 instance with needed arguments
 - completely removed sensio`s script handler
Latest commit e025081 Jul 11, 2019
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
Annotation/ORM CRM-2199: Create search handler for A/CI field autocomplete Dec 7, 2014
Async BAP-16867: Fix failed tests (#18022) Apr 28, 2018
Cache BAP-18363: Assets compile and fixtures instalation in parallel proces… Feb 27, 2019
Command
Configuration BAP-18279: Move feature configuration YAML files cache from Applicati… Apr 22, 2019
Controller BAP-19055: Update friendsofsymfony/rest-bundle package (#24428) Jul 5, 2019
DBAL/Types BB-9814: Rename rich_text to html_escaped type May 30, 2017
DependencyInjection BAP-19011: Remove direct service retrieval (#24332) Jun 21, 2019
Entity BB-15820: Change ownership type for Customer to FRONTEND_CUSTOMER (#2… Dec 6, 2018
EntityConfig BAP-15381: Add 'cascade' option to inverse side of extended associati… Sep 8, 2017
Event BAP-18080: Error using DatesAwareTrait with a non-configurable entity… Jan 28, 2019
EventListener BAP-16936: Remove upgrade20 command (#22345) Feb 19, 2019
Exception BAP-7269: Can't add ManyToOne relation from Contact entity to Country… Feb 26, 2016
Extend BAP-18279: Move feature configuration YAML files cache from Applicati… Apr 22, 2019
Form BAP-18431: Enum translations are missing (#23335) Apr 9, 2019
Grid BAP-18576: Custom field columns rendered before system field columns … May 2, 2019
ImportExport/Serializer BAP-17441: backmerge epic Aug 7, 2018
Mapping Add ORM metadata retriving time to Symfony profiler Oct 17, 2015
Migration ticket/BB-16291: Exceptions during command execution (`oro:migration:… Jun 5, 2019
Migrations
Model CRM-5294: Change workflow (sales/opportunity) to use new status field Apr 15, 2016
ORM BB-16680: Missing validation of imported product variant SKUs (#23763) Jun 13, 2019
Provider BAP-18279: Move feature configuration YAML files cache from Applicati… Apr 22, 2019
Resources BAP-19043: Remove direct service retrieval - mark services as public … Jul 2, 2019
Serializer OPA-85: Apply new CS to eliminate manual CS checks by developers duri… Feb 20, 2018
Tests
Tools BAP-18431: Enum translations are missing (#23335) Apr 9, 2019
Twig BAP-19039: Remove direct service retrieval (#24397) Jun 27, 2019
Validator
EntityExtendEvents.php BAP-5885: Support multiple display fields for extend ManyToOne fields Nov 4, 2014
OroEntityExtendBundle.php
README.md BAP-17728: Fix various typos Oct 15, 2018

README.md

OroEntityExtendBundle

OroEntityExtendBundle enables the entities structure extension using UI or migration scripts without changing the entity definition class. The bundle also supports the creation of new entities (custom entities) using the UI.

You can find additional information about the bundle's features in their dedicated sections:

Manage entities through UI

To manage existing entities or create new ones through UI go to System > Entities > Entity Management page. On this page you can see a list of all entities, but please note that you can modify only entities marked as extendable. Check IS EXTEND column to see whether an entity can be modified or not. To create a new entity click Create entity button at the top right corner of the page, fill the form and click Save And Close. Next add necessary fields to your entity clicking Create field button. To add new field to existing entity go to a view page of this entity and click Create field button. When all changes are made do not forget to click Update schema button to apply your changes to a database.

Modify entities using migration scripts

The existing entity can be extended using migration scripts. To create new extended field you can use addColumn method with a special options named oro_options. The following example shows it:

<?php

namespace Oro\Bundle\AccountBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
use Oro\Bundle\EntityBundle\EntityConfig\DatagridScope;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;

class OroAccountBundle implements Migration
{
    public function up(Schema $schema, QueryBag $queries)
    {
        $table = $schema->createTable('orocrm_account');
        $table->addColumn(
            'description',
            'text',
            [
                'oro_options' => [
                    'extend'   => ['owner' => ExtendScope::OWNER_CUSTOM],
                    'datagrid' => ['is_visible' => DatagridScope::IS_VISIBLE_FALSE],
                    'merge'    => ['display' => true],
                ]
            ]
        );
    }
}

Please pay attention on owner attribute in extend scope. In this example we use ExtendScope::OWNER_CUSTOM, it means that OroPlatform is fully responsible for render this field on edit and view pages, as well as grids. The default value of owner attribute is ExtendScope::OWNER_SYSTEM, and in this case you have to add such field in forms, views and grids manually.

Also you can use OroOptions class to build oro_options. It can be helpful in same cases, for example if you work with arrays. The following example shows how to use this class:

<?php

namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\Migration\OroOptions;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;

class AcmeTestBundle implements Migration
{
    public function up(Schema $schema, QueryBag $queries)
    {
        $options = new OroOptions();

        // include Email entity in 'acme' group
        // please note that 'append' method adds new value in additional to existing values
        // so, if Email entity was already included in some other groups this information will not be lost
        $options->append('grouping', 'groups', 'acme');

        $table = $schema->getTable('oro_email');
        $table->addOption(OroOptions::KEY, $options);
    }
}

Extend fields can be also marked as unique:

<?php
   $table->addColumn(
   'phone',
   'string',
   [
       'length' => 255,
       'oro_options'   => [
           'extend'    => ['is_extend' => true, 'owner' => ExtendScope::OWNER_SYSTEM, 'unique' => true,],
           'dataaudit' => ['auditable' => true]
       ]
   ]
   );
   $table->addUniqueIndex(['phone'], 'oro_idx_user_phone');

Add relation

Creating relations is more complex task than creation of regular field. OroPlatform provides a special extension for Migration bundle named ExtendExtension to help you. To use this extension your migration should implement ExtendExtensionAwareInterface. The following example shows how to create many-to-one relation:

<?php

namespace Oro\Bundle\SalesBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;

class OroSalesBundle implements Migration, ExtendExtensionAwareInterface
{
    protected $extendExtension;

    public function setExtendExtension(ExtendExtension $extendExtension)
    {
        $this->extendExtension = $extendExtension;
    }

    public function up(Schema $schema, QueryBag $queries)
    {
        $table = $schema->createTable('orocrm_sales_lead');
        $this->extendExtension->addManyToOneRelation(
            $schema,
            $table,
            'users',
            'oro_user',
            'username',
            [
                'extend' => ['owner' => ExtendScope::OWNER_CUSTOM]
            ]
        );
    }
}

More examples you can find in relations chapter.

Add option set field

The option set is a special type of a field which allows to choose one or more options from a predefined set of options. OroPlatform provides two different data types for these purposes:

  • enum (named Select on UI) - only one option can be selected
  • multiEnum (named Multi-Select on UI) - several options can be selected

The option sets are quite complex types, but to understand how they work you need to know that both enum and multiEnum types are based on relations, the main difference between them is that enum type is based on many-to-one relation however multiEnum type is based on many-to-many relation. To add option set field to some entity you can use ExtendExtension. The following example shows how it can be done:

<?php

namespace Oro\Bundle\SalesBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;

class OroSalesBundle implements Migration, ExtendExtensionAwareInterface
{
    protected $extendExtension;

    public function setExtendExtension(ExtendExtension $extendExtension)
    {
        $this->extendExtension = $extendExtension;
    }

    public function up(Schema $schema, QueryBag $queries)
    {
        $table = $schema->createTable('orocrm_sales_lead');
        $this->extendExtension->addEnumField(
            $schema,
            $table,
            'source', // field name
            'lead_source', // enum code
            false, // only one option can be selected
            false, // an administrator can add new options and remove existing ones
            [
                'extend' => ['owner' => ExtendScope::OWNER_CUSTOM]
            ]
        );
    }
}

Please pay attention on the enum code parameter. Each option set should have code and it should be unique system wide and it's length should be no more than 21 characters (due to dynamic name generation and prefix). Same principle applied to field name, in case above - it should be less than 27 symbols, due to suffix _id will be applied (30-3). To load a list of options you can use data fixtures, for example:

<?php

namespace Oro\Bundle\DemoDataBundle\Migrations\Data\Demo\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\Persistence\ObjectManager;

use Oro\Bundle\EntityExtendBundle\Entity\Repository\EnumValueRepository;
use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper;

class LoadLeadSourceData extends AbstractFixture
{
    /** @var array */
    protected $data = [
        'Website'     => true,
        'Direct Mail' => false
    ];

    /**
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        $className = ExtendHelper::buildEnumValueClassName('lead_source');

        /** @var EnumValueRepository $enumRepo */
        $enumRepo = $manager->getRepository($className);

        $priority = 1;
        foreach ($this->data as $name => $isDefault) {
            $enumOption = $enumRepo->createEnumValue($name, $priority++, $isDefault);
            $manager->persist($enumOption);
        }

        $manager->flush();
    }
}

As you can see in this example we use buildEnumValueClassName function to convert the option set code to the class name of an entity responsible to store all options of this option set. It is important because such entities are generated automatically by OroPlatform and you should not use the class name directly. Also there are other functions in ExtendHelper class which can be helpful when you work with option sets:

  • buildEnumCode - builds an option set code based on its name.
  • generateEnumCode - generates an option set code based on a field for which this option set is created.
  • buildEnumValueId - builds an option identifier based on the option name. The option identifier is 32 characters length string.
  • buildEnumValueClassName - builds the class name of an entity responsible to store all options of the option set by the option set code.
  • getMultiEnumSnapshotFieldName - builds the name of a field which is used to store snapshot of selected values for option sets that allows to select several options. We use this data to avoid GROUP BY clause.
  • getEnumTranslationKey - builds label names for option set related translations.

As it was mentioned above each option set has own table to store available options. But translations for all options of all option sets are stored in one table. You can find more details in EnumValueTranslation and AbstractEnumValue. The AbstractEnumValue is a base class for all option set entities. The EnumValueTranslation is used to store translations.

If by some reasons you create system option sets and you have to render it manually the following components can be helpful:

  • TWIG extension to sort and translate options. It can be used in the following way: optionIds|sort_enum(enumCode), optionId|trans_enum(enumCode).
  • Symfony form types which can be used to build forms contain option set fields: EnumChoiceType and EnumSelectType.
  • Grid filters: EnumFilter and MultiEnumFilter. Some help how to use these filters in datagrids.yml and how to configure datagrid formatters for option sets you can find in ExtendColumnOptionsGuesser. Please take in account that this class pass the class name as the option set identifier, but you can use the enum code as well.

Create custom entity

A custom entity is an entity which has no PHP class in any bundle. The definition of such entity is created automatically in Symfony cache. To create a custom entity you can use ExtendExtension. The following example shows it:

<?php

namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;

class AcmeTestBundle implements Migration, ExtendExtensionAwareInterface
{
    protected $extendExtension;

    public function setExtendExtension(ExtendExtension $extendExtension)
    {
        $this->extendExtension = $extendExtension;
    }

    public function up(Schema $schema, QueryBag $queries)
    {
        $table = $this->extendExtension->createCustomEntityTable(
            $schema,
            'TestCustomEntity'
        );
        $table->addColumn(
            'name',
            'string',
            [
                'length' => 100,
                'oro_options' => [
                    'extend'  => ['owner' => ExtendScope::OWNER_CUSTOM],
                ]
            ]
        );
        $this->extendExtension->addManyToOneRelation(
            $schema,
            $table,
            'users',
            'oro_user',
            'first_name'
        );
    }
}

Preparing entity extend configuration

The following command prepares extended entities configuration:

php bin/console oro:entity-extend:update-config

Updating database schema for extended entities

The following command updates a database schema for extended entities:

php bin/console oro:entity-extend:update-schema

Warming up the cache

To save entity extend configuration stored in the database to the application cache, the following command can be used:

php bin/console oro:entity-extend:cache:warmup

Clearing up the cache

The following command removes all data related to entity extend functionality from the application cache:

php bin/console oro:entity-extend:cache:clear --no-warmup

To reload all cached data just run this command without --no-warmup option.

Custom form type and options

To configure custom form type and options for extended field, read Custom form type and options

Validation for extended fields

By default all extended fields are not validated. In general extended fields rendered as usual forms, same way as not extended, but there's a way to define validation constraints for all extended fields by their type. This is done through the configuration of oro_entity_extend.validation_loader:

    oro_entity_extend.validation_loader:
        class: %oro_entity_extend.validation_loader.class%
        public: false
        arguments:
            - @oro_entity_config.provider.extend
            - @oro_entity_config.provider.form
        calls:
            -
                - addConstraints
                -
                    - integer
                    -
                        - NotNull: ~
                        - Regex:
                            pattern: "/^[\d+]*$/"
                            message: "This value should contain only numbers."

            - [addConstraints, ["boolean", [{ NotBlank: ~ }]]]

To pass constraints there are two ways:

  • use compiler pass to add 'addConstraints' call with necessary constraint configuration
  • directly call service

Pay attention to the fact that all constraints defined here applied to all extended fields with corresponding type.

Extend Fields View

Before extend fields rendering in view page, event "oro.entity_extend_event.before_value_render" fired. There is possibility for customize field rendering using this event.

As example you can create Event Listener. Example:

    oro_entity_extend.listener.extend_field_value_render:
        class: %oro_entity_extend.listener.extend_field_value_render.class%
        arguments:
            - @oro_entity_config.config_manager
            - @router
            - @oro_entity_extend.extend.field_type_helper
            - @doctrine.orm.entity_manager
        tags:
            - { name: kernel.event_listener, event: oro.entity_extend_event.before_value_render, method: beforeValueRender }

Each event listener try to made decision how we need to show field value and if it know how value need to be shown, he use $event->setFieldViewValue($viewData); to change field view value. Example:

<?php
    $underlyingFieldType = $this->fieldTypeHelper->getUnderlyingType($type);
        if ($value && $underlyingFieldType == 'manyToOne') {
            $viewData = $this->getValueForManyToOne(
                $value,
                $this->extendProvider->getConfigById($event->getFieldConfigId())
            );

            $event->setFieldViewValue($viewData);
        }

In this code we:

  • check if value not null and field type is "manyToOne".
  • calculate field view value and set it using $event->setFieldViewValue($viewData);

In variable $viewData can be simple string or array [ 'link' => 'example.com', 'title' => 'some text representation']. In case of string it will be formatted in twig template automatically based on field type. In case of array we show field with text equal to 'title'. Also title will be escaped. If 'link' option exists we show field as link with href equal to 'link' option value.

Custom fields and entities in search

During creation or editing custom entity or field, user can set parameter 'searchable'. If this parameter will be set to true, this custom entity or field will be indexed by search engine.

For string field type, user can set additional parameter title_field. If this parameter is set to true, value of this field will be included into the search result title.

For the custom entity, search alias will be the same as table name. For example, if user creates new entity with the name 'myentity', it's table name will be 'oro_ext_myentity', and this name will be set search entity alias.

During indexation, for entity field will be created search field in search index with the same name.

You can’t perform that action at this time.