I have FOSElasticaBundle in my Symfony project. I have an entity mapped with ElasticaBundle that has some "simple fields", like
mappings:
createdAt:
type: "date"
and other fields that are IDs (like owner_id) to correlate to other entities, like
owner:
type: "nested"
properties:
fullname:
type: string
index: not_analyzed
because I need to have the user fullname searchable with ES/Kibana. This works but it created of course a nested field "owner.fullname" and this kind of fields are not searchable with Kibana (it's since years there are requests about it).
So I'm asking: is there a way to flat out that field so that I have a simple plain string field in ES named "owner_fullname" with no nested data?
Thanks
Answer to self.
You can create a method (or use existing where possible) in your class to return informations about related class.
So if you have a User class related to several Address class to store user's addresses, and you want to store in ES the default one, you can create a method "getDefaultAddress" in User class, something like
public function getDefaultAddress() {
return $this->addresses->getDefault()->getFormattedFlatString()
}
then map it to a field with elastica Bundle and use the "property_path:" descriptor to tell elastica where to get the content for that field.
In this way you have a simple field in ES and not a nested one.
I'm not sure I like this philosophy because you mix up code and external persistence layers, but it works, is clear and easy to maintain especially if you create dedicated methods with same prefix in your class, like esGetAddress, esGetPlace and so on.
Related
I am trying to adopt the Symfony workflow component to my app.
As documentation says marking_store points to string. I was googling for it - it can be a string or json_array-field of a Doctrine entity.
But what if I have an Entity BlogPost with a relation BlogPostStatus that have two fields: some primary id and statusName. Can I configure the workflow component to change statuses of my BlogPost (i.e set new BlogPostStatus to BlogPost entity) and persist it to database?
Now I have only one solution: Add to my BlogPost entity non-mapped field and when it's changed change status of Entity.
Do you have a better solution?
For all built-in marking_store implementations the following is true:
If the functions setMarking or getMarking exist on the object holding the state, they will be used to set or get the marking respectively.
There are 3 built-in marking stores, the SingleStateMarkingStore (using the property accessor, hence setMarking/getMarking), the MultiStateMarkingStore (same), the MethodMarkingStore (explicitly calling those functions, you can change the function via the property setting of your marking_store config).
The difference lies within the argument provided in the setMarking call, for single state (this is the state_machine type, and by default NOT the the workflow type), the argument is the place (or state) where the mark is placed. For multi state (workflow type by default), the argument is an array where the keys are places and the values are marks, usually the marks are 1, and empty places are omitted.
So, I'll assume that your BlogPost (currently) only has a single state at any given time, and what you have to do now is to transform the marking given into the status entity - I will assume your workflow has type state_machine:
/** in class BlogPost */
public function setMarking(string $marking/*, array $context*/) {
$this->status->statusName = $marking;
}
public function getMarking() {
return $this->status->statusName;
}
special cases
If the BlogPostStatus should be a different one (for example, a constant object), then you'd have to use the new interface that dbrumann linked, and hook into the event to add that to the context.
If the BlogPostStatus may not exist at the time of the setMarking/getMarking, you have to create it on the fly in the setter and check for it in the getter. But I'm sure you're capable of doing that ;o)
Also if you're not using the single state workflows but multi state instead, you have to find a way to transform the array of (places->marks) into your status object and vice versa.
Based on the socially project, I tried to extend the user to add a field:
export interface User extends Meteor.User {
experience: number;
}
However I get the following error:
client/imports/app/parties/party-details.component.ts (71, 7): Type 'Observable<User[]>' is not assignable to type 'Observable<User>'.
Type 'User[]' is not assignable to type 'User'.
Property 'experience' is missing in type 'User[]'.
As far as I understand that is because of the collection definition:
export const Users = MongoObservable.fromExisting(Meteor.users);
It uses Meteor.users (without experience field) and not my custom typescript defined version (with experience field).
Update
Related files:
repo
party-details.component.ts 71
user model
users collection
How can I fix that? Or in other words: How do I extend the user model for an angular-meteor project?
Update 2
I want to rephrase the question to: How can I extend the Meteor user collection?
export const Users = MongoObservable.fromExisting(Meteor.users);
Bonus question: And how can I initiate the field with 0 when creating new users?
However I get the following error:
You have to change line 31 to
users: Observable<User[]>; instead of users: Observable<User>;
Although I don't even get an error thrown without changing it, so I don't know when the error is supposed to happen exactly?
Anyhow, a Meteor Collection lookup always returns an Array of objects so users: Observable<User[]>; is correct.
How can I extend the Meteor user collection?
As for how to extend the User:
Technically you wouldn't even have to extend it just to store an extra value, all the values that you give the Collection on insert will be stored for that document.
What you mean by extending is, extending the Model / Schema for the User document that is to be inserted. With TypeScript this usually just is an interface but if you weren't using TS, there would be alternatives such as https://github.com/aldeed/meteor-simple-schema which actually offer to set a default value. I just say this to help you understand the concept.
An interface though does only shape values, it does not set default values or anything like that. AFAIK this is the same with interfaces in Java for example.
So what you do is extend that interface like you said:
export interface User extends Meteor.User {
experience: number;
}
This should be correct. You can not extend the "collection", the collection simply defines the collection name in the MongoDB, and possibly who can access it.
And how can I initiate the field with 0 when creating new users
As already said, you can't set default values for interfaces. All you could do is set values to optional using experience? : number which would set it to undefined.
You can however create a class that implements that interface and then set default values.
export class SignupUser implements User{
experience:number=0;
email: string;
password: string;
}
then in your signup.component.ts on line 29 you could do
let signupUser=new SignupUser();
signupUser.email= this.signupForm.value.email;
signupUser.password= this.signupForm.value.password;
Accounts.createUser(signupUser, err => [...])
or even create a new SignupUser right away and bind directly to those values.
Alternatively the simple way you could also just do
Accounts.createUser({
experience: 0,
email: this.signupForm.value.email,
password: this.signupForm.value.password
}
I hope this helps you.
It looks like your issue is that somewhere you are trying to assign an Observable of a User array Observable<User[]> to an Observable of a User Observable<User>. Did you incorrectly apply Observable<User[]> as the type of the MongoObservable?
I'm using FOSUserBundle in a Symfony2 project, and I want to disable the 'unique' attributes on both username and email fields of the User entity. Maybe that sounds strange at first, but I'm using another combination of fields to make sure that there are unique (active) users in my database, which works fine.
Right now I've setup my User.orm.yml as follows:
MyProjectBundle\Entity\User:
type: entity
attributeOverride:
usernameCanonical:
unique: false
emailCanonical:
unique: false
table: fos_user
id:
id:
type: guid
generator:
strategy: UUID
fields:
etc... etc...
When I use this setup, and create/update my database via the console, the UNIQUE indexes are removed from the database, just as I wanted. YAY!
However, when I try to generate my project entities with
doctrine:generate:entities MyProjectBundle
I get the following error:
Invalid field override named 'usernameCanonical' for class 'MyProjectBundle\Entity\User'.
So it is not generating my entities correctly....
For now, I temporarily remove the attributeOverride part in my User.orm.yml whenever I want generate the entities, and put them back again if I wish to update my database.
My project is working fine with this workaround, but I would feel much more comfortable knowing why my attributeOverride is invalid, and set it up correctly...
Anyone with expierience or suggestions?
This was a known bug in the entity generator #3433. It was fixed by #1098 and backported to 2.4 by #1231. You should update to doctrine/orm >=2.4.8.
(By the way the entity generator is for generating entities for the first time and you are supposed to manually edit generated entities after that.)
I have a #OneToMany JPA association with an instance of Curriculum having several instances of WorkExperience.
The issue I have is that I want to be able to persist one Curriculum together with several WorkExperiences in a single entity manager persist/save call.
I am not sure how to glue several instances of WorkExperiences coming from a regular HTTP POST of a html form to a java collection/set in the Spring MVC model attribute...
First of all, is this possible at all? If so is it a good idea and what kind of html can post collections/arrays of data in a regular HTTP POST?
The Curriculum JPA entity:
#Entity
public class Curriculum {
...
#OneToMany
private Set<WorkExperience> workExperiences;
...
The WorkExperience JPA entity:
#Entity
public class WorkExperience {
...
Yes, it is possible. Spring MVC supports sending Lists/Maps as form values. The way that works is by subscripting the value. For a List, you use the list number, like
<form:input path="myVal[1].property" />
And for a Map you use the map key like
<form:input path="myVal[key].property' />
This is assumming your Model Attribute has a List/Map of said item. I would recommend using a DTO and translating to your Entity. It may be overkill, but I have a problem with allowing the View to manipulate my Entity objects directly.
Also to note: You will have to do some View-side coding to dynamically add/remove items on the form. This can be a real pain, as deletes don't work like you would imagine. Spring MVC has the ability to add to a List/Map, alter the items in a List/Map, but I haven't found a way to remove items from a map directly. I usually handle removes by adding a "remove" boolean flag into my DTOs, then tracking removals by simply adding a form:hidden element for that item in the List/Map, and clean the List/Map on the server-side when I get it.
Once you get all your Entities on the Server-side, then you have to store them. If you want this to be more auto-magic, simply set an annotaion on your parent Entity like
#OneToMany(cascade=CascadeType.PERSIST)
There are several CascadeTypes available, so pick the one that makes sense.
I am porting a legacy application to Symfony2 and I am struggling because routing doesn't include query string parameters. Some quick examples: Suppose you have a search page for books where you can filter results based on criteria:
http://www.bookstore.com/books?author=Stephen+King&maxPrice=20
The nice thing about query string parameters in a case like this is you can have any number of filters, use the ones you want for any given request, and not crowd the URL with filters you're not using.
Let's say we rewrote the routing for the above query using the Symfony2 routing component. It might look like this:
http://www.mybookstore.com/book/any_title/stephen%20king/any_release_date/max_price_20/any_min_price/any_format/any_category
Even not taking into account how arbitrarily long an unclean that URL is I still don't think it is as intuitive because each 'segment' of that route is not a key value pair but instead just a value (e.g. author=Stephen+King > /stephen%20king/).
You can of course access query string parameters in the controller by passing the Request object into the action method (e.g. indexAction(Request $request) {) but then validating them and passing them into other parts of the application becomes a hassle (i.e. where I find myself now). What if you are using the Knp Menu Bundle to build your sidebar and you want parts to be marked as .current based on query string parameters? There is no functionality for that, just functionality to integrate with Symfony2 routes.
And how to validate that your query string parameters are acceptable? I am currently looking at validating them like a form to then pass them into the database to generate a query. Maybe this is the way the Symfony2 team envisioned handling them? If so I'd just really like to know why. It feels like I'm fighting the application.
I ended up actually asking Fabien this question at Symfony Live San Francisco 2012. There was another talk at the same conference in regards to this question and I will share with you the slides here:
http://www.slideshare.net/Wombert/phpjp-urls-rest#btnNext
Basically in the slides you can see that the author agrees with me in that query string parameters should be used for filtering. What they should not be used for is determining a content source. So a route should point to a product (or whatever) and query string parameters should then be used in your controller to apply whatever logic you require to filter that content source (as per Fabien).
I ended up creating an entity in my application that I bind all my query string parameters to and then manipulate, much the same way forms are handled. In fact when you think about it it's basically the same thing.
Like in Symfony1, query strings are independent from the route parameters.
If you have a path defined as #Route("/page/{id}", name="single_page"), you can create a path in your view like this:
{{ path('single_page', { id: 3, foo: "bar" }) }}
The resulting URL will be /page/3?foo=bar.