I'm using stof/doctrine-extensions-bundle with the Translatable extension for my entities (& doctrine/doctrine-migrations-bundle) and want to move ext_translations table from the public schema to a custom schema (e.g. product)
product.ext_translations table is created with bin/console doctrine:migrations:migrate
but when I want to import some data with this script:
use App\Infrastructure\Translatable\Entity\Translation\Translation;
...
$repository = $em->getRepository(Translation::class);
$colorEntity = new Color();
$colorEntity->setName("bleu");
$repository->translate($colorEntity, 'name', 'en', 'blue');
$em->persist($colorEntity);
$em->flush();
I get this error
[error] Migration App\Migrations\VersionXXX failed during Post-Checks. Error: "An exception occurred while executing a query: SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "ext_translations" does not exist
LINE 1: INSERT INTO ext_translations (locale, object_class, field, f...
Here is my conf:
doctrine.yaml
orm:
mappings:
Translatable:
type: annotation
is_bundle: false
prefix: App\Infrastructure\Translatable\Entity
dir: '%kernel.project_dir%/src/Infrastructure/Translatable/Entity'
alias: GedmoTranslatable
Metadata:
is_bundle: false
type: xml
dir: '%kernel.project_dir%/src/Infrastructure/ORM/Mapping/Metadata'
prefix: 'App\Domain\Metadata'
alias: Metadata
Color.orm.xml mapping file
<entity name="App\Domain\Metadata\Color" table="colors" schema="product" repository-class="App\Infrastructure\ORM\Repository\ColorRepository">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="name" type="string">
<gedmo:translatable/>
</field>
...
<gedmo:translation entity="App\Infrastructure\Translatable\Entity\Translation" locale="locale"/>
</entity>
App\Infrastructure\Translatable\Entity\Translation
<?php
namespace App\Infrastructure\Translatable\Entity;
use ...
/**
* #ORM\Table(name="ext_translations", schema="product", indexes={
* #ORM\Index(name="translations_lookup_idx", columns={"locale", "object_class", "field", "foreign_key"})
* })
* #ORM\Entity(repositoryClass="Gedmo\Translatable\Entity\Repository\TranslationRepository")
*/
class Translation extends AbstractTranslation
{
/**
* All required columns are mapped through inherited superclass
*/
}
Related
Here is my entity :
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Client } from './client.entity';
#Entity({ name: 'directory_dir' })
export class Directory extends BaseEntity {
#PrimaryGeneratedColumn("uuid", { name: 'dir_id' })
id: string;
#Column( { name: 'dir_name' } )
name: string;
#JoinColumn({ name: 'cli_client_id'})
#ManyToOne(() => Client, { eager: true })
client: Client;
}
The table is created via liquibase :
<changeSet id="3" author="Me">
<createTable tableName="DIRECTORY_DIR">
<column name="DIR_ID" type="uuid">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="DIR_NAME" type="varchar(64)">
<constraints nullable="false"/>
</column>
<column name="CLI_CLIENT_ID" type="uuid">
<constraints nullable="false" foreignKeyName="FK_DIRECTORY_DIR_CLI_CLIENT_ID" references="CLIENT_CLI(CLI_ID)"/>
</column>
</createTable>
</changeSet>
I struggle to have it inserted into the database. I have tried several syntaxes but every time, I get an error because the id is not auto-generated:
null value in column "dir_id" of relation "directory_dir" violates not-null constraint
I tried this :
const directory = new Directory();
/* Also tried this one
* const directory = this.directoryRepository.create({
* name: makeDirRequest.name,
* client: user.client
* });
*/
directory.name = makeDirRequest.name;
directory.client = user.client;
this.logger.log('directory', JSON.stringify(directory));
// Also tried with insert instead of save
return await this.directoryRepository.save(directory);
In the logs, I can see that there is no dir_id but, in the way I understand it, it should not be a problem, as I expect TypeOrm to do it when generating the INSERT statement.
When I insert entities in the database with SQL statements, I can easily find them from typeORM so the typeORM configuration seems ok.
The problem came from the definition of the table in PostgreSQL. A default value must be supplied in order to explain how to auto-generate the primary key.
Declaring the column like this in Liquibase does the job :
<column name="DIR_ID" type="uuid" defaultValue="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
I just added defaultValue="gen_random_uuid()".
gen_random_uuid() is the new uuid_generate_v4(), available from Postgres 13
From this point, I could just create the entity and insert it like this :
return await Directory.create({
name: makeDirRequest.name,
client: user.client
}).save();
I created a bare minimum Shopware 6 plugin to display product ID when the product is loaded. It worked fine. Below is my code.
PATH: src/Resources/config/services.xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="TestPlugin\Listener\ProductLoadedListener" >
<tag name="kernel.event_listener" event="product.loaded" />
</service>
</services>
</container>
Below is the ProductLoadedListener.php codes
PATH: src/Listener/ProductLoadedListener.php
<?php declare(strict_types=1);
namespace TestPlugin\Listener;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
class ProductLoadedListener{
public function onProductLoaded(EntityLoadedEvent $entityLoadedEvent){
print_r($entityLoadedEvent->getIds());
}
}
The above codes did the job it was created to do.
So I updated the ProductLoadedListener.php codes
<?php declare(strict_types=1);
namespace TestPlugin\Listener;
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price;
class ProductLoadedListener{
public function onProductLoaded(Price $price){
print_r($price->getNet());
}
}
I go an error
Argument 1 passed to TestPlugin\Listener\ProductLoadedListener::onProductLoaded() must be an instance of Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price, instance of Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent given, called in /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php on line 270
So I am asking why I got the above error, I was expecting it to echo the net price?
Shopware will inject in the onProductLoaded function an EntityLoadedEvent object, not a Price object. That's why PHP throws this error.
If you want the get the price of the loaded product, then you should get the product from the $entityLoadedEvent and then get the price:
class ProductLoadedListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductLoaded'
];
}
public function onProductLoaded(EntityLoadedEvent $entityLoadedEvent)
{
/** #var ProductCollection $loadedProducts */
$loadedProducts = $event->getEntities();
$firstProduct = $loadedProducts->first();
$productNetPrice = $firstProduct->getPrice()->first()->getNet();
dd($productNetPrice);
}
}
I'm following docs from here: https://docs.sulu.io/en/2.2/cookbook/smart-content-data-provider.html
Trying to make custom data provider for my custom entity type "MatchEvent":
I did it how it's explained here. Since I have services.yaml file I defined those 2 like this:
app.match_event_repository:
class: App\Repository\MatchEventRepository
factory: ['#doctrine', 'getRepository']
arguments:
- Symfony\Bridge\Doctrine\ManagerRegistry
app.smart_content.data_provider.matchevent:
class: App\SmartContent\MatchEventDataProvider
arguments: ['#app.match_event_repository', '#sulu_core.array_serializer', '#request_stack' ]
tags: [{name: 'sulu.content.type', alias: 'smart_matchevent_selection'}]
So, if I understood well, after that I should have my custom content data provider named "smart_matchevent_selection" and I can use it like this:
<property name="match" type="smart_content">
<meta>
<title lang="en">Match</title>
<title lang="de">Match</title>
</meta>
<params>
<param name="provider" value="smart_matchevent_selection"/>
</params>
</property>
But when I try to edit page containing this field I get error:
Missing parameter token in Sulu\Bundle\PreviewBundle\Controller\PreviewController
Exception is triggered at project/vendor/sulu/sulu/src/Sulu/Bundle/PreviewBundle/Controller/PreviewController.php:
public function stopAction(Request $request): Response
{
$this->preview->stop($this->getRequestParameter($request, 'token', true));
return new JsonResponse();
}
What I'm doing wrong here?
If you carefully look at the service definition of the SmartContentDataProvider in the documentation, you will notice that the tag should be {name: 'sulu.smart_content.data_provider', alias: 'match_events'} instead of {name: 'sulu.content.type', alias: 'smart_matchevent_selection'} ...
Then you can use it like <param name="provider" value="match_events"/>
I am using EclipseLink 2.5.2 and is using the following orm.xml to map the result of native query to POJOs.
<named-native-query name="RelSumView.findFmkItem">
<query>
select item_file_name
, last_release_id
, last_release_version
from tableA
</query>
</named-native-query>
<sql-result-set-mapping name="FmkItemDtoMapping">
<constructor-result target-class="xxx.model.common.biz.dto.FmkItemDTO">
<column name="item_file_name" class="java.lang.String" />
<column name="last_release_id" class="java.lang.Integer" />
<column name="last_release_version" class="java.lang.String" />
</constructor-result>
</sql-result-set-mapping>
But in below DAO class:
public List<FmkItemDTO> getFmkReleaseItemsByEnvLabelId(int envLabelId) {
return getEntityManager().createNamedQuery("RelSumView.findFmkItem", "FmkItemDtoMapping").getResultList();
}
The following exception is thrown:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-6042] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.QueryException
Exception Description: A session name must be specified for
non-object-level queries. See the setSessionName(String) method.
Query: ResultSetMappingQuery(sql="RelSumView.findFmkItem")
at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:480)
at xxx.model.common.dao.FmkReleaseDAO.getFmkReleaseItemsByEnvLabelId(FmkReleaseDAO.java:28)
I have no idea of what this exception is and on how to set the session name to the query.
I have a CodeFluent Entities Model such as:
<cf:project defaultNamespace="S5T" xmlns:cf="http://www.softfluent.com/codefluent/2005/1" xmlns:cfx="http://www.softfluent.com/codefluent/modeler/2008/1" xmlns:cfmy="http://www.softfluent.com/codefluent/producers.mysql/2012/1" xmlns:cfom="http://www.softfluent.com/codefluent/producers.model/2005/1" xmlns:cfasp="http://www.softfluent.com/codefluent/producers.aspnet/2011/1" xmlns:cfaz="http://www.softfluent.com/codefluent/producers.sqlazure/2011/1" xmlns:cfps="http://www.softfluent.com/codefluent/producers.sqlserver/2005/1" defaultKeyPropertyTypeName="long" maxParameterNameLength="62" defaultConcurrencyMode="None" persistencePropertyNameFormat="{1}" defaultMethodAllowDynamicSort="false" defaultProducerProductionFlags="Default, Overwrite, RemoveDates" defaultMethodDistinct="false" createDefaultMethodForms="true" createDefaultApplication="false" createDefaultHints="false" productionFlags="Default, Overwrite, RemoveDates">
<cf:import path="Default.Surface.cfp" />
<cf:producer name="SQL Server" typeName="CodeFluent.Producers.SqlServer.SqlServerProducer, CodeFluent.Producers.SqlServer">
<cf:configuration produceViews="true" targetDirectory="..\Model7Bom\Persistence" connectionString="Server=MY-MACHINE\SQLEXPRESS;Database=model7;Integrated Security=true;Application Name=S5T;Password=MyPassword;User ID=MyUser" cfx:targetProject="..\Model7Bom\Model7Bom.vbproj" cfx:targetProjectLayout="Update, DontRemove" />
</cf:producer>
<cf:entity name="User" namespace="S5T">
<cf:property name="Id" key="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" />
<cf:property name="Roles" typeName="{0}.RoleCollection" relationPropertyName="Users" />
</cf:entity>
<cf:entity name="Role" namespace="S5T">
<cf:property name="Id" key="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" />
<cf:property name="Users" typeName="{0}.UserCollection" relationPropertyName="Roles" />
</cf:entity>
</cf:project>
I could sucessfully decorate the cf:property name="Id" on both entities with cfps:hint="CLUSTERED". This got me Sql Server producer to correctly output
CONSTRAINT [PK_Use_Id_Use] PRIMARY KEY CLUSTERED
CONSTRAINT [PK_Rol_Id_Rol] PRIMARY KEY CLUSTERED
as opposed to default NONCLUSTERED.
How can I accomplish that with the THIRD TABLE generated by the model, to accomodate the many to many relationship?
By default, the table creation generated snippet is such as:
CREATE TABLE [dbo].[Role_Users_User_Roles] (
[Id] [bigint] NOT NULL,
[Id2] [bigint] NOT NULL,
CONSTRAINT [PK_Roe_Id_Id2_Roe] PRIMARY KEY NONCLUSTERED
(
[Id],
[Id2]
) ON [PRIMARY]
)
However, if I decorate both properties with cfps:hint="CLUSTERED" as in:
cf:property name="Roles" typeName="{0}.RoleCollection" relationPropertyName="Users" cfps:hint="CLUSTERED" /
cf:property name="Users" typeName="{0}.UserCollection" relationPropertyName="Roles" cfps:hint="CLUSTERED" /
I get a snippet generated with PRIMARY KEY CLUSTERED for the PK in TABLE [dbo].[Role_Users_User_Roles], BUT, in addition, I get an UNDESIRED effect of having an incorrect script generated for adding relations (generated filename ...relations_add.sql), such as:
ALTER TABLE [dbo].[Role_Users_User_Roles] WITH NOCHECK ADD CONSTRAINT [FK_Roe_Id_Id_Rol] FOREIGN KEY (
[Id]
) REFERENCES [dbo].[Role](
[Id]
) CLUSTERED
Along with the error from Sql Server:
Error 3 SQL80001: Incorrect syntax near 'CLUSTERED'.
And CodeFluent Producer Error:
CodeFluentRuntimeDatabaseException: CF4116: Execution of file ...path..._relations_add.sql statement at line 2
I need all three PKs CLUSTERED in the three tables generated, but not the side effect of syntax error for generating the relations.
This is not supported out-of-the-box. The hint declared on the a relation is rarely used, more meant as a Foreign Key hint than a column hint. There are several options you can use to do this.
The easiest is to use a post-generation .SQL script to add the clustered setting manually. This is described here: How to: Execute custom T-SQL scripts with the Microsoft SQL Server producer.
You could also use the Patch Producer to remove the CLUSTERED word from the file once it has been created : Patch Producer
Otherwise, here is another solution that involves an aspect I've written as a sample here. You can save the following piece of XML as a file, and reference it as an aspect in your model.
This aspect will add the CLUSTERED hint to primary keys of all Many To Many tables inferred from entities that have CLUSTERED keys. It will add the hint before the table scripts are created and ran, and will remove it after (so it won't end up in the relations_add script).
<cf:project xmlns:cf="http://www.softfluent.com/codefluent/2005/1">
<!-- assembly references -->
<?code #reference name="CodeFluent.Producers.SqlServer.dll" ?>
<?code #reference name="CodeFluent.Runtime.Database.dll" ?>
<!-- namespace includes -->
<?code #namespace name="System" ?>
<?code #namespace name="System.Collections.Generic" ?>
<?code #namespace name="CodeFluent.Model.Code" ?>
<?code #namespace name="CodeFluent.Model.Persistence" ?>
<?code #namespace name="CodeFluent.Model.Code" ?>
<!-- add global code to listen to inference steps -->
<?code
Project.StepChanging += (sender1, e1) =>
{
if (e1.Step == ImportStep.End) // hook before production begins (end of inference pipeline)
{
var modifiedTables = ProjectHandler.AddClusteredHint(Project);
// get sql server producer and hook on production events
var sqlProducer = Project.Producers.GetProducerInstance<CodeFluent.Producers.SqlServer.SqlServerProducer>();
sqlProducer.Production += (sender, e) =>
{
// determine what SQL file has been created
// we want to remove hints once the table_diffs has been created, before relations_add is created
string script = e.GetDictionaryValue("filetype", null);
if (script == "TablesDiffsScript")
{
ProjectHandler.RemoveClusteredHint(modifiedTables);
}
};
}
};
?>
<!-- add member code to handle inference modification -->
<?code #member
public class ProjectHandler
{
public static IList<Table> AddClusteredHint(Project project)
{
var list = new List<Table>();
foreach (var table in project.Database.Tables)
{
// we're only interested by tables inferred from M:M relations
if (table.Relation == null || table.Relation.RelationType != RelationType.ManyToMany)
continue;
// check this table definition is ok for us
if (table.RelationKeyColumns.Count < 1 || table.RelationRelatedKeyColumns.Count < 1)
continue;
// check clustered is declared on both sides
string keyHint = GetSqlServerProducerHint(table.RelationKeyColumns[0].Property);
string relatedKeyHint = GetSqlServerProducerHint(table.RelationKeyColumns[0].Property);
if (keyHint.IndexOf("clustered", StringComparison.OrdinalIgnoreCase) < 0 ||
relatedKeyHint.IndexOf("clustered", StringComparison.OrdinalIgnoreCase) < 0)
continue;
// force hint now, we only need to do this on one of the keys, not all
table.PrimaryKey.Elements[0].SetAttribute("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri, "clustered");
// remember this table
list.Add(table);
}
return list;
}
public static void RemoveClusteredHint(IEnumerable<Table> list)
{
foreach (var table in list)
{
table.PrimaryKey.Elements[0].RemoveAttribute("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri);
}
}
// helper method to read XML element's hint attribute in the SQL Server Producer namespace
private static string GetSqlServerProducerHint(Node node)
{
if (node == null)
return null;
return node.GetAttributeValue<string>("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri, null);
}
}
?>
</cf:project>