Adding New Attribute to an Entity¶
In the following example, we will add the extId
(alias "external ID") field to the Product
entity.
It is a common modification when you need your e-commerce application and ERP system to co-work smoothly.
Extend framework Product
entity¶
Tip
"How does the entity extension work?"
Find it out in the separate article.
Most common entities (including Product
) are already extended in project-base
to ease your development.
However, when extending any other entity, there are few more steps that need to be done.
Add new extId
field with Doctrine ORM annotations and a getter for the field into App\Model\Product\Product
class.
Overwrite setData
method for setting entity data from a data object.
namespace App\Model\Product;
use Doctrine\ORM\Mapping as ORM;
use Shopsys\FrameworkBundle\Model\Product\Product as BaseProduct;
use Shopsys\FrameworkBundle\Model\Product\ProductData as BaseProductData;
/**
* @ORM\Table(name="products")
* @ORM\Entity
*/
class Product extends BaseProduct
{
/**
* @var int
*
* @ORM\Column(type="integer")
*/
protected $extId;
/**
* @param \App\Model\Product\ProductData $productData
*/
protected function setData(BaseProductData $productData): void
{
parent::setData($productData);
$this->extId = $productData->extId ?? 0;
}
/**
* @return int
*/
public function getExtId(): int
{
return $this->extId;
}
}
Notice that type hints and annotations of the methods do not match. This is on purpose - extended class must respect the interface of its parent while annotation ensures proper IDE autocomplete.
Database migrations¶
Generate a database migration creating a new column for the field by running:
php phing db-migrations-generate
The command prints a file name the migration was generated into:
Checking database schema...
Database schema is not satisfying ORM, a new migration was generated!
Migration file ".../src/Migrations/Version20180503133713.php" was saved (525 B).
As you are adding a not nullable field, you need to modify the generated migration manually and add a default value for already existing entries:
$this->sql('ALTER TABLE products ADD ext_id INT NOT NULL DEFAULT 0');
$this->sql('ALTER TABLE products ALTER ext_id DROP DEFAULT');
Hint
In this step, you were using Phing target db-migrations-generate
.
More information about what Phing targets are and how they work can be found in Console Commands for Application Management (Phing Targets)_
Run the migration to create the column in your database:
php phing db-migrations
ProductData class¶
Add public extId
field into App\Model\Product\ProductData
class.
namespace App\Model\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductData as BaseProductData;
class ProductData extends BaseProductData
{
/**
* @var int
*/
public $extId;
}
ProductDataFactory class¶
In the following steps, we will overwrite all services that are responsible
for Product
and ProductData
instantiation to make them take our new attribute into account.
Edit App\Model\Product\ProductDataFactory
- overwrite create()
and createFromProduct()
methods.
namespace App\Model\Product;
use Shopsys\FrameworkBundle\Model\Product\Product as BaseProduct;
use Shopsys\FrameworkBundle\Model\Product\ProductData as BaseProductData;
use Shopsys\FrameworkBundle\Model\Product\ProductDataFactory as BaseProductDataFactory;
class ProductDataFactory extends BaseProductDataFactory
{
/**
* @return \App\Model\Product\ProductData
*/
protected function createInstance(): BaseProductData
{
return new ProductData();
}
/**
* @param \App\Model\Product\Product $product
* @return \App\Model\Product\ProductData
*/
public function createFromProduct(BaseProduct $product): BaseProductData
{
$productData = $this->createInstance();
$this->fillFromProduct($productData, $product);
$productData->extId = $product->getExtId() ?? 0;
return $productData;
}
/**
* @return \App\Model\Product\ProductData
*/
public function create(): BaseProductData
{
$productData = $this->createInstance();
$this->fillNew($productData);
$productData->extId = 0;
return $productData;
}
}
Your ProductDataFactory
is already registered in services.yaml
as an alias for the original class.
Shopsys\FrameworkBundle\Model\Product\ProductDataFactory:
alias: App\Model\Product\ProductDataFactory
Enable an administrator to edit the extId
field¶
Add your extId
field into the form by editing ProductFormTypeExtension
in App\Form\Admin
namespace.
The original ProductFormType
is set as the extended type by the implementation of getExtendedType()
method.
namespace App\Form\Admin;
use Shopsys\FrameworkBundle\Form\Admin\Product\ProductFormType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints;
class ProductFormTypeExtension extends AbstractTypeExtension
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// "basicInformationGroup" is defined in ProductFormType
$basicInformationGroup = $builder->get('basicInformationGroup');
$basicInformationGroup->add('extId', IntegerType::class, [
'required' => true,
'constraints' => [
new Constraints\NotBlank(['message' => 'Please enter external ID']),
],
'label' => 'External ID',
]);
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return ProductFormType::class;
}
}
Tip
If you want to change the order for your newly created field, please look at the section Changing order of groups and fields
Overwrite the setData()
method in your' Product' class.
namespace App\Model\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductData as BaseProductData;
// ...
/**
* @param \App\Model\Product\ProductData $productData
*/
public function setData(BaseProductData $productData) {
parent::setData($productData);
$this->extId = $productData->extId;
}
In your ProductDataFactory
class, update the createFromProduct()
method so it sets your new extId
field.
namespace App\Model\Product;
use Shopsys\FrameworkBundle\Model\Product\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductData as BaseProductData;
// ...
class ProductDataFactory extends BaseProductDataFactory
{
/**
* @param \App\Model\Product\Product $product
* @return \App\Model\Product\ProductData
*/
public function createFromProduct(BaseProduct $product): BaseProductData
{
$productData = $this->createInstance();
$this->fillFromProduct($productData, $product);
$productData->extId = $product->getExtId();
return $productData;
}
// ...
}
Front-end¶
To display your new attribute on a front-end page, you can modify the corresponding template directly
as it is a part of your open-box, e.g., detail.html.twig
.
{{ product.extId }}
Data fixtures¶
You can modify data fixtures in src/DataFixtures/
of your project.
Random extId
¶
If you want to add a unique random extId
for products from data fixtures, you can add it in the createProduct
method of ProductDataFixture.php
.
You can use Faker
to generate random numbers like this:
+ use Faker\Generator as Faker;
//...
+ /**
+ * @var \Faker\Generator
+ */
+ protected $faker;
/**
* @param \Shopsys\FrameworkBundle\Model\Product\ProductFacade $productFacade
* @param \Shopsys\FrameworkBundle\Model\Product\ProductVariantFacade $productVariantFacade
* @param \Shopsys\FrameworkBundle\Component\Domain\Domain $domain
* @param \Shopsys\FrameworkBundle\Model\Pricing\Group\PricingGroupFacade $pricingGroupFacade
* @param \App\Model\Product\ProductDataFactory $productDataFactory
* @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ProductParameterValueDataFactory $productParameterValueDataFactory
* @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterValueDataFactory $parameterValueDataFactory
* @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterFacade $parameterFacade
* @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterDataFactory $parameterDataFactory
* @param \Shopsys\FrameworkBundle\Model\Pricing\PriceConverter $priceConverter
+ * @param \Faker\Generator $faker
*/
public function __construct(
ProductFacade $productFacade,
ProductVariantFacade $productVariantFacade,
Domain $domain,
PricingGroupFacade $pricingGroupFacade,
ProductDataFactoryInterface $productDataFactory,
ProductParameterValueDataFactory $productParameterValueDataFactory,
ParameterValueDataFactory $parameterValueDataFactory,
ParameterFacade $parameterFacade,
ParameterDataFactory $parameterDataFactory,
- PriceConverter $priceConverter
+ PriceConverter $priceConverter,
+ Faker $faker
) {
$this->productFacade = $productFacade;
$this->productVariantFacade = $productVariantFacade;
$this->domain = $domain;
$this->pricingGroupFacade = $pricingGroupFacade;
$this->productDataFactory = $productDataFactory;
$this->productParameterValueDataFactory = $productParameterValueDataFactory;
$this->parameterValueDataFactory = $parameterValueDataFactory;
$this->parameterFacade = $parameterFacade;
$this->parameterDataFactory = $parameterDataFactory;
$this->priceConverter = $priceConverter;
+ $this->faker = $faker;
}
//...
/**
* @param \App\Model\Product\ProductData $productData
* @return \App\Model\Product\Product
*/
protected function createProduct(ProductData $productData): Product
{
+ $productData->extId = $this->faker->unique()->numberBetween(1, 10000);
/** @var \App\Model\Product\Product $product */
$product = $this->productFacade->create($productData);
$this->addProductReference($product);
return $product;
}
Specific extId
¶
If you need to add specific extId
to products in the data fixture, you will have to update the creation of products in ProductDataFixture::load
.
//...
$productData = $this->productDataFactory->create();
$productData->catnum = '9184440';
$productData->partno = '8328B006';
$productData->ean = '8845781245936';
+ $productData->extId = 1;
//...