Display Laravel Notification (MailMessage) with markdown after sent - php

I'm saving every email I send to an entity into the database by creating a function storeEmail and make an insert of MailMessage class into EmailMessage model. Everything works fine, and the main goal is to display the message exactly as it was, when the recipient received it and retrieve all the messages I sent as a User, to a page. To be much easier to retrieve a render of each specific Message in foreach loop, I think is better to fetch it from the Model.
This is my Notification class:
class SimpleEmail extends Notification
{
use Queueable;
private $link;
private $user;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($link)
{
$this->link = $link;
$this->user = Auth::user();
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$mail = (new MailMessage)
->from($this->user->email, $this->user->name)
->subject('My Dummy Subject')
->greeting('To: '.$notifiable->email)
->action('Action Button', url($this->link))
->line('Thank you for reading my message')
->salutation('Friendly, '.$this->user->name);
$this->storeEmail($mail,$notifiable);
return $mail;
}
public function storeEmail($mail,$notifiable){
$email = new EmailMessage;
$email->sender_type = 'App\User';
$email->sender_id = $this->user->id;
$email->mail = $mail;
$email->save();
$notifiable->email_messages()->save($email);
}
}
Note:
I'm using Illuminate\Notifications\Messages\MailMessage
My class extends Illuminate\Notifications\Notification
I'm saving (new MailMessage) in the $email->mail = $mail;
I tried to dd($email->mail); and I get this:
^ array:20 [▼
"view" => null
"viewData" => []
"markdown" => "notifications::email"
"theme" => null
"from" => array:2 [▶]
"replyTo" => []
"cc" => []
"bcc" => []
"attachments" => []
"rawAttachments" => []
"priority" => null
"callbacks" => []
"level" => "info"
"subject" => "My Dummy Subject"
"greeting" => "To: Dohn John"
"salutation" => "Friendly, Nikolas Diakosavvas"
"introLines" => array:2 [▶]
"outroLines" => array:1 [▶]
"actionText" => "Action Button"
"actionUrl" => "http://my-example-url.com ▶"
How can I display the Mail Notification, as it was when I sent it ? What is the optimal solution for that ?
Thanks, in advance
EDITED
Managed to render MailMessage using this code works :
$email = EmailMessage::first();
return (new \App\Notifications\SimpleEmail('my-link', $email->recipient->assignto))->toMail($email->recipient);
But this is not exactly what I wanted, because every time I need to find:
Which Notification class used on every email so I can render it.
Variables for each Notification class.

In order to accomplish this:
1. You can create a accessor.
2. Use Markdown's render method.
3. Pass in render method the mail's markdown you saved in storeEmail.
You can see an example above :
use \Illuminate\Mail\Markdown;
public function getRenderAttribute(){
$markdown = new Markdown(view());
return $markdown->render($this->mail['markdown'], $this->mail);
}

Related

Loop through multiple accounts in codeception functional test

I got multiple accounts: "userWithCertainRole", "userWithAnotherRole" & "userWithTwoRoles". I want to functional test a specific page for all these accounts with certain roles. The functional test is the same for all the accounts, so I don't want to duplicate the code or make multiple php files. Is there any way to loop through those three accounts in one functional test?
/**
* #var string|null
*/
protected ?string $account = 'userWithCertainRole';
/**
* #param FunctionalTester $I
*/
public function page(FunctionalTester $I)
{
$this->login($I);
$I->amOnPage('/page');
$I->dontSee('You cannot access this page with this role');
$I->see('Page header');
}
Use data provider as in PHPUnit, or Codeception specific Example anotation/attribute for providing data to test method.
See https://codeception.com/docs/AdvancedUsage#Examples-Attribute
/**
* #dataProvider getRoles
*/
public function page(FunctionalTester $I, Example $example)
{
$this->login($example['username'], $example['password']);
}
protected function getRoles(): array
{
// keys make test output easier to understand
return [
'userWithRole1' => ['username' => 'username1', 'password' => 'password1'],
'userWithRole2' => ['username' => 'username2', 'password' => 'password2'],
'userWithRole3' => ['username' => 'username3', 'password' => 'password3'],
];
}

Symfony notifier attach custom metadata to envelope

I'm using the Symfony notifier and messenger components to asynchronously send SMS messages (and in the future push and email notifications).
Everything works just fine, however once a message is sent, I'd like to log information about it.
I can catch a successful message by subscribing to WorkerMessageHandledEvent which provides me the Message object, along with the containing Envelope and all its Stamp objects inside. From all the available information, I'll be logging this in my database using an entity named MessageLog.
class MessengerSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
WorkerMessageHandledEvent::class => ['onHandled']
];
}
public function onHandled(WorkerMessageHandledEvent $event) {
$log = new MessageLog();
$log->setSentAt(new DateTime());
if($event->getEnvelope()->getMessage() instanceof SmsMessage) {
$log->setSubject($event->getEnvelope()->getMessage()->getSubject());
$log->setRecipient($event->getEnvelope()->getMessage()->getPhone());
}
// Do more tracking
}
}
What I'd like to do, is track the object that "invoked" the message. For example, if I have a news feed, and posting a post sends out a notification, I'd like to attribute each logged message to that post (to display audience reach/delivery stats per post - and from an admin POV auditing and reporting).
I've tried to go about adding a Stamp, or other means of trying to attach custom metadata to the message, but it seems to be abstracted when using the symfony/notifier bundle.
The below is what I'm using to send notifications (more or less WIP):
class PostService {
protected NotifierInterface $notifier;
public function ___construct(NotifierInterface $notifier) {
$this->notifier = $notifier;
}
public function sendNotifications(Post $post) {
$notification = new PostNotification($post);
$recipients = [];
foreach($post->getNewsFeed()->getSubscribers() as $user) {
$recipients[] = new Recipient($user->getEmail(), $user->getMobilePhone());
}
$this->notifier->send($notification, ...$recipients);
}
}
class PostNotification extends Notification implements SmsNotificationInterface {
protected Post $post;
public function __construct(Post $post) {
parent::__construct();
$this->post = $post;
}
public function getChannels(RecipientInterface $recipient): array {
return ['sms'];
}
public function asSmsMessage(SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage {
if($transport === 'sms') {
return new SmsMessage($recipient->getPhone(), $this->getPostContentAsSms());
}
return null;
}
private function getPostContentAsSms() {
return $post->getTitle()."\n\n".$post->getContent();
}
}
By the time this is all done, this is all I have in the WorkerMessageHandledEvent
^ Symfony\Component\Messenger\Event\WorkerMessageHandledEvent^ {#5590
-envelope: Symfony\Component\Messenger\Envelope^ {#8022
-stamps: array:7 [
"Symfony\Component\Messenger\Stamp\BusNameStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\BusNameStamp^ {#10417
-busName: "messenger.bus.default"
}
]
"Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp" => array:1 [
0 => Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp^ {#10419
-id: "2031"
}
]
"Symfony\Component\Messenger\Stamp\TransportMessageIdStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\TransportMessageIdStamp^ {#10339
-id: "2031"
}
]
"Symfony\Component\Messenger\Stamp\ReceivedStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\ReceivedStamp^ {#5628
-transportName: "async"
}
]
"Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp^ {#7306}
]
"Symfony\Component\Messenger\Stamp\AckStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\AckStamp^ {#7159
-ack: Closure(Envelope $envelope, Throwable $e = null)^ {#6205
class: "Symfony\Component\Messenger\Worker"
this: Symfony\Component\Messenger\Worker {#5108 …}
use: {
$transportName: "async"
$acked: & false
}
}
}
]
"Symfony\Component\Messenger\Stamp\HandledStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\HandledStamp^ {#11445
-result: Symfony\Component\Notifier\Message\SentMessage^ {#2288
-original: Symfony\Component\Notifier\Message\NullMessage^ {#6625
-decoratedMessage: Symfony\Component\Notifier\Message\SmsMessage^ {#10348
-transport: null
-subject: ".................................................."
-phone: "0412345678"
}
}
-transport: "null"
-messageId: null
}
-handlerName: "Symfony\Component\Notifier\Messenger\MessageHandler::__invoke"
}
]
]
-message: Symfony\Component\Notifier\Message\SmsMessage^ {#10348}
}
-receiverName: "async"
}
The doco shows me ways to add my own stamps to the envelope, which I'm guessing I can use to attach metadata such as my Post object, but this means I need to use the MessageBusInterface to send notifications. I don't want to do because I would like to route messages through the NotifierInterface to gain all the benefits of channel policies, texter transports, etc.
tl;dr: how do I get some metadata through to a WorkerMessageHandledEvent if I send a message using the NotifierInterface
I've found a way to make it work!
Essentially what happens is that we have two components here, the Symfony notifier and the Symfony messenger. When used together, they create a powerful way to send messages to any number of endpoints.
Firstly what I did was create an interface called NotificationStampsInterface and a trait called NotificationStamps that satisfies the interface (by storing a protected array using the interface methods to read/write to it).
class NotificationStampsInterface {
public function getStamps(): array;
public function addStamp(StampInterface $stamp);
public function removeStamp(StampInterface $stamp);
}
This interface can then be added onto your custom notification object, in this instance PostNotification, alongside with the NotificationStamps trait to satisfy the interface methods.
The trick here is that when sending a notification via the notifier, it ultimately calls on the messenger component to send the message. The bit that handles this is Symfony\Component\Notifier\Channel\SmsChannel. Essentially, if a MessageBus is available, it will push messages through that rather than going straight though the notifier.
We can extend the SmsChannel class to add our own logic inside notify() method.
class SmsNotify extends \Symfony\Component\Notifer\Channel\SmsChannel {
public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void {
$message = null;
if ($notification instanceof SmsNotificationInterface) {
$message = $notification->asSmsMessage($recipient, $transportName);
}
if (null === $message) {
$message = SmsMessage::fromNotification($notification, $recipient);
}
if (null !== $transportName) {
$message->transport($transportName);
}
if (null === $this->bus) {
$this->transport->send($message);
} else {
// New logic
if($notification instanceof NotificationStampsInterface) {
$envelope = Envelope::wrap($message, $notification->getStamps());
$this->bus->dispatch($envelope);
} else {
$this->bus->dispatch($message);
}
// Old logic
// $this->bus->dispatch($message);
}
}
}
Lastly we need to override the service by adding the following in services.yaml
notifier.channel.sms:
class: App\Notifier\Channel\SmsChannel
arguments: ['#texter.transports', '#messenger.default_bus']
tags:
- { name: notifier.channel, channel: sms }
And that's it! We now have a way to append stamps to our Notification object that will carry all the way through to the WorkerMessageHandledEvent.
An example use would be (for my situation at least)
class RelatedEntityStamp implements StampInterface {
private string $className;
private int $classId;
public function __construct(object $entity) {
$this->className = get_class($entity);
$this->classId = $entity->getId();
}
/**
* #return string
*/
public function getClassName(): string {
return $this->className;
}
/**
* #return int
*/
public function getClassId(): int {
return $this->classId;
}
}
class PostService {
protected NotifierInterface $notifier;
public function ___construct(NotifierInterface $notifier) {
$this->notifier = $notifier;
}
public function sendNotifications(Post $post) {
$notification = new PostNotification($post);
$stamp = new RelatedEntityStamp($post); // Solution
$notification->addStamp($stamp); // Solution
$recipients = [];
foreach($post->getNewsFeed()->getSubscribers() as $user) {
$recipients[] = new Recipient($user->getEmail(), $user->getMobilePhone());
}
$this->notifier->send($notification, ...$recipients);
}
}
Once the message is sent, dumping the result shows that we do indeed have our stamp registered at the point where our event fires.
^ Symfony\Component\Messenger\Event\WorkerMessageHandledEvent^ {#1078
-envelope: Symfony\Component\Messenger\Envelope^ {#1103
-stamps: array:8 [
"App\Notification\Stamp\RelatedEntityStamp" => array:1 [
0 => App\Notification\Stamp\RelatedEntityStamp^ {#1062
-className: "App\Entity\Post"
-classId: 207
}
]
"Symfony\Component\Messenger\Stamp\BusNameStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\BusNameStamp^ {#1063
-busName: "messenger.bus.default"
}
]
"Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp" => array:1 [
0 => Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp^ {#1066
-id: "2590"
}
]
"Symfony\Component\Messenger\Stamp\TransportMessageIdStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\TransportMessageIdStamp^ {#1067
-id: "2590"
}
]
"Symfony\Component\Messenger\Stamp\ReceivedStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\ReceivedStamp^ {#1075
-transportName: "async"
}
]
"Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp^ {#1076}
]
"Symfony\Component\Messenger\Stamp\AckStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\AckStamp^ {#1077
-ack: Closure(Envelope $envelope, Throwable $e = null)^ {#1074
class: "Symfony\Component\Messenger\Worker"
this: Symfony\Component\Messenger\Worker {#632 …}
use: {
$transportName: "async"
$acked: & false
}
}
}
]
"Symfony\Component\Messenger\Stamp\HandledStamp" => array:1 [
0 => Symfony\Component\Messenger\Stamp\HandledStamp^ {#1101
-result: Symfony\Component\Notifier\Message\SentMessage^ {#1095
-original: Symfony\Component\Notifier\Message\NullMessage^ {#1091
-decoratedMessage: Symfony\Component\Notifier\Message\SmsMessage^ {#1060
-transport: null
-subject: ".................................................."
-phone: "0412345678"
}
}
-transport: "null"
-messageId: null
}
-handlerName: "Symfony\Component\Notifier\Messenger\MessageHandler::__invoke"
}
]
]
-message: Symfony\Component\Notifier\Message\SmsMessage^ {#1060}
}
-receiverName: "async"
}

Handling rate limits when using an API in Laravel

In my Laravel application I am using the HubSpot API extensively to perform various actions. I have read in the documentation that you can make 150 requests per each 10 second period.
To monitor this HubSpot provide the following Headers when making any API call.
"X-HubSpot-RateLimit-Daily" => array:1 [▶]
"X-HubSpot-RateLimit-Daily-Remaining" => array:1 [▶]
"X-HubSpot-RateLimit-Interval-Milliseconds" => array:1 [▶]
"X-HubSpot-RateLimit-Max" => array:1 [▶]
"X-HubSpot-RateLimit-Remaining" => array:1 [▶]
"X-HubSpot-RateLimit-Secondly" => array:1 [▶]
"X-HubSpot-RateLimit-Secondly-Remaining" => array:1 [▶]
In my application I am making use of Laravel's Http Client, which is basically just a wrapper for Guzzle.
In order to adhere to the rate limits would I literally just have to wrap an if statement around every request?
Here's an example:
$endpoint = 'https://api.hubapi.com/crm/v3/owners/';
$response = Http::get($endpoint, [
'limit' => 100,
'hapikey' => config('hubspot.api_key'),
]);
In this case the $response would contain the Headers but would there be a way to effectively use them, as surely I would only know what the rates were once I'd made the API call?
I ask as I have to pull down 1,000 + deals and then update some records, but this would definately go over the API limit. For reference, here is the command I wrote.
<?php
namespace App\Console\Commands;
use App\Events\DealImportedFromHubspot;
use App\Hubspot\PipelineHubspot;
use App\Models\Deal;
use App\Models\DealStage;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class ImportHubspotDeals extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'import:hubspot-deals
{--force : Whether we should force the command}
';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Import Deal objects from the HubSpot API in bulk.';
/**
* An array to store imported Deals
*
* #var array
*/
private $importedDeals = [];
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return int
*/
public function handle()
{
$this->line('Importing Pipelines & Deal Stages from HubSpot API...');
PipelineHubspot::import();
$this->line('Importing Deals from HubSpot API...');
$this->getDealsFromHubspot();
$this->line('Found ' . count($this->importedDeals) . ' Deals to import');
if ($this->option('force')) {
$this->doImport();
} else {
if ($this->confirm('Do you want to import these deals? (yes|no)', false)) {
$this->doImport();
} else {
$this->line('Process aborted');
}
}
}
/**
* Grab Deals from Hubspot by calling the Deals API and looping through the paginated data
*
* #param int $limit: the number of deals per page
* #param string $next: the link to the next page of results
*/
private function getDealsFromHubspot(?int $limit = 100, string $next = null)
{
$endpoint = 'https://api.hubapi.com/crm/v3/objects/deals';
$properties = [
'limit' => $limit,
'properties' => implode(',', Deal::HUBSPOT_DEAL_PROPERTIES),
'hapikey' => config('hubspot.api_key'),
'associations' => 'engagements',
];
// If there's another page, append the after parameter.
if ($next) {
$properties['after'] = $next;
}
$response = Http::get($endpoint, $properties);
if ($response->successful()) {
$data = $response->json();
// If there are results, get them.
if (isset($data['results'])) {
foreach ($data['results'] as $hubspotDeal) {
$this->importedDeals[] = $hubspotDeal['properties'];
}
}
// If there's paginate we need to call the function on itself
if (isset($data['paging']['next']['link'])) {
$this->getDealsFromHubspot(null, $data['paging']['next']['after']);
}
}
$response->json();
}
/**
* Pull the Deal data in order to create a Deal model.
*
* #param array $data
*/
private function syncDeal(array $data)
{
$excludedDealStages = DealStage::excludeFromDealImport()->pluck('hubspot_id');
if ($excludedDealStages->contains($data['dealstage'])) {
return false;
}
$deal = Deal::updateOrCreate([
'hubspot_id' => $data['hs_object_id'],
], [
'name' => $data['dealname'],
'deal_stage_id' => $data['dealstage'],
'hubspot_owner_id' => $data['hubspot_owner_id'] ?? null,
]);
event(new DealImportedFromHubspot($deal));
return $deal;
}
/**
* Create and increment a nice progress bar as we import deals.
*/
private function doImport()
{
$bar = $this->output->createProgressBar(count($this->importedDeals));
$bar->start();
foreach ($this->importedDeals as $deal) {
$this->syncDeal($deal);
$bar->advance();
}
$bar->finish();
$this->newLine(2);
$this->line('Successfully imported ' . count($this->importedDeals) . ' Deals from HubSpot.');
}
}
Building on this event(new DealImportedFromHubspot($deal)); also makes an API call back to HubSpot to add the URL of the portal it had just been pulled into.
In this situation I'm thinking I either need to treat the deal importing as its own job, or add in some kind of rate limiter.
Would it be bad practise just to use sleep(10) to get around the rate limiting?
Sounds like a job for a Queue.
You can define your own rate limiter on the Queue, but the correct solution is probably to extend ShouldQueue and run $this->fail() when you get a response saying your request has been throttled.

Laravel 5: Odd "Undefined Variable" error when returning a defined array to View

As the title states, I'm getting an odd error in Laravel 5. I'm new to Laravel, and this week I dived into Jobs/Queues. I've gotten an "Undefined Variable: $errors" error in the past, and that one I was able to understand and fix. But now, I can't seem to get past this one. To my knowledge, everything looks fine. The following breakdown will (hopefully) give you an idea of what I'm doing/where the error happens:
class PostFormFields extends Job implements SelfHandling
{
use InteractsWithQueue, SerializesModels;
/**
* The id (if any) of the Post row
*/
protected $id;
/**
* List of fields and default value for each field
*/
protected $fieldList = [
'title' => '',
'subtitle' => '',
'page_image' => '',
'content' => '',
'meta_description' => '',
'is_draft' => '8',
'publish_date' => '',
'publish_time' => '',
'layout' => 'blog.layouts.post',
'tags' => [],
];
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($id = null)
{
$this->id = $id;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$fields = $this->fieldList;
if($this->id)
{
$fields = $this->fieldsFromModel($this->id, $fields);
} else {
$when = Carbon::now()->addHour();
$fields['publish_date'] = $when->format('M-j-Y');
$fields['publish_time'] = $when->format('g:i A');
}
/**
* Populate with old values, if they exist
* #var [type]
*/
foreach ($fields as $fieldName => $fieldValue)
{
$fields[$fieldName] = old($fieldName, $fieldValue);
}
$fields = array_merge($fields, ['allTags' => Tag::lists('tag')->all()]);
return $fields;
}
Above is the code inside the handler function of my Job class, the file it sits in is called PostFormFields.php. It's job, essentially, is just to return an array filled with all the values pertaining to a post, based on the Post Model and what's in the database that pertains to that specific Post ('title','content',etc) if a user's entered them in the past
public function create()
{
$data = $this->dispatch(new PostFormFields());
$data['title'] = 'testing';
var_dump($data);
return view('admin.post.create', $data);
}
Above is the code inside my PostController class, in the create() method. As you can tell, I'm using a resource controller for my Post Controller. It dispatches the PostFormFields Job and stores all the returned data in an array $data. However, since the create() method will be used to create a new post, only the keys should be returned, with values set to their default value ''.
This works. As you can see, i run a 'var_dump()' on the variable $data to see what, if anything, is returned. I then pass the $data array to the create View. This is where the error comes up.
Laravel "Undefined Varieble" Error
Above is a picture of the error I get when I try to access the /create route. It's clear that the $data does have the $title variable defined, as well as all the other keys in the array. Why am I getting an "Undefined Variable" array when I clearly have it defined by the time it's sent to the create View?
The line of code is says the error is in is the following:
<input type="text" class="radius" name="title" id="title" value="{{ $title }}">
You have to pass that array to view via compact function of laravel. So that you can use it in view as you want.
Please check about compact here - https://laracasts.com/discuss/channels/general-discussion/phps-compact-pros-and-cons?page=1
public function create()
{
$data = $this->dispatch(new PostFormFields());
$data['title'] = 'testing';
var_dump($data);
return view('admin.post.create', compact('data'));
}

How to include fractal transformed objects directly to collection meta without data key

I'm using league/fractal with JsonApiSerializer,
I've got users collection for json output.
Now I want to add some filters data to this json response (like users count for current filters).
I got this:
$resource = new Collection($dataProvider->getData(), new UserTransformer());
//the only way to include some not directly linked data i found is using setMeta():
$resource->setMetaValue('projects', $dataProvider->getProjects());
$resource->setMetaValue('somes', $dataProvider->getTasks());
But! 'projects' & 'somes' collections (yes, they are collection too) also included with 'data' key in it.
So, I've got this structure:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {
'data' => {...}
},
'somes' => {
'data' => {...}
}
}
}
but I want something like:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {...}, //there is no 'data' key
'somes' => {...} //there is no 'data' key
}
}
What should I do?
This is kinda hack but works fine without refactor Scope class which hardcoded in fractal's League\Fractal\Manager::createData() and is only way to use your own Scope class realization is to overload this method in Manager's extension.
<?php
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class EmbedSerializer
*/
class EmbedSerializer extends JsonApiSerializer
{
const RESOURCE_EMBEDDED_KEY = 'embedded';
/**
* Serialize a collection.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function collection($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => $data];
}
/**
* Serialize an item.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function item($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => [$data]];
}
}
So, now i could use it like:
/** #var $this->fractal League\Fractal\Manager */
$this->fractal->setSerializer(new EmbedSerializer());
$projectsCollection = $this->fractal->createData(
new Collection($projects, new UserProjectTransformer(), 'embedded')
)->toArray();
$resource = new Collection($users, new UserTransformer());
$resource->setMetaValue('projects', $projectsCollection);
That's all u need. Hope this will be helpful.

Categories