I want to create a new task from Command in a custom Bundle. But having troubles with setting the task status. I took the fixture from Backend developer's guide and adapted it to Command
$task = new Task();
$task->setSubject('Important task');
$task->setDescription('This is an important task');
$defaultPriority = $this->doctrine->getRepository(TaskPriority::class)->find('normal');
if ($defaultPriority) {
$task->setTaskPriority($defaultPriority);
}
$task->setOwner($taskDataArray['user']);
$task->setOrganization($this->getOrganization());
$this->getEntityManager(Task::class)->persist($task);
$this->getEntityManager(Task::class)->flush();
The database record is created, but by default status_id field is empty.
Without status it isn't shown in data grid.
The status has type AbstractEnumValue. The method which sets the status is defined in Model as * #method Task setStatus(AbstractEnumValue $status)
How to set up the status correctly in CRUD operation?
Thanks ahead.
Status is a enum field.
Internally enums are entities with autogenerated class names.
To work with the enum entity, first, you have to generate its name, then you can access it using doctrine, as a regular entity.
// generate enum entity class name by the enum code
$statusClass = ExtendHelper::buildEnumValueClassName('task_status');
// find existing status entity
$statusOpen = $this->doctrine->find($statusClass, 'open');
// assign status to the task
$task->setStatus($statusOpen);
See the reference at the OroEntityExtendBundle documentation.
Related
I'm completely new to Symfony and I'm struggling with the query builder.
I have card entity and transaction entity in manytomany relation.
card entity: (id, card_number, code)
transaction entity: (id, amount, source, destination)
card_transaction: (card_id, transaction_id)
I want to get all the transactions that have a given card number.
We'd typically need to see the source for both of those entity classes.
Have you made a repository class for the CardTransaction entity yet? You could create this to have a reusable query as follows:
<?php
namespace App\Repository;
class CardTransactionRepository extends \Doctrine\ORM\EntityRepository
{
public function findByCardNumber(int $cardNumber): array
{
if ($cardNumber < 1) {
throw new \InvalidArgumentException("Card ID invalid.");
}
$qb = $this->createQueryBuilder("ct")
->leftJoin("ct.card", "c")
->andWhere("c.cardNumber = :cardNumber")
->setParameter("cardNumber", $cardNumber)
;
return $qb->getQuery()->getResult();
}
}
That being said what you're doing is such a simple query/operation you shouldn't need a query builder to do it. This is precisely what entity classes make easy, including Doctrine's ArrayCollection class.
If you're just doing this in a controller or view and have already loaded the Card entity you don't need a query in a repository class. Just iterate $card->getCardTransactions() assuming you've created the appropriate data accessor functions in Card. -- Then again, without seeing the Card and CardTransaction entity sources I couldn't tell you exactly what method names you have (or need).
You can retrieve a Card entity by its card_number property in a controller as follows:
$card = $this->getRepository(Card::class)->findOneByCardNumber($cardNumber);
Ideally you should check it exists too though kind of like this:
if (!($cardNumber = intval($cardNumber))) { // Do something better to validate the card number that has been specified, e.g. a `preg_match()`
throw $this->createNotFoundException("Card not specified.");
}
if (!($card = $this->getRepository(Card::class)->findOneByCardNumber($cardNumber))) {
throw $this->createNotFoundException("Card not found.");
}
I created custom Command where I added a new attribute.
protected function configure() : void
{
parent::configure();
$this
->setName('doctrine:migrations:generate:entitychange')
->addOption('db', null, InputOption::VALUE_REQUIRED, 'The database connection to use for this command.')
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command.')
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command.')
->addOption('entity',null,InputOption::VALUE_OPTIONAL,
'Entity on which migration will be applied',
'User');
}
Now I want to have a value of that parameter in up() and down() functions in migrations.
For example:
public up(Schema $schema) : void
{
// How to get this
$attributOfMyInputValue = $getInputValueOfParam('entity')
if($attributOfMyInputValue === 'someValue') {
$sql = 'some query';
$this->addSql($sql);
} else {
...
}
}
I assume, that you want to write minimal code here. So the first part of the problem is, to actually make doctrine migrations commands execute&migrate with that additional parameter/option. for that you probably have to extend both and add that parameter/option (or call them via https://symfony.com/doc/current/console/calling_commands.html)
To pass it on, I would probably go the lazy route and just create a service that is dependency injected into both the command (via constructor) and the migrations (via container, by making the migration container-aware) - something similar to Request's attribute bags. On the command side, I would add the parameters/options, and obviously on the migration side, read them from the same service, which can be fetched from the container.
I believe that approach entails the least amount of code, however, it's somewhat hacky. A possibly cleaner approach would be to create a new class of migrations, where those parameters are set by the migration/execute command, which probably would mean going deep into the migrations library and duplicating some of the behaviour or somehow figure out how to hook into or override the Migration execution process (which is - as far as i can tell from a quick glance - irritating, but there's probably some object you can decorate).
I have a collection of entities with both parent keys and string ids. Sometimes I need to change the string id (update the entity with a new id). From this question (Modify a Google App Engine entity id?), it looks like I need to create a new entity and delete the old one.
Of course, I want to preserve all of the properties in the old entity when creating the new one, but there doesn't seem to be a clone method for NDB entities.
Is this the best way to change an entity's id, while preserving the parent?
# clone the old_entity and parent as new_entity
new_entity = MyModel(**old_entity.to_dict(), id=new_id, parent=old_entity.parent())
And then, I should be able to do this to replace the old entity with the new one:
new_entity.put() # save the new entity
old_entity.key.delete() # delete the old entity
def clone_entity(e, **extra_args):
klass = e.__class__
props = dict((v._code_name, v.__get__(e, klass)) for v in klass._properties.itervalues() if type(v) is not ndb.ComputedProperty)
props.update(extra_args)
return klass(**props)
example
b = clone_entity(a, id='new_id_here')
#sanch's answer works fine in most cases, but for some reason it will not copy attributes of type ndb.PickleProperty.
This modification will work for all attributes, including PickleProperty, and will also accept an optionnal new_class parameter to make a clone of another class.:
def clone_entity(e, **extra_args):
"""
Clone an ndb entity and return the clone.
Special extra_args may be used to:
- request cloned entity to be of a different class (and yet have attributes from original entity)
- define a specific parent, id or namespace for the cloned entity.
:param e: The ndb entity to be cloned.
:param extra_args: May include special args 'parent', 'id', 'namespace', that will be used when initializing new entity.
other extra_args, may set values for specific attributes.
:return: The cloned entity
"""
if 'new_class' in extra_args:
klass = extra_args.pop('new_class')
else:
klass = e.__class__
props = dict((v._code_name, v.__get__(e, klass)) for v in klass._properties.itervalues() if type(v) is not ndb.ComputedProperty)
init_args = dict()
for arg in ['parent', 'id', 'namespace']:
if arg in extra_args:
init_args[arg] = extra_args.pop(arg)
clone = klass(**init_args)
props.update(**extra_args)
clone.populate(**props)
return clone
Using Breeze, what is the simplest way to populate a GUID key when an entity is created?
I'll assume that your entity is configured such that the client is responsible for setting the Guid key for new entities. That's the default for the Guid key of an Entity Framework Code First entity; it is as if the key property were adorned with [DatabaseGenerated(DatabaseGeneratedOption.None)]
The obvious approach is to set the key after creating the entity and before adding it to the manager, e.g.:
function createFoo() {
var foo = fooType.createEntity();
foo.id(breeze.core.getUuid()); // Knockout implementation
manager.addEntity(foo);
}
This may be all you ever need.
On the other hand, you may find that you're creating new Foos in many places and for some strange reason you can't use the createFoo function. You certainly don't want to repeat that code.
You can extend the Foo entity type with id-setting behavior after which you'd be able to write:
function createFoo() {
var foo = fooType.createEntity(); // foo.id is set for you
manager.addEntity(foo);
}
There are two approaches to consider - custom constructor and type initializer; both are described in "Extending Entities"
Constructor
You can initialize the key inside a custom constructor. Breeze calls the constructor both when you create the entity and when it materializes a queried entity. Breeze will replace the initial key value when materializing.
Here's an example that assumes the Knockout model library.
function Foo() {
foo.id(breeze.core.getUuid()); // using KO
}
// one way to get the MetadataStore
var store = manager.metadataStore;
// register the ctor with the Foo type
store.registerEntityTypeCtor("Foo", Foo);
Pretty simple. The only downside is that Breeze will generate a Guid every time it makes an entity, whether creating a new one or materializing one from a query. It's wasted effort during materialization but so what? Well, I suppose that might become a performance issue although I wouldn't assume so until I had measured it.
Initializer
Suppose you measured and the repeated Guid generation is a serious problem (really?). You could set the key in a type initializer instead and only call the Guid generator when creating a new entity.
Breeze calls a type initializer after the entity has been created or materialized from query just before returning that entity to the application. Clearly you don't want to overwrite a materialized key from the database so you'll test the key value to make sure it's not real (i.e. to make sure you're fixing a created entity) before assigning it. Here's an example.
function fooInitializer(foo) {
var emptyGuid = "00000000-0000-0000-0000-000000000000";
if (foo.id() !=== emptyGuid) {
foo.id(breeze.core.getUuid());
}
}
var store = manager.metadataStore;
// register the initializer; no ctor in this example
store.registerEntityTypeCtor("Foo", function(){}, fooInitializer);
Assuming you have a Guid surrogate Key on all your entities like we have in our case, you could code a createInstance factory that does the following in a very generic approach:
function createInstance(breezeEntityManager, typeName) {
var keyProperty = breezeEntityManager.metadataStore.getEntityType(typeName, false).dataProperties.filter(function (p) {
return p.isPartOfKey;
})[0];
var config = {};
config[keyProperty.name] = breeze.core.getUuid();
return breezeEntityManager.createEntity(typeName, config);
}
This way, you won't have to create an initializer for all your entities.
Im trying to get my head around attaching an entity with a related entity to a new context when I want to update the entity.
I have a Person Table (Generalised to Personnel), which has a LanguageID field. This field is linked as a FK via the EF to another table Language with LanguageID as the primary key (1-M). I need to update a particular Persons language preference, however, the relationship seems to remain linked to the old context as i get a "Object cannot be referenced by multiple instances of IEntityChangeTracker" error on the line marked below. Is there any way to attach the Language entity to the new context as a relationship of the Personnel (Person) entity???
The entities were not detached in the orginal GetPersonnel() method which uses an .Include() method to return the PreferredLanguage
PreferredLanguage is the NavigationProperty name on the Person table...
public static void UpdateUser(Personnel originalUser, Personnel newUser )
{
using (AdminModel TheModel = new AdminModel())
{
((IEntityWithChangeTracker)originalUser).SetChangeTracker(null);
((IEntityWithChangeTracker)originalUser.PreferredLanguage).SetChangeTracker(null);
TheModel.Attach(originalUser);--Error Line
TheModel.ApplyPropertyChanges("Person", newUser);
TheModel.SaveChanges();
}
}
Thanks
Sean
To avoid these sort of problems you should make GetPersonnel() do a NoTracking query.
I.e.
ctx.Person.MergeOption = MergeOption.NoTracking;
// and then query as per normal.
This way you can get a graph of connected entities (assuming you use .Include()) that is NOT attached. Note this won't work if you try to manually detach entities, because doing so schreds your graph.
Hope this helps
Alex