Finding Related Rows in Doctrine -- "Cleanest" Design? - doctrine

I'm new to Doctrine, and I'm trying to get my head around both Doctrine and Symfony at the same time, so please bear with me.
I have a "Sentence" object. Sentences can be "rated" (scored out of five) by Users. I'm modelling this with the fairly obvious design where I have a Sentence table, a User table and a Rating table. Each row in the Rating table represents one user's rating of one sentence.
Rating:
...
columns:
rating: { type: integer } # This is my score out of five
sentence_id: { type: integer }
user_id: { type: integer }
relations:
sfGuardUser: { local: user_id }
Sentence: { local: sentence_id, onDelete: CASCADE, foreignAlias: Ratings }
That's working fine, but I'm struggling to find the cleanest way of asking questions of my model.
For example, in my code I have a logged-in User, related to my session, and I'm displaying a page representing a Sentence, which is also already sitting in a variable. I want to display the logged-in user's rating of that Sentence.
In raw SQL, effectively what I want to do is:
SELECT
rating
FROM
rating_table
WHERE
sentence_id = {$sentence->id} AND user_id = {$user->id}
Now, I know I could just do this on the RatingTable, simply by writing a new query with a bit of DQL, or even using one of the "magic" finders. But that seems a bit clumsy and not very OO, when I already have a Sentence object in memory with a getRatings() method generated by Doctrine for me which must already have the rating I want in its Collection of "all ratings for this sentence".
Is there an easy, efficient way that I'm missing of doing something like $sentence->getRatings()->findByUserId($user->getId())?
Or is it actually sensible just to ignore the fact that I've already got a collection of "ratings for this sentence" in memory and dive back out to the database, ignoring them completely?

There isnt a finder within a collection like that that im aware of (unlike with Propel)... if there is and I missed it in the Doctrine API then im going to leanr a blissful piece of code when someone posts it :-)
Having said that, IF you already have the collection loaded i would just add a custom method tot he model.. for example:
public function getUserRating($user){
if($user instanceof Doctrine_Record){ // or sfGuardUser depending on how tightly you want to couple
$user = $user->getId();
}
foreach($this->getRatings() as $rating){
if($user == $rating->getUserId()){
return $rating;
}
}
return null;
}

Related

Perform graphQL query with result from another graphQL query [duplicate]

