Sometimes we may need to display some information of a subresource, like this:

{
	"@context": "\\/api\\/contexts\\/Comment",
	"@id": "\\/api\\/blog_posts\\/583\\/comments",
	"@type": "hydra:Collection",
	"hydra:member": [
		{
			"@id": "\\/api\\/comments\\/764",
			"@type": "Comment",
			"content": "Do you think, at your age, it is I hate cats and dogs.' It was the Cat in a solemn tone, 'For the Duchess. An invitation from the roof. There were doors all round her, about the whiting!' 'Oh, as to.",
			"published": "2021-10-18T20:12:08+00:00",
			**"author": {
				"@id": "\\/api\\/users\\/22",
				"@type": "User",
				"username": "john_doe",
				"name": "John Doe"
			}**
		}
	],
	"hydra:totalItems": 1
}

We can accomplish that with subresourceOperations.

How to do it

For a specific route (optional)

First we need to get the route on which we want to have the attributes of the sub-resource:

php bin/console debug:route
----------------------------------------- -------- -------- ------ ----------------------------------------- 
 Name                                      Method   Scheme   Host   Path                                     
----------------------------------------- -------- -------- ------ ----------------------------------------- 
 ...
 **api_blog_posts_comments_get_subresource   GET      ANY      ANY    /api/blog_posts/{id}/comments.{_format}**  
 ...
----------------------------------------- -------- -------- ------ -----------------------------------------

Then we define the subresource operation in our ApiResource:

<aside> ❗ If we do not want this for a spefic route, there’s no need to use the subresourceOperations, meaning that assign that normalization_context to an itemOperations.

</aside>

/**
 * @ApiResource(
 *     itemOperations={
 *          "get",
 *          "put"={
 *              "security"="is_granted('IS_AUTHENTICATED_FULLY') and object.getAuthor() == user"
 *          }
 *     },
 *     collectionOperations={
 *          "get",
 *          "post"={
 *              "security"="is_granted('IS_AUTHENTICATED_FULLY')"
 *          }
 *     },
 ***     subresourceOperations={
 *          "api_blog_posts_comments_get_subresource"={
 *              "normalization_context"={
 *                  "groups"={"get-comment-with-author"}
 *              }
 *          }
 *     },**
 *     denormalizationContext={
 *      "groups"={"post"}
 *     }
 * )
 * @ORM\\Entity(repositoryClass=CommentRepository::class)
 */
class Comment implements AuthoredEntityInterface, PublishedDateEntityInterface
{
    /**
     * @ORM\\Id
     * @ORM\\GeneratedValue
     * @ORM\\Column(type="integer")
     *** @Groups({"get-comment-with-authorget-comment-with-author"})**
     */
    private $id;

    /**
     * @ORM\\Column(type="text")
     * @Assert\\NotBlank()
     * @Assert\\Length(min=5, max=3000)
     *** @Groups({"get-comment-with-author","post"})**
     */
    private $content;

    /**
     * @ORM\\Column(type="datetime")
     *** @Groups({"get-comment-with-author"})**
     */
    private $published;

    /**
     * @ORM\\ManyToOne(targetEntity="App\\Entity\\User", inversedBy="comments")
     * @ORM\\JoinColumn(nullable=false)
     *** @Groups({"get-comment-with-author"})**
     */
    private $author;

    /**
     * @ORM\\ManyToOne(targetEntity="App\\Entity\\BlogPost")
     * @ORM\\JoinColumn(nullable=false)
     * @Groups({"post"})
     */
    private $blogPost;

We now just need to use the same group on our sub resources:

/**
 *@ApiResource(
 *     normalizationContext={"groups"={"get"}},
 *     itemOperations={
 *          "get"={
 *              "security"="is_granted('IS_AUTHENTICATED_FULLY')",
 *              "normalization_context"={
 *                  "groups"={"get"}
 *               }
 *           },
 *           "put"={
 *              "security"="is_granted('IS_AUTHENTICATED_FULLY') and object.getUsername() == user.getUsername()",
 *              "denormalization_context"={
 *                  "groups"={"put"}
 *               }
 *          }
 *      },
 *     collectionOperations={
 *          "post"={
 *               "denormalization_context"={
 *                  "groups"={"post"}
 *               }
 *          }
 *     },
 * )
 *@ORM\\Entity(repositoryClass=UserRepository::class)
 *@UniqueEntity("username")
 *@UniqueEntity("email")
 */
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
		/**
     *@ORM\\Id
		 *@ORM\\GeneratedValue
		 *@ORM\\Column(type="integer")
     *@Groups({"get"})
     */
		 private $id;

		/**
     *@ORM\\Column(type="string", length=255)
     ***@Groups({"get", "post", "get-comment-with-author"})**
     *@Assert\\NotBlank()
     *@Assert\\Length(min=6, max=255)
     */
		 private $username;

		/**
     *@ORM\\Column(type="string", length=255)
     *@Groups({"put", "post"})
     *@Assert\\NotBlank()
     *@Assert\\Regex(
     *     pattern="/(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{7,}/",
     *     message="Password must be seven characters long and contains at least one digit, one upper case and one lower case letter"
     * )
     */
		 private $password;

		/**
     *@Groups({"put", "post"})
     *@Assert\\NotBlank
		 *@Assert\\Expression(
     *     "this.getPassword() === this.getRetypedPassword()",
     *     message="Passwords does not match"
     * )
     */
		 private $retypedPassword;

		/**
     *@ORM\\Column(type="string", length=255)
     ***@Groups({"get", "post", "put", "get-comment-with-author"})**
     *@Assert\\NotBlank()
     *@Assert\\Length(min=3, max=255)
     */
		 private $name;

		/**
     *@ORM\\Column(type="string", length=255)
     *@Groups({"post", "put"})
     *@Assert\\NotBlank()
     *@Assert\\Email()
     *@Assert\\Length(min=6, max=255)
     */
		 private $email;

		/**
     *@ORM\\OneToMany(targetEntity="App\\Entity\\BlogPost", mappedBy="author")
     *@Groups({"get"})
     */
		 private $posts;

		/**
     *@ORM\\OneToMany(targetEntity="App\\Entity\\Comment", mappedBy="author")
     *@Groups({"get"})
     */
		 private $comments;