Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Role/scope based result in transformers? #327

Open
bobmulder opened this issue Aug 29, 2016 · 10 comments
Open

[Question] Role/scope based result in transformers? #327

bobmulder opened this issue Aug 29, 2016 · 10 comments
Labels

Comments

@bobmulder
Copy link

bobmulder commented Aug 29, 2016

While using Fractal, I want to base my output on the given scopes or roles from logged in users. My question is, can this be done inside the transformer? Of course, in my workflow it can be done, but I'm not sure if it follows the 'rules' of Fractal.

Example code would be:

$data = [
    'id' => $entity->get('id'),
    'name' => $entity->get('name')
];

if($this->user('role') === 'moderator') {
    $data['status'] = $entity->get('status');
}

Thanks in advance!

@Art4
Copy link
Contributor

Art4 commented Aug 29, 2016

I'm doing the exact same thing.

@johannesschobel
Copy link
Contributor

johannesschobel commented Aug 1, 2017

In my application-scenario I would use different Transformers in my Controller. E.g., there would be a ProductTransformer_Moderator and a ProductTransformer_User.
Things could get a bit nasty if you would output different strcutures depending on Roles and/or Permissions, so it may be easier to just have different Transformers.. What do you think?

Furthermore, you would need to inject the currentUser to each Transformer - which kind of binds the Transformer to your User Model..

@hegoku
Copy link

hegoku commented May 8, 2019

Have the same issue... Is there any beautiful solution?

@johannesschobel
Copy link
Contributor

@hegoku , I would suggest to use different Transformers in this use-case. I am not a fan of putting to much logic into the Transformers. Please note that you can extend some kind of BaseProjectTransformer which holds your includes (and the respective relationships) and implement the required transform(...) method in your specific classes..

e.g, like this:

abstract class AbstractProjectTransformer extends Transformer { 
   $availableIncludes = ['your', 'custom', 'includes'];
   
   includeYour(Project $project) {
      // do some stuff here, like
      return $this->collection($project->something(), new WhateverTransformer());
   }

   // other include methods here
}

class UserProjectTransformer extends AbstractProjectTransformer {
   transform(Project $project) {
      return [
         // your custom structure for USER roles here
      ];
   }
}


class ManagerProjectTransformer extends AbstractProjectTransformer {
   transform(Project $project) {
      return [
         // your custom structure for MANAGER roles here
      ];
   }
}

The proposed solution is way easier to extend (e.g., add new attributes) than constantly adding new if / else statements.. Further, it helps to "decouple" the transformers from the currentUser that called a specific endpoint. Transformers are - as they should be - only classes that transform one class to another one - without having any additional dependencies (like the user that wants to transform the object).

Hope this helps.. all the best

@hegoku
Copy link

hegoku commented May 8, 2019

@johannesschobel This solution is nice, thank you very much!

@yoannisj
Copy link

yoannisj commented May 9, 2019

@johannesschobel , I used this approach and mixed in some advanced transformation with includes, which adds some nice extra flexibility to my APIs endpoints.

BUT.. I am have trouble making this work with collection, which contains multiple types of items (models) and therefor should delegate the transformation of each of the items to a different transformer.

To continue with the user example, when including a collection of users with different roles, how do I tell fractal to use a different transformer based on the user's role, and make sure nested includes are passed through?

I have been stuck on this problem for days, and can't find a solution. Thanks for anybody's help!

@johannesschobel
Copy link
Contributor

johannesschobel commented May 9, 2019

@yoannisj , what exactly do you mean by

collections, which contains multiple types of items

?

Do you mean something like this:

$data = [
   $book1,
   $author1,
   $book2,
   $car,
   $user1,
   $user2,
   // ...
];

Are you actually mixing different resources in one response? if yes, i don't think that this is the desired behaviour or how you should implement it.. Usually you have one "root" resource (e.g., determined by the URL, /api/books) and the other resources should be "embedded" (e.g., through the includesX methods..

Hope this helps

@yoannisj
Copy link

yoannisj commented May 9, 2019

@johannesschobel, I am building an API for a website which contains listing/index pages, including a list of entries (a.k.a. posts). The endpoint to that page /api/pages/some-listing-page?include=entries returns something like so:

$data = [
    "title" => "Some listing page",
    "url" =>"https://www.example.com/some-listing-page",
    "postDate" => "2019-05-08T12h30"
    "intro" => "Lorem ipsum dolor…",
    "entries" => [
        $newsItem, $article, $article, $event, $newsItem, $article, /* ... */
    ],
    /* ... */
];

The "root" transformer for that page loads the data for the "entries" key, through an includeEntries method, which returns a fractal CollectionResource. But, the data-structure of the entry items in that collection can differ quite a lot from one entry type to another, some having their own nested includes (for example an article entry could respond to "?include=entries.writers", and an event entry could respond to another"?include=entries.location").

The examples I have seen in the documentation, seem to suggest that I can only pass a single transformer class to the collection returned in the page transformer's includeEntries method. Something like:

public function includeEntries( $page )
{
    $entries = new Query('entries')->parentPage($page)->fetch();

    return $this->collection($entries, new EntryTransformer());
}

But that would mean, that the EntryTransformer needs to implement the data transformation logic for all the different entry types, and all the possible nested includes in one single transformer class, which gets messy very quickly.

Ideally, there would be a way to tell Fractal to:

  • a) use a different transformer class for each type of entry included in the $entries collection (i.e. NewsEntryTransformer, ArticleEntryTransformer, …)

  • b) pass the nested include queries from the endpoint url to all the entry transformers (in such way that "?include=entries.writers" would call ArticleEntryTransformer::includeWriters()` for example)

Is that possible, and how can I achieve such behaviour? Maybe the answer is to rely on a common PolyEntryTransformer class, which gets passed to the included collection of entries, and then instanciates a sub-transformer for each entry, while passing in the nested include scope? How would that look like?

Sorry, this starts to look more and more like a stack overlfow ticket, rathat than a github issue. I would be happy to re-iterate my question in a more appropriate channel.

Thanks a lot for the help!

@yoannisj
Copy link

yoannisj commented May 10, 2019

#Seems to be a duplicate of #244

@stale
Copy link

stale bot commented Apr 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 4 weeks if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants