I'm currently setting up a site and using 2 custom content entities (generated with Drupal Console) :
- A tenant, with some custom fields
- A Building, with some custom fields
Theses two entities need a many to many relationship.
Here is my current status, I'd like to know if it is the valid way to do or if I'm implementing this badly.
I'd also like to know how to handle the update of an entity properly (if I add a tenant in a building, I need to update the tenant to add him to this building)
My issue is that if I create a new tenant, add him to an existing building, the ID of my tenant will be null since it is a new entity not saved yet. But I can't save my tenant yet since I may have to add others data to it first, or pass some validation...
A workaround would be to do the building mapping at last, and saving twice my tenant (one when setting basing fields so it can have an ID and one after setting building relationship).
I know that Doctrine can handle many to many relationship by referencing the entity when it is not saved and I'd like to know if their is a way to do it with Drupal properly.
/**
* Defines the Tenant entity.
*
* @ingroup bnpp_re_business
*
* @ContentEntityType(
* id = "bnpp_re_business_tenant",
* label = @Translation("Tenant"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\bnpp_re_business\TenantListBuilder",
* "views_data" = "Drupal\bnpp_re_business\Entity\TenantViewsData",
* "translation" = "Drupal\bnpp_re_business\TenantTranslationHandler",
*
* "access" = "Drupal\bnpp_re_business\TenantAccessControlHandler",
* },
* base_table = "bnpp_re_business_tenant",
* data_table = "bnpp_re_business_tenant_field_data",
* translatable = TRUE,
* admin_permission = "administer tenant entities",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* "langcode" = "langcode",
* "published" = "status",
* },
* )
*/
class Tenant extends ContentEntityBase implements TenantInterface {
/**
* {@inheritdoc}
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
*
* @return array|\Drupal\Core\Field\FieldDefinitionInterface[]
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
// ... some fields
$fields[self::BUILDING_CODE] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Buildings'))
->setDescription(t('The associated buildings.'))
->setSetting('target_type', 'bnpp_re_business_building')
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED);
return $fields;
}
// ... some methods
public function addBuilding(int $building_id) {
$items = $this->get(self::BUILDING_CODE)->getValue();
if (is_null($items) || !is_array($items)) {
$items = [];
}
$data = ['target_id' => $building_id];
if (!in_array($data, $items)) {
$items[] = $data;
$this->set(self::BUILDING_CODE, $items);
$building = Building::load($building_id);
// FIXME Here is the bug. If my tenant is new, it does not have an ID yet so this will be null.
$building->addTenant($this->id());
}
}
}
/**
* Defines the Building entity.
*
* @ingroup bnpp_re_business
*
* @ContentEntityType(
* id = "bnpp_re_business_building",
* label = @Translation("Building"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\bnpp_re_business\BuildingListBuilder",
* "views_data" = "Drupal\bnpp_re_business\Entity\BuildingViewsData",
* "translation" = "Drupal\bnpp_re_business\BuildingTranslationHandler",
*
* "access" = "Drupal\bnpp_re_business\BuildingAccessControlHandler",
* },
* base_table = "bnpp_re_business_building",
* data_table = "bnpp_re_business_building_field_data",
* translatable = TRUE,
* admin_permission = "administer building entities",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* "langcode" = "langcode",
* "published" = "status",
* },
* )
*/
class Building extends ContentEntityBase implements BuildingInterface {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
// some fields ...
$fields[self::TENANT] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Tenants'))
->setDescription(t('The associated building.'))
->setSetting('target_type', 'bnpp_re_business_tenant')
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED);
return $fields;
}
// some methods...
public function addTenant($tenant_id) {
$items = $this->get(self::TENANT)->getValue();
if (is_null($items) || !is_array($items)) {
$items = [];
}
$data = ['target_id' => $tenant_id];
if (!in_array($data, $items)) {
$items[] = $data;
$this->set(self::Tenant, $items);
// Since we only call this method from addBuilding (Tenant Entity) method, we save the building.
$this->save();
}
}
}
Thanks by advance for your help !
$items = $this->get(self::TENANT)->getValue(); if (is_null($items) || !is_array($items)) { $items = []; } $data = ['target_id' => $tenant_id]; if (!in_array($data, $items)) { $items[] = $data; $this->set(self::Tenant, $items); }
If could be doing this ?$items = $this->get(self::TENANT)->getValue(); if (is_null($items) || !is_array($items)) { $items = []; } if (!in_array($data, $items)) { $items[] = $data; $this->set(self::Tenant, $items); }
and passing the entity as$data
?