Hullo everyone,
This has been discussed a bit before, but it's one of those things where there is so much scattered discussion resulting in various proposed "hacks" that I'm having a hard time determining what I should do.
I would like to use the result of a query as an argument for another nested query.
query {
allStudents {
nodes {
courseAssessmentInfoByCourse(courseId: "2b0df865-d7c6-4c96-9f10-992cd409dedb") {
weightedMarkAverage
// getting result for specific course is easy enough
}
coursesByStudentCourseStudentIdAndCourseId {
nodes {
name
// would like to be able to do something like this
// to get a list of all the courses and their respective
// assessment infos
assessmentInfoByStudentId (studentId: student_node.studentId) {
weightedMarkAverage
}
}
}
}
}
}
Is there a way of doing this that is considered to be best practice?
Is there a standard way to do it built into GraphQL now?
Thanks for any help!
The only means to substitute values in a GraphQL document is through variables, and these must be declared in your operation definition and then included alongside your document as part of your request. There is no inherent way to reference previously resolved values within the same document.
If you get to a point where you think you need this functionality, it's generally a symptom of poor schema design in the first place. What follows are some suggestions for improving your schema, assuming you have control over that.
For example, minimally, you could eliminate the studentId argument on assessmentInfoByStudentId altogether. coursesByStudentCourseStudentIdAndCourseId is a field on the student node, so its resolver can already access the student's id. It can pass this information down to each course node, which can then be used by assessmentInfoByStudentId.
That said, you're probably better off totally rethinking how you've got your connections set up. I don't know what your underlying storage layer looks like, or the shape your client needs the data to be in, so it's hard to make any specific recommendations. However, for the sake of example, let's assume we have three types -- Course, Student and AssessmentInfo. A Course has many Students, a Student has many Courses, and an AssessmentInfo has a single Student and a single Course.
We might expose all three entities as root level queries:
query {
allStudents {
# fields
}
allCourses {
# fields
}
allAssessmentInfos {
# fields
}
}
Each node could have a connection to the other two types:
query {
allStudents {
courses {
edges {
node {
id
}
}
}
assessmentInfos {
edges {
node {
id
}
}
}
}
}
If we want to fetch all students, and for each student know what courses s/he is taking and his/her weighted mark average for that course, we can then write a query like:
query {
allStudents {
assessmentInfos {
edges {
node {
id
course {
id
name
}
}
}
}
}
}
Again, this exact schema might not work for your specific use case but it should give you an idea around how you can approach your problem from a different angle. A couple more tips when designing a schema:
Add filter arguments on connection fields, instead of creating separate fields for each scenario you need to cover. A single courses field on a Student type can have a variety of arguments like semester, campus or isPassing -- this is cleaner and more flexible than creating different fields like coursesBySemester, coursesByCampus, etc.
If you're dealing with aggregate values like average, min, max, etc. it might make sense to expose those values as fields on each connection type, in the same way a count field is sometimes available alongside the nodes field. There's a (proposal)[https://github.com/prisma/prisma/issues/1312] for Prisma that illustrates one fairly neat way to do handle these aggregate values. Doing something like this would mean if you already have, for example, an Assessment type, a connection field might be sufficient to expose aggregate data about that type (like grade averages) without needing to expose a separate AssessmentInfo type.
Filtering is relatively straightforward, grouping is a bit tougher. If you do find that you need the nodes of a connection grouped by a particular field, again this may be best done by exposing an additional field on the connection itself, (like Gatsby does it)[https://www.gatsbyjs.org/docs/graphql-reference/#group].

Use Query Result as Argument in Next Level in GraphQL

Hullo everyone,
This has been discussed a bit before, but it's one of those things where there is so much scattered discussion resulting in various proposed "hacks" that I'm having a hard time determining what I should do.
I would like to use the result of a query as an argument for another nested query.
query {
allStudents {
nodes {
courseAssessmentInfoByCourse(courseId: "2b0df865-d7c6-4c96-9f10-992cd409dedb") {
weightedMarkAverage
// getting result for specific course is easy enough
}
coursesByStudentCourseStudentIdAndCourseId {
nodes {
name
// would like to be able to do something like this
// to get a list of all the courses and their respective
// assessment infos
assessmentInfoByStudentId (studentId: student_node.studentId) {
weightedMarkAverage
}
}
}
}
}
}
Is there a way of doing this that is considered to be best practice?
Is there a standard way to do it built into GraphQL now?
Thanks for any help!
The only means to substitute values in a GraphQL document is through variables, and these must be declared in your operation definition and then included alongside your document as part of your request. There is no inherent way to reference previously resolved values within the same document.
If you get to a point where you think you need this functionality, it's generally a symptom of poor schema design in the first place. What follows are some suggestions for improving your schema, assuming you have control over that.
For example, minimally, you could eliminate the studentId argument on assessmentInfoByStudentId altogether. coursesByStudentCourseStudentIdAndCourseId is a field on the student node, so its resolver can already access the student's id. It can pass this information down to each course node, which can then be used by assessmentInfoByStudentId.
That said, you're probably better off totally rethinking how you've got your connections set up. I don't know what your underlying storage layer looks like, or the shape your client needs the data to be in, so it's hard to make any specific recommendations. However, for the sake of example, let's assume we have three types -- Course, Student and AssessmentInfo. A Course has many Students, a Student has many Courses, and an AssessmentInfo has a single Student and a single Course.
We might expose all three entities as root level queries:
query {
allStudents {
# fields
}
allCourses {
# fields
}
allAssessmentInfos {
# fields
}
}
Each node could have a connection to the other two types:
query {
allStudents {
courses {
edges {
node {
id
}
}
}
assessmentInfos {
edges {
node {
id
}
}
}
}
}
If we want to fetch all students, and for each student know what courses s/he is taking and his/her weighted mark average for that course, we can then write a query like:
query {
allStudents {
assessmentInfos {
edges {
node {
id
course {
id
name
}
}
}
}
}
}
Again, this exact schema might not work for your specific use case but it should give you an idea around how you can approach your problem from a different angle. A couple more tips when designing a schema:
Add filter arguments on connection fields, instead of creating separate fields for each scenario you need to cover. A single courses field on a Student type can have a variety of arguments like semester, campus or isPassing -- this is cleaner and more flexible than creating different fields like coursesBySemester, coursesByCampus, etc.
If you're dealing with aggregate values like average, min, max, etc. it might make sense to expose those values as fields on each connection type, in the same way a count field is sometimes available alongside the nodes field. There's a (proposal)[https://github.com/prisma/prisma/issues/1312] for Prisma that illustrates one fairly neat way to do handle these aggregate values. Doing something like this would mean if you already have, for example, an Assessment type, a connection field might be sufficient to expose aggregate data about that type (like grade averages) without needing to expose a separate AssessmentInfo type.
Filtering is relatively straightforward, grouping is a bit tougher. If you do find that you need the nodes of a connection grouped by a particular field, again this may be best done by exposing an additional field on the connection itself, (like Gatsby does it)[https://www.gatsbyjs.org/docs/graphql-reference/#group].

GraphQL Authorization / Permission

So basically how do you handle permissions?
Let's say we have a list of Post(s) of some kind, with an argument first to limit the amount of posts. And only the owner and approved users can read the posts, everyone else can't. What is the best way to implement this?
query {
{
viewer {
posts(first: 10) {
id
text
}
}
}
}
What I'm currently thinking of, is to have a single source of truth to whether a user can read the post or not, and hook it up with the dataloader module.
But, how do I query for exactly 10 posts? If I query my DB for exactly 10 rows, when I then later on filter them with some business logic, then I can get for example 8 posts returned.
A solution is to not put a limit on the query, but that's not very efficient. So what is a good way to go about this?
Inspiration from here
(1) https://dev-blog.apollodata.com/auth-in-graphql-part-2-c6441bcc4302
(2) https://dev-blog.apollodata.com/graphql-at-facebook-by-dan-schafer-38d65ef075af
(1) solved it by
export const DB = {
Lists: {
all: (user_id) => {
return sql.raw("SELECT id FROM lists WHERE owner_id is NULL or owner_id = %s, user_id);
}
}
}
as the query, and then to filter out which rows can be read:
resolve: (root, _, ctx) => {
// factor out data fetching
return DB.Lists.all(ctx.user_id)
.then( lists => {
// enforce auth on each node
return lists.map(auth.List.enforce_read_perm(ctx.user_id));
});
}
So, we can clearly see that it's querying for all the rows, even if, say, the first argument was 1, which is what I'm trying to avoid.
Maybe I'm approaching the problem wrong in some way, as the business logic lives on another layer than the DB one, so there's no way but to query all the rows. Any help appreciated.
For future reference and other people searching for solutions.
Used Dataloader to solve the authentication problem.
Literally implemented what they did in https://dev-blog.apollodata.com/graphql-at-facebook-by-dan-schafer-38d65ef075af and used this boilerplate repo as guidance. Not much more to say than that.

Merging a dynamic number of collections together

I'm working on my first laravel project: a family tree. I have 4 branches of the family, each with people/families/images/stories/etc. A given user on the website will have access to everything for 1, 2, or 4 of these branches of the family (I don't want to show a cousin stuff for people they're not related to).
So on various pages I want the collections from the controller to contain stuff based on the given user's permissions. Merge seems like the right way to do this.
I have scopes to get people from each branch of the family, and in the following example I also have a scope for people with a birthday this month. In order to show the right set of birthdays for this user, I can get this by merging each group individually if they have access.
Here's what my function would look like if I showed everyone in all 4 family branches:
public function get_birthday_people()
{
$user = \Auth::user();
$jones_birthdays = Person::birthdays()->jones()->get();
$smith_birthdays = Person::birthdays()->smith()->get();
$lee_birthdays = Person::birthdays()->lee()->get();
$brandt_birthdays = Person::birthdays()->brandt()->get();
$birthday_people = $jones_birthdays
->merge($smith_birthdays)
->merge($lee_birthdays )
->merge($brandt_birthdays );
return $birthday_people;
My challenge: I'd like to modify it so that I check the user's access and only add each group of people accordingly. I'm imagining something where it's all the same as above except I add conditionals like this:
if($user->jones_access) {
$jones_birthdays = Person::birthdays()->jones()->get();
}
else{
$jones_birthdays =NULL;
}
But that throws an error for users without access because I can't call merge on NULL (or an empty array, or the other versions of 'nothing' that I tried).
What's a good way to do something like this?
if($user->jones_access) {
$jones_birthdays = Person::birthdays()->jones()->get();
}
else{
$jones_birthdays = new Collection;
}
Better yet, do the merge in the condition, no else required.
$birthday_people = new Collection;
if($user->jones_access) {
$birthday_people->merge(Person::birthdays()->jones()->get());
}
You are going to want your Eloquent query to only return the relevant data for the user requesting it. It doesn't make sense to query Lee birthdays when a Jones person is accessing that page.
So what you will wind up doing is something like
$birthdays = App\Person::where('family', $user->family)->get();
This pulls in Persons where their family property is equal to the family of the current user.
This probably does not match the way you have your relationships right now, but hopefully it will get you on the right track to getting them sorted out.
If you really want to go ahead with a bunch of queries and checking for authorization, read up on the authorization features of Laravel. It will give let you assign abilities to users and check them easily.

Cake PHP automagic forms and multiple tables

I have a form with the following fields:
firstName
lastName
emailAddress
followed by (up to) ten name/email fields, like so
friendName-1
friendEmail-1
friendName-2
friendEmail-2
...
etc.
The firstName, lastName & emailAddress fields will be saved into a 'referrals' table, but 10 name/email pairs will go into a 'friends' table, containing a foreign key refferal_id.
Normally I use the cakephp 'automagic' forms, and set up my validation in the model, and do a $this->model->save(); If it fails I fall back on my model validation rules and the error messages get spat back into my form html. No problems.
How would I do this for TWO tables though? I'm thinking that the automagic stuff is not the way to go, but I feel I will lose a lot without it, such as validation in my models and custom error messages.
Is there a happy medium? I'm fairly new to cake so I'm wondering if theres a way to use automagic with multiple tables.
See http://book.cakephp.org/view/84/Saving-Related-Model-Data-hasOne-hasMany-belongsTo
Create the relationship in your Referral model:
var $hasMany = array('Friend');
Essentially, make sure you name your fields correctly, e.g.
echo $form->create('Referral');
echo $form->input('first_name');
echo $form->input('last_name');
echo $form->input('email');
foreach (range(0,9) as $index) {
echo $form->input('Friend.'.$index.'.name');
echo $form->input('Friend.'.$index.'.email');
}
echo $form->end();
And in your referrals controller:
function add() {
if(!empty($this->data)) {
$this->Referral->saveAll($this->data, array('validate'=>'first'));
}
}
First, I really hope that you don't have 20 fields in table called 'friendName-1', 'friendEmail-1', etc. Your second table should have 3 fields--your referral_id, friendname, and friendemail. If you don't want to normalize, then you don't need the second table.
But I digress, the way to keep your automagic forms is to put a "hasMany" in your referrals model and/or a "belongsTo" in your "friends" model. Then you can still use the FormHelper and Model->Save().

Resources