I have an events extension (for TYPO3 9 LTS and 10 LTS), say MyVendor\MyEvents and a Locations extension, say MyVendor\MyLocations.
The Model MyVendor\MyEvents\Domain\Model\Events has a property eventLocation which is defined to be an object of MyVendor\MyLocations\Domain\Model\Locations.
Now I want to make the relation to MyVendor\MyLocations\Domain\Model\Locations optional. I have found a way for the TCA to show a different form field in the backend depending on the MyLocations extension being installed. But I have no idea how to make all the type definitions in the Events model conditional. They are crucial for the extension to work:
namespace MyVendor\MyEvents\Domain\Model
class Events extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* #var \MyVendor\MyLocations\Domain\Model\Locations
*/
protected $eventLocation = NULL;
/**
* #return \MyVendor\MyLocations\Domain\Model\Locations $eventLocation
*/
public function getEventLocation()
{
return $this->eventLocation;
}
/**
* #param \MyVendor\MyLocations\Domain\Model\Locations $eventLocation
* #return void
*/
public function setEventLocation(\MyVendor\MyLocations\Domain\Model\Locations $eventLocation)
{
$this->eventLocation = $eventLocation;
}
}
In case MyVendor\MyLocations is loaded it needs to be defined as above, in case it isn’t loaded it should be just an integer.
In the TCA I am using if (TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('my_locations')) for showing a different field in the backend form for an event.
The Locations Model is in a separate extension because I am using it in a third extension as well.
In your events extension you could setup a repository for locations. Then you can map this repository to your location extensions model via TypoScript.
in a extbase extension i us sys_category. In list action there is no problem, all categories work as expected. But i want to write category entries with a custom database finisher from tx_form.
In the model all seems correct:
/**
* Sets the categories
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $categories
* #return void
*/
public function setCategories($categories)
{
$this->categories = $categories;
}
in my finisher:
$newAddress->setCategories($newCat);
$this->addressRepository->add($newAddress);
The form gives me only the uid of the category but for "setCategories" i need an \TYPO3\CMS\Extbase\Persistence\ObjectStorage.
How do i get a \TYPO3\CMS\Extbase\Persistence\ObjectStorage from the uid of the category?
Thanks!
You need a setter method for the categories like this:
public function addCategory($category)
{
$this->categories->attach($category);
}
This method will add one model to your object storage.
As you need the category model to be added, you need to get the corresponding model of the uid with $categoryRepository->findByUid($uid);
A property of the model is a relation to one other record like this:
/**
* #var \MyCompany\MyExtension\Domain\Model\OtherObject
*/
public $otherObject;
/**
* #return OtherObject
*/
public function getOtherObject(): OtherObject
{
return $this->otherObject;
}
Now, assume the connected object to be invisible (e.g. it's hidden or time-restricted). Extbase is trying to assign 0, but PHP expects an instance of OtherObject... Bam! - you get an error.
How to deal with that? Hidden or time-restricted records are not uncommon.
You either need to require PHP 7.1 and use a nullable return type hint like ?OtherObject or remove the type hint completely for now. In any case you cannot rely on something being returned here so your consuming code needs to handle this.
Building a JSON response for an API type thing, to retrieve a specific set of data that includes a ManyToOne relationship in the entity for my entity that extends FOSUSerBundle's User entity (called Account in my case).
The problem is, the Account entity thats included as a field in the response, is wanted, but I dont want to include all of the password and role type stuff.
I've been browing the internet for a couple hours now, and I've followed many guides on this, and I've cleared my cache every single time, and to no avail; So here's where I ended up:
// app/config/config.yml
jms_serializer:
metadata:
auto_detection: true
directories:
FOSUserBundle:
namespace_prefix: "FOS\\UserBundle"
path: "%kernel.root_dir%/Resources/serializer/FOS"
I've for below I've tried User.Model.yml and Model.User.yml and User.Entity.yml as well in a vain thought that the file name actually matters
// app/Resources/serializer/FOS/Entity.User.yml
FOS\UserBundle\Model\User:
exclusion_policy: ALL
properties:
id:
expose: true
and what I get still looks like this:
{
"status":"ok",
"api_version":"1.0",
"code":200,
"data":{
"video":{
"id":1,
"published":true,
"visibility":true,
"title":"Megaman 2",
"slug":"megaman-2",
"summary":"A rap song about Megaman",
"description":"A rap song\r\nAbout megaman",
"youtube_id":"R6L9bUouDr8",
"date_published":"2014-07-02T14:09:26-0700",
"date_created":"2014-07-02T14:09:26-0700",
"date_updated":"2014-07-02T14:09:26-0700",
"author_id":3,
"author":{
"id":3,
"username":"kharrison",
"username_canonical":"kharrison",
"email":"(sorry private)",
"email_canonical":"(sorry, private)",
"enabled":true,
"salt":"(sorry, private)",
"password":"(sorry, private)",
"last_login":"2014-07-04T15:17:34-0700",
"locked":false,
"expired":false,
"roles":[
"ROLE_SUPER_ADMIN"
],
"credentials_expired":false,
"display_name":"Kyle Harrison",
"slug":"kyle-harrison",
"bio":"Test"
}
}
}
}
The "author" field, is my Account entity thats being run through the JMSSerializer
I want to exclude ALL of that, except the user ID, Display name, and slug.
And finally this is how the API works:
// My/Bundle/Controller/BaseAPIController.php
//......... other code
/**
* #param string $status
* #param integer $code
* #return Response
*/
public function render_api($status, $code)
{
$this->apiResponse->setStatus($status);
$this->apiResponse->setCode($code);
return new Response($this->apiResponse->serialize($this->get('jms_serializer')), $this->apiResponse->getCode(), ["Content-type"=>"application/json"]);
}
//............. other code
and finally, that calls this:
// My/Bundle/Models
class APIResponse {
protected $status;
protected $apiVersion;
protected $code;
protected $data;
public function __construct($apiVersion, $status = "OK", $code = 500)
{
$this->status = $status;
$this->code = $code;
$this->apiVersion = $apiVersion;
$this->data = [];
}
// ... getters and setters
/**
* #return mixed
*/
public function serialize($serializer) {
return $serializer->serialize($this, "json");
}
}
I've for below I've tried User.Model.yml and Model.User.yml and
User.Entity.yml as well in a vain thought that the file name actually
matters.
It does matter, actually. It's a concatenation of the namespace and class name. In this case, you're trying to configure the FOS\UserBundle\Model\User class, so the file name should be Model.User.yml. (FOS\UserBundle\ should be excluded from the file name, since you configured it as namespace_prefix in your config.yml)
Also make sure that your Account class doesn't re-declare (overwrite) the properties, as the serializer config only works if you configure it for the class that actually declares the properties.
Ok So, the actual answer, couldn't have been arrived to via the information I provided. But Nic's Answer did lead me towards the solution. The description of how the the serializer looks at and deciphers the config file lead me to the real problem at hand.
This is what I failed to show:
<?php
namespace [PRIVATE]\[PRIVATE]Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\VirtualProperty;
/**
* Account
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="[PRIVATE]\[PRIVATE]Bundle\Entity\AccountRepository")
*/
class Account extends BaseUser
{
The problem lays with the Alias I provided the FOS\UserBundle\Model\User namespace. I no longer remember why I wrote that that way. However, the moment I remove the Alias and rewrote the extends to resemble this instead:
<?php
namespace [PRIVATE]\[PRIVATE]Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\VirtualProperty;
/**
* Account
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="[PRIVATE]\[PRIVATE]Bundle\Entity\AccountRepository")
*/
class Account extends User
{
combined with the new correct filename from Nic's answer, the config based Exclusion policy for JMSSerializerBundle totally kicks in, and every instance of FOSUserBundle's items are now completely hidden, except for the fields I've now explicitly told it to expose.
This is exactly what I wanted :)
Thanks everyone for your help! Cheers
~k
I'm not sure it's the exact way you want it, more a way around:
way around 1: Select only the properties you want (via the entity manager) and then serialize the array obtained.
It's what I do with what I call my API (which is not a class as you but controllers)
Can anybody tell me whether its posible to override doctrine2 persistentobject magic getters\setters? i'd like to do the below:-
public function setDob($dob)
{
$this->dob= new \Date($date);
}
however my entity is defined as:-
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Ajfit\Repository\User")
* #ORM\HasLifecycleCallbacks
*/
class User extends \Doctrine\Common\Persistence\PersistentObject
{
/**
* #var date $dob
*
* #ORM\Column(name="dob", type="date")
*/
protected $dob;
}
the public function setDob does not get called when I create the entity using:-
public function getNewRecord() {
return $this->metadata->newInstance();
}
I get the below error:-
Notice:- array to string conversion ...Doctrine\DBAL\Statement.php on line 98
Any help would be much apprieciated.
Thanks
Andrew
__call of PersistentObject#__call will not be called if you defined the setDob method.
What you're doing there is creating a new instance via metadata. What you are doing there is probably assuming that __construct or any setter/getter should be called by the ORM. Doctrine avoids to call any methods on your object when generating it via metadata/hydration (check ClassMetadataInfo#newInstance to see how it is done) as it does only know it's fields.
This allows you to be completely independent from Doctrine's logic.
About the notice, that is a completely different issue coming from Doctrine\DBAL\Statement, which suggests me that you have probably some wrong parameter binding in a query. That should be handled separately.