I am writing REST API, where resources are entities. There is a problem with Id field, which has NoBlank and NotNull constraints (which are logical) when creating new entity - obviously a new entity has no Id before writing to DB. However validation component of course says the entity is not valid. How to overcome this issue without removing the constraints from the Id field?
In my opinion you shouldn't have a constraint on your id.
Url of create should be [POST]/resource and url of edit should be [PUT]/resource/{id}.
(Or POST/PATCH depending on how strictly you are doing rest HTTP methods)
THis way the id is always mandatory.
If you don't want this routing logic, you can use validation groups
/**
* #Assert\NotNull(groups={"create"})
*/
private $id;
/**
* #Assert\NotNull(groups={"create","edit"})
*/
private $whatever;
Related
Using API Platform 1.2.
I've simplified my setup for the purpose of this question. Please excuse lack of following standards.
I have 2 entities: Book and Category. Book properties:
/**
* #Groups({book:read})
*/
$name;
/**
* #Groups({book:read})
*/
$summary;
/**
* #Groups({book:read})
*/
$category;
The $category property is mapped to a Category entity. Category has a $categoryName property. This is also attached to the book:read group.
In the GET API call this the output contains all the Book properties plus the $categoryName property. This is great 👍
What I want to know is: Using API Platform, how would I go about getting all properties for a serialization group such as above?
I have found that I could tap into the \ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface to get the property info but this requires knowing what classes and properties to check. Looping through all entities with a metadata lookup, then looping through all properties, performing another metadata lookup seems wasteful and slow.
Is there a better way to acheieve this? I basically want the same output as what the API produces by feeding am entity name and group name.
I am trying to implement a time tracking mechanism in my custom project management app.
This app contains multiple entities (tickets, projects, wiki pages, sprints, ...)
I want my timetracking to be "generic" in the sense that I want users to be able to log time against a ticket, project, wiki page, ...well any entity actually.
Now, I am trying to figure out what database schema (relation) to use for my TimeLog entity.
I could theoretically create a relation to each entity I have in my app, but that will require me to keep updating schema when I introduce new entities later on.
Has anybody every implemented anything like this?
All suggestions are welcomed.
Many thanks in advance.
I faced a similar situation in my app while trying to add comments, likes and other types of elements whose behaviour would not really depend on the entity they are attached to.
The solution I eventually chose was to have two fields in my referring entities (e.g. Comment) to hold both the id of the entity being referred to and its type. Since I was using this multiple times, I put the properties into the following trait:
namespace AppBundle\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait EntityReferenceTrait
{
/**
* #ORM\Column(name="reference_id", type="integer")
*/
private $referenceId;
/**
* #ORM\Column(name="reference_type", type="integer")
*/
private $referenceType;
/* ... setters & getters ... */
}
Then I could use it in the entities holding those kind of references:
/**
* #ORM\Table(name="comments", indexes={#ORM\Index(name="references", columns={"reference_id", "reference_type"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\Comment\CommentRepository")
*/
class Comment
{
/* ... other traits ... */
use \AppBundle\Entity\Traits\EntityReferenceTrait;
/* ... other fields & methods ... */
}
Note: I added an index for the references but it is not necessary for the whole thing to work properly. If you use such an index, beware of the order of your WHERE clauses if you want to benefit from it
In order to improve performance a bit and add additional configurations depending on the type of the entity being referred to, I handled settings directly in the config of my app. Thus, I have something like:
commentables:
news:
classname: AppBundle\Entity\News\News
type_id: 1
browse_route: news_comments
multiple_locales: false
...
This allows me to know precisely what kind of entities my Comment entity can refer. It also allows me to automatically hook specific listeners to the entities being referred to so that the removal of a referred entity triggers the removal of the related comments for example. I do this by processing the configuration in AppBundle/DependencyInjection/AppExtension.php (more about this here) and saving the needed listeners list into a parameter. Then, by adding a listener to the loadClassMetadata event, I can effectively handle the removal of related entities for example.
Here is the listener that hooks the listeners for specific lifecycle events of referred entities by using addEntityListener on the ClassMetadata instance:
namespace AppBundle\Listener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
private $entityListenersMapping = [];
/**
* #param array $mappingConfig Associative array with keys being listeners classnames and values being arrays associating an event to a method name
*/
public function __construct(array $mappingConfig)
{
$this->entityListenersMapping = $mappingConfig;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if(!array_key_exists($classMetadata->name, $this->entityListenersMapping))
{
return;
}
// Hook the entity listeners in the class metadata
foreach($this->entityListenersMapping[$classMetadata->name] as $listenerClassName => $eventsCallbacks)
{
foreach($eventsCallbacks as $event => $methodName)
{
$classMetadata->addEntityListener($event, $listenerClassName, $methodName);
}
}
}
}
Either way, for this part, it mainly depends on the specific needs of your entity but I guess it is quite a common need that these "soft" foreign keys emulate a ON DELETE CASCADE behaviour via preRemove and postRemove events.
Considering the handling of those references and the entities owning them, I also created a EntityRefererManagerTrait to easily create services that manage those entities so that the other components interacting with them would not have to worry about the underlying configuration.
The interface of most public methods of those managers thus usually require:
the classname of the entity being referred to
the numeric id of the entity being referred to
With those two info and the configuration retrieved in my manager service, I can easily interact with the database even if, in my case, it stores an integer defined in the configuration as the reference type in place of the classname of the entity being referred to.
Based on this, I can enable comments, likes, votes, subscriptions and so on for any of my app entities (as long as its primary key is a single integer) with just a few more lines in my configuration files. No need to update database schema and with proper lifecycle events listeners being hooked, no worries about orphan entries in the database.
On a side note, it should be mentioned that you won't be able to retrieve referring entities from the inverse side as it won't be a real association. You won't benefit from foreign keys behaviours either. Thus, even if you emulate the ON DELETE CASCADE behaviour by listening to remove events, you won't be able to ensure that there are no orphans in your database if some DELETE operations are performed directly via DQL for example.
In the Symfony documentation about Embed forms, I just read this :
class Task{
/**
* #Assert\Type(type="AppBundle\Entity\Category")
* #Assert\Valid()
*/
protected $category;
// ...
}
They later say that
The Category instance is accessible naturally via $task->getCategory()
and can be persisted to the database or used however you need.
How is that different from a Many-To-One mapping ? (many tasks for one category of course)
Well, ORM mapping map the php class to the doctrine metadata.
Assert is a mecanism to validate objects.
It means you could use assert on objects wich are not entities or you could not use a mapped field in your formType
ManyToOne map an object to another from the doctrine point of view.
Assert\Type indicate that this attribute of your form is validated like another related object, wich is Category
I have a an entity with an ID as such:
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
I'm migrating data into this entity, and want to preserve existing keys. I looked at "Explicitly set Id with Doctrine when using "AUTO" strategy" and found that I should be able to do the following:
$newData = ... // array containing data to bring in
$newEntity = new MyEntity();
$newEntity->setId($newData['id']);
$newEntity->... // set other data fields
$em->persist($newEntity);
$metadata = $em->getClassMetadata('\CS\AcmeBundle\Entity\MyEntity');
$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
$em->flush();
However, Doctrine is not using the provided ID. It's ignoring it when inserting. I've also tried this approach instead, since some people seemed to have had luck with it (even tried both):
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
But that doesn't change anything. ID's are still inserted automatically by the database. In the query log, I see that Doctrine isn't even attempting to insert the ID.
If I remove #ORM\GeneratedValue(strategy="AUTO") from MyEntity annotations, then the migration will respect the provided ID I give it. But I want to override it just during the migration.
I'm using Doctrine 2.4.2.
For this technique to work, you must use the second of these:
$metadata = $em->getClassMetadata('\CS\AcmeBundle\Entity\MyEntity');
$metadata = $em->getClassMetadata('CS\AcmeBundle\Entity\MyEntity');
The problem is that Doctrine will return the same class meta data values for both.
They will both correctly identify the class file, read its annotations, etc. Obviously they are equivalent, except that one is an absolute namespace and the other is not.
But these strings will return different instances from getClassMetadata. Changes to one won't reflect in the other. If you want your intended technique to work, you must use the second form, because that is what UnitOfWork uses. It uses this normalization:
// \Doctrine\ORM\UnitOfWork->getCommitOrder()
...
$className = $this->em->getClassMetadata(get_class($entity))->name;
$class = $this->em->getClassMetadata($className);
...
Note that in the linked-to question, the solution uses get_class($entity). That is probably sufficient to get the correct behavior.
Even more detail: after a lot of stepping through code, I noticed that \Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory was memoizing both versions of the class name string in its private property $loadedMetadata. The version that was being used to actually flush the entities was the one without the leading slash, and I was editing the one with the leading slash.
Because both strings return the same data, I think this represents a bug in the implementation.
The differences between GeneratedValue strategies
Inside your entity
Replace
#ORM\GeneratedValue(strategy="AUTO")
with
#ORM\GeneratedValue(strategy="NONE")
I am not sure whether you are using annotations or xml, or yml files. So better to change the xml or yml doctrine entity files inside your bundle config as well.
I have a Doctrine entity that has boolean field. Should I add Symfony validation for it (for type boolean), or my form is correctly validated by inferring the type automatically?
class Entity
{
/**
* #ORM\Column(type="boolean")
* #Assert\.... <- do I have to apply any Symfony assertion here?
*/
private $isActive;
}
No, you have only two cases. Value is present or not - so true or false.
I imagine a validator only in one case if this checkbox has to be set by user always like "accept disclaimer" during registration process
In addition to what Lazy Ants has said, you would only need to assert the type as bool if this field is nullable. -- That's because null and false are not identical.
You would only need this scenario if not all of the entity is going to be populated immediately though, for example a multi step form you're going to persist across each step hop or have auto-save capability for. If the entire entity is being populated in a single request the property should not be nullable.