I'm using Typo3 version 8.7.8 and I'm developing an extention.
The two tables "auftrag" and "verstorbener" are connectet with a 1:n relation.
I try to search for a field in the table "verstorbener" in the repository of "auftrag". The relation of both is necessary.
If I try to execute the following query I get the error "The ColumnMap for property "verstorbener" of class "...\Auftrag" is missing."
$name = "Mustermann";
$query->matching(
$query->logicalAnd(
$query->equals('verstorbener.nachname', $name)
)
);
How can I solve this problem?
If you need more input feel free to ask for it.
Edit -- The relevant TCA code of the field "verst_id" in "auftrag" which contains the UID of "verstorbener":
'verst_id' => [
'exclude' => true,
'label' => 'LLL:EXT:.../locallang_db.xlf:auftrag.verst_id',
'config' => [
'type' => 'inline',
'foreign_table' => 'verstorbener',
'foreign_field' => 'uid',
'minitems' => 0,
'maxitems' => 1,
'appearance' => [
'collapseAll' => 0,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => 1,
'showPossibleLocalizationRecords' => 1,
'showAllLocalizationLink' => 1
],
],
],
Edit -- This is the object model
/**
* verstId
*
* #var \...\Domain\Model\Verstorbener
*/
protected $verstId = null;
/**
* Returns the verstId
*
* #return \...\Domain\Model\Verstorbener $verstId
*/
public function getVerstId()
{
return $this->verstId;
}
/**
* Sets the verstId
*
* #param \...\Domain\Model\Verstorbener $verstId
* #return void
*/
public function setVerstId(\...\Domain\Model\Verstorbener $verstId)
{
$this->verstId = $verstId;
}
I've solved the problem.
It seems like there layd some old data in the Typo3 cache.
Clearing cache in the backend or in the installer doesn't helped.
I had uninstall and reinstall the extension by hand.
I encountered this issue when I was upgrading an Extension from Typo3 v6 to Typo3 v8. The mechanism for including TCA config files has changed, so the Files in /Configuration/TCA/ need to be named according to the extension name. For example: tx_extension_domain_model_auftrag.php
The TCA configuration file only consists of a return array, no more $TCA['tx_extension_domain_model_auftrag']!
If you have TCA ctrl configuration in your ext_tables.php then combine this into the corresponding TCA files and remove the dynamicConfigFile definition!
Hope that helps :)
Related
I got a project about snowboard tricks. In my project, I got an entity Trick, that itslef has an attribute "illustrations" that is a collection of another entity : Illustration. I'm trying to do a form TrickType in which the user could load many illustrations (images : png, jpeg, jpg, gif) at once. Then the controller save the files, and several illustration entities are created with the names and paths of the files as "url" attribute.
So, the TrickType can't manage directly illustration entities, because I got to use a FileType field in my form this way the files could be load, and filetype of course doesn't directly create entities. That's why the filetype field in my tricktype form has to have the following options :
multiple : true (serveral files are loaded at once)
mapped : false (there's no field in Trick that is about url, just a collection of illustration entities)
My entities, forms and controllers are correctly done : if I don't do any verifications about the files, or if I do verifications, but on only one file, everything perfectly works. But I got to ensure each file is really an image.
Here is what I tried to do so :
1 - constraints directly in TrickType :
->add('illustrations', FileType::class, [
"label" => "Illustrations (optionnel)",
"multiple" => true,
"mapped" => false,
"required" => false,
"constraints" => [
new File([
"maxSize" => "10M",
"mimeTypes" => [
"image/png",
"image/jpg",
"image/jpeg",
"image/gif"
],
"mimeTypesMessage" => "Veuillez envoyer une image au format png, jpg, jpeg ou gif, de 10 mégas octets maximum"
])
]
])
problem : constraint try to check the field, which is a collection and not a file. So I got the error "this value should be of type string"
2 - no constraint in the form, but in entities
Note : this solution and the following ones wouldn't have prevent wrong files to be save because it only concern entities, not files. So even if it worked, it would just prevent wrong files to be found from url in database and put in the website, but it wouldn't have been a really good solution anyway
in Illustration entity :
/**
* #ORM\Column(type="string", length=255)
* #Assert\Regex("/((.jpg)|(.jpeg)|(.png)|(.gif))$/")
*/
private $url;
in Trick entity, 2 things tried :
/**
* #ORM\OneToMany(targetEntity="App\Entity\Illustration", mappedBy="trick")
* #Assert\All({
* #Assert\Valid
* })
*/
private $illustrations;
error : "The constraint Valid cannot be nested inside constraint Symfony\Component\Validator\Constraints\All"
Second try for tricks :
/**
* #ORM\OneToMany(targetEntity="App\Entity\Illustration", mappedBy="trick")
* #Assert\Valid
*/
private $illustrations;
no errors, but the form is considered valid whatever happen (I can add any type of files, symfony doesn't stop me)
3 - none of what I did before, but callback instead in Trick entity
/**
* #Assert\Callback
*/
public function checkIllustrations(ExecutionContextInterface $context, $payload)
{
$forbidenExtensions = false;
foreach ($this->illustrations as $illustration) {
$url = $illustration->getUrl();
if (! preg_match('/((.jpg)|(.jpeg)|(.png)|(.gif))$/', $url))
$forbidenExtensions = true;
}
if ($forbidenExtensions)
{
$context->buildViolation("L'un des fichiers semble ne pas être une image. Seuls les extensions jpg, jpeg, png et gif sont acceptés.")
->atPath("illustrations")
->addViolation();
}
}
This, like in the previous case when I only used Valid (not within All constraint) doesn't do anything. Just like if nothing is checked.
Adding
dump($this->illustrations);
die();
at the beginning of my callback give me a result : an empty arraycollection. And of course, I send some files.
So, The callback is executed, but without illustrations.
This make me wonder if the callback is executed directly when I try to submit the form (so the empty illustrations is normal : there's no illustrations before the controller create it)
For informations, this is my method that handle form submission in my controller :
/**
* #Route("/adding-trick", name="adding_trick")
* #IsGranted("ROLE_USER")
*/
public function addingTrick(Request $request, ObjectManager $manager)
{
$trick = new Trick();
$form = $this->createForm(TrickType::class, $trick);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$trick->setEditeur($this->getUser());
$illustrations = $form['illustrations']->getData();
if($illustrations)
{
foreach ($illustrations as $illustrationFile) {
$folder = "illustrations";
$extension = $illustrationFile->guessExtension();
if(!$extension)
$extension = "bin";
$illustrationName = rand(1, 999999999);
$illustrationFile->move($folder, $illustrationName . "." . $extension);
$illustration = new Illustration();
$illustration->setUrl("/" . $folder . "/" . $illustrationName . "." . $extension)
->setAlt("une illustration de la figure " . $trick->getNom())
->setTrick($trick);
$manager->persist($illustration);
$figure->addIllustration($illustration);
}
}
// some code for other fields
$manager->persist($figure);
$manager->flush();
$this->addFlash("success", "Figure ajoutée avec succès");
return $this->redirectToRoute("trick_display", [
"slug" => $trick->getSlug()
]);
}
return $this->render('handlingTricks/addingTrick.html.twig', [
"form" => $form->createView()
]);
}
if somebody has a clue about what I got to do ?
Thank you !
The solution was given jakumi in comments below my first message. Those are my changes :
As in first attempt, but with a modification (new All contains new File) :
->add('illustrations', FileType::class, [
"label" => "Illustrations (optionnel)",
"multiple" => true,
"mapped" => false,
"required" => false,
"constraints" => [
new All([
new File([
"maxSize" => "10M",
"mimeTypes" => [
"image/png",
"image/jpg",
"image/jpeg",
"image/gif"
],
"mimeTypesMessage" => "Veuillez envoyer une image au format png, jpg, jpeg ou gif, de 10 mégas octets maximum"
])
])
]
])
Subject
When I have a set of entities with a Doctrine Discriminator Map then I cannot add a filter to get just one type of all mapped entities, due SonataAdminBundle and/or SonataDoctrineORMAdminBundle are throwing an error.
Example:
Entities with a Doctrine Discriminator Map
/**
* #ORM\Table(name="activities")
* #ORM\Entity()
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "joined" = "...\JoinedActivity",
* "other" = "...\OtherActivity"
* })
*/
abstract class Activity()
{
abstract public function getType();
}
/**
* #ORM\Entity()
*/
class JoinActivity extends Activity()
{
const TYPE = 'joined';
public function getType()
{
return self::type;
}
}
/**
* #ORM\Entity()
*/
class OtherActivity extends Activity()
{
const TYPE = 'other';
public function getType()
{
return self::type;
}
}
Then I add the Sonata Admin filter:
protected function configureDatagridFilters(DatagridMapper $filter)
{
$filter->add(
'type',
null,
[
'label' => 'Activity Type',
],
'choice',
[
'choices' => [
JoinActivity::TYPE => ucfirst(JoinActivity::TYPE),
OtherActivity::TYPE => ucfirst(OtherActivity::TYPE),
],
]
);
}
Expected results
Get a new filter to select just joined or other activities.
Actual results
Notice: Undefined index: type
500 Internal Server Error - ContextErrorException
Stack trace
As requested by greg0ire this is the Stack Trace returned by Symfony/Sonata:
[1] Symfony\Component\Debug\Exception\ContextErrorException: Notice: Undefined index: type
at n/a
in /path/to/symfony/project/vendor/sonata-project/doctrine-orm-admin-bundle/Guesser/FilterTypeGuesser.php line 69
at Symfony\Component\Debug\ErrorHandler->handleError('8', 'Undefined index: type', '/path/to/symfony/project/vendor/sonata-project/doctrine-orm-admin-bundle/Guesser/FilterTypeGuesser.php', '69', array('class' => 'AppBundle\EntityBundle\Entity\Activity', 'property' => 'type', 'modelManager' => object(ModelManager), 'ret' => array(object(ClassMetadata), 'type', array()), 'options' => array('field_type' => null, 'field_options' => array(), 'options' => array(), 'parent_association_mappings' => array()), 'metadata' => object(ClassMetadata), 'propertyName' => 'type', 'parentAssociationMappings' => array()))
in /path/to/symfony/project/vendor/sonata-project/doctrine-orm-admin-bundle/Guesser/FilterTypeGuesser.php line 69
at Sonata\DoctrineORMAdminBundle\Guesser\FilterTypeGuesser->guessType('AppBundle\EntityBundle\Entity\Activity', 'type', object(ModelManager))
in /path/to/symfony/project/app/cache/dev/classes.php line 15104
at Sonata\AdminBundle\Guesser\TypeGuesserChain->Sonata\AdminBundle\Guesser\{closure}(object(FilterTypeGuesser))
in /path/to/symfony/project/app/cache/dev/classes.php line 15111
at Sonata\AdminBundle\Guesser\TypeGuesserChain->guess(object(Closure))
in /path/to/symfony/project/app/cache/dev/classes.php line 15105
at Sonata\AdminBundle\Guesser\TypeGuesserChain->guessType('AppBundle\EntityBundle\Entity\Activity', 'type', object(ModelManager))
in /path/to/symfony/project/vendor/sonata-project/doctrine-orm-admin-bundle/Builder/DatagridBuilder.php line 105
at Sonata\DoctrineORMAdminBundle\Builder\DatagridBuilder->addFilter(object(Datagrid), null, object(FieldDescription), object(ActivityAdmin))
in /path/to/symfony/project/app/cache/dev/classes.php line 13069
at Sonata\AdminBundle\Datagrid\DatagridMapper->add('type', null, array('label' => 'Activity Type', 'field_options' => array('choices' => array('joined' => 'Joined')), 'field_type' => 'choice', 'field_name' => 'type'), 'choice', array('choices' => array('joined' => 'Joined')))
in /path/to/symfony/project/src/AppBundle/SonAdminBundle/Admin/ActivityAdmin.php line 64
at AppBundle\SonAdminBundle\Admin\ActivityAdmin->configureDatagridFilters(object(DatagridMapper))
in /path/to/symfony/project/app/cache/dev/classes.php line 10609
at Sonata\AdminBundle\Admin\AbstractAdmin->buildDatagrid()
in /path/to/symfony/project/app/cache/dev/classes.php line 10910
at Sonata\AdminBundle\Admin\AbstractAdmin->getDatagrid()
in /path/to/symfony/project/vendor/sonata-project/admin-bundle/Controller/CRUDController.php line 104
at Sonata\AdminBundle\Controller\CRUDController->listAction()
in line
at call_user_func_array(array(object(CRUDController), 'listAction'), array())
in /path/to/symfony/project/app/bootstrap.php.cache line 3222
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
in /path/to/symfony/project/app/bootstrap.php.cache line 3181
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
in /path/to/symfony/project/app/bootstrap.php.cache line 3335
at Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(object(Request), '1', true)
in /path/to/symfony/project/app/bootstrap.php.cache line 2540
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
in /path/to/symfony/project/web/app_dev.php line 15
at require('/path/to/symfony/project/web/app_dev.php')
in /path/to/symfony/project/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php line 40
Any idea how I can fix it?
Thanks,
I face the same issue. I did a work around - I use doctrine_orm_callback type with doctrine INSTANCE OF operator.
Code looks like this:
->add('userType',
'doctrine_orm_callback',
[
'callback' => function ($queryBuilder, $alias, $field, $value) {
if (!is_array($value) || !array_key_exists('value', $value) || empty($value['value'])) {
return false;
}
$queryBuilder->andWhere($alias . ' INSTANCE OF :userType');
$queryBuilder->setParameter('userType', $value['value']);
return true;
},
],
ChoiceType::class,
[
'choices' => array_flip(UserType::getChoices()),
'translation_domain' => $this->getTranslationDomain(),
]
)
And it's working. Maybe it helps you.
I think Sonata is confused b/c it expects type to appear in the fields in your Doctrine mapping. I don't think filtering by type is supporting, but I recall having some support for single inheritance, especially when creating new objects. Can't find it back though.
It is not recommanded to modify the link between an inherited entity and his superclass once it is created. If you need it, you should consider using composition instead.
That's why Doctrine does not allow to directly manage the discriminator field.
All the following text is copied from this very useful response which is precious for a global and better understanding.
It is not a good sign when the type of an instance of an object needs to change over time. I'm not talking about downcasting/upcasting here, but about the need to change the real type of an object.
First of all, let me tell you why it is a bad idea:
A subclass might define more attributes and do some additionnal work
in it's constructor. Should we run the new constructor again? What
if it overwrites some of our old object's attributes?
What if you were working on an instance of that Person in some part of your code, and then it suddenly transforms into an Employee (which might have some redefined behavior you wouldn't expect)?!
That is part of the reason why most languages will not allow you to change the real class type of an object during execution (and memory, of course, but I don't want to get into details). Some let you do that (sometimes in twisted ways, e.g. the JVM), but it's really not good practice!
More often than not, the need to do so lies in bad object-oriented design decisions.
For those reasons, Doctrine will not allow you to change the type of your entity object. Of course, you could write plain SQL (at the end of this post - but please read through!) to do the change anyway, but here's two "clean" options I would suggest:
I realize you've already said the first option wasn't an option but I spent a while writing down this post so I feel like I should make it as complete as possible for future reference.
Whenever you need to "change the type" from Person to Employee, create a new instance of the Employee and copy the data you want to copy over from the old Person object to the Employee object. Don't forget to remove the old entity and to persist the new one.
Use composition instead of inheritance (see this wiki article for details and links to other articles). EDIT: For the hell of it, here's a part of a nice conversation with Erich Gamma about "Composition over Inheritance"!
See related discussions here and here.
Now, here is the plain SQL method I was talking about earlier - I hope you won't need to use it!
Make sure your query is sanitized (as the query will be executed without any verification).
$query = "UPDATE TABLE_NAME_HERE SET discr = 'employee' WHERE id = ".$entity->getId();
$entity_manager->getConnection()->exec( $query );
Here is the documentation and code for the exec method which is in the DBAL\Connection class (for your information):
/**
* Execute an SQL statement and return the number of affected rows.
*
* #param string $statement
* #return integer The number of affected rows.
*/
public function exec($statement)
{
$this->connect();
return $this->_conn->exec($statement);
}
Did someone tried the tutorial about Sortable Sonata Type Model in Admin.
I've followed it step by step without missing anything (I'm pretty sure) but can't get a good result at the end.
Basically what I'm trying to do is : I have 3 entities, Article, Tag and ArticleTag (eq to User, Expectation and UserHasExpectation in the tutorial)
Everything seems good until the UserHasExpectationAdmin:
protected function configureFormFields(FormMapper $formMapper){
// ...
$formMapper
->add('userHasExpectations', 'sonata_type_model', array(
'label' => 'User\'s expectations',
'query' => $this->modelManager->createQuery('UserBundle\Entity\Expectation'),
'required' => false,
'multiple' => true,
'by_reference' => false,
'sortable' => true,
))
;
$formMapper->get('userHasExpectations')->addModelTransformer(new ExpectationDataTransformer($this->getSubject(), $this->modelManager));}
I think an attribute 'class' => 'UserBundle\Entity\Expectation' should be added to 'userHasExpectations' field else Symfony says that it's an invalid value.
Then the other problem is in the dataTransformer:
It launch me the error:
Attempted to call an undefined method named "create" of class "Main\CoreBundle\Form\DataTransformer\TagDataTransformer"
I think a use statement should be added but I don't know which one. More over, suppose I have the right use statement I don't realize what the writer is aiming to do, if it's creating UserHasExpectation records why don't he add a userHasExpectations->setUser($this->User) ???
Also I want to add after "vardumping" $this->Subject before :
$formMapper->get('userHasExpectations')->addModelTransformer(new ExpectationDataTransformer($this->getSubject(), $this->modelManager));
It seems to have a proper Entity Object with all fields on NULL values...
FINALLY SOLVED IT!
So, the code of the tutorial contains many...mistakes
In spite of trying to create 'userHasExpectation' in the DataTransformer we just return the object userHasExpectation in the reverse DataTransformer then we create our records in the postPersist and postUpdate of our Admin Class that way :
/**
* {#inheritdoc}
*/
public function postUpdate($object)
{
$position = 0;
$uniqId = $this->getUniqId();
$request = $this->getRequest()->get($uniqId);
$qb = $this->modelManager->createQuery('MainCoreBundle:ArticleTag', 'at');
$TagsToRemove = $qb->where('at.article = :article')
->setParameter('article', $object)
->getQuery()
->getResult();
foreach ($TagsToRemove as $Tag) {
$this->modelManager->delete($Tag);
}
foreach($request["article_tags"] as $tag)
{
$Tag = $this->modelManager->find('MainCoreBundle:Tag', $tag);
$article_tags = new ArticleTag;
$article_tags->setTag($Tag);
$article_tags->setArticle($object);
$article_tags->setPosition($position++);
$this->modelManager->create($article_tags);
}
}
/**
* {#inheritdoc}
*/
public function postPersist($object)
{
$position = 0;
$uniqId = $this->getUniqId();
$request = $this->getRequest()->get($uniqId);
foreach($request["article_tags"] as $tag)
{
$Tag = $this->modelManager->find('MainCoreBundle:Tag', $tag);
$article_tags = new ArticleTag;
$article_tags->setTag($Tag);
$article_tags->setArticle($object);
$article_tags->setPosition($position++);
$this->modelManager->create($article_tags);
}
}
Hope this will help Somebody who has the same trouble.
#Sonata-admin-team : I hope you will read this and have time to update the tutorial in question.
Thanks,
Epixilog
For Sonata 3 adding the class attribute 'class'=> 'UserBundle\Entity\Expectation' resolved the problem for me.
I understand that the most recent JMSTranslationBundle automatically translates form labels, but it appears to be translating ANY php array element with the key 'label'.
Here is my test file:
class DefaultController extends Controller
{
$WTF = array( 'label' => 'WHY IS THIS BEING TRANSLATED?');
return $this->render('MyAppSomeBundle:Default:index.html.twig', array('x' => $WTF) );
}
If I run the command:
./app/console translation:extract fr --output-dir=./app/Resources/ --bundle=MyAppSomeBundle --keep
The JMSTranslationBunlde finds the $WTF array and translates it:
<trans-unit id="4b68507f1746b0e5f3efe99b8ef42afef79da017" resname="WHY IS THIS BEING TRANSLATED">
<source>WHY IS THIS BEING TRANSLATED</source>
<target state="new">WHY IS THIS BEING TRANSLATED</target>
<jms:reference-file line="11">Some/Controller/DefaultController.php</jms:reference-file>
</trans-unit>
Is anyone else experiencing this? Is there a workaround?
Also note that if I change 'label' to something else (such as 'notlabel'), the translation doesn't occur.
The label gets extracted because of the form label translation stuff.
Have you tried using the #ignore annotation?
$wtf = array('label' => /** #Ignore */ 'This should not get translated');
My Entity
/**
* Set friend
*
* #param \Frontend\ChancesBundle\Entity\UserFriends $friend
* #return ChanceRequest
*/
public function setFriend(\Frontend\ChancesBundle\Entity\UserFriends $friend = null)
{
$this->friend = $friend;
return $this;
}
My Action
$task = new ChanceRequest();
$form = $this->createFormBuilder($task)
->add('friend', 'choice', array(
'required' => true,
'expanded' => true,
'choices' => $fb_friends,
'multiple' => true,
'mapped' => true
))
->getForm();
Because setFriend is expecting a scalar, I cannot validate this or save it to db. It is an array from how many friends the user want to send a message to somebody. How can I change it?
I have seen here a post:
Symfony2 Choice : Expected argument of type "scalar", "array" given
but this don't work that I put an array in front of \Frontend or $friend. I guess because of the related table.
What do I have to do in Entity to get it work?
If friends could be found in your database (for example it is a User entity), you should declare ManyToMany relationship for these two tables. If not, make friend property to be a doctrine array type. if friends is not a valid entity class, all the rest you have is to make a custom validator and datatransformer. Otherwise you have nothing left to do. All this information you can find in the official symfony and doctrine documentation.
How to create a Custom Validation Constraint
How to use Data Transformers
Association Mapping
Working with Associations