how to intercept graphql request in api-platform? - api-platform.com

i'm using api-platform 2.3.5 and i can't find a way to intercept a graphQl request.
I mean that let's say i'm making a mutation (update) and want to also log the data or send an email. How do i do that ?
I did read the api-platform documentation, but there's very little about their implementation of graphQl. It does quite a lot automagically.
Events are not yet implemented (https://github.com/api-platform/core/pull/2329)
I also found this - https://github.com/api-platform/core/blob/master/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php#L101
but i'd rather not touch it. Is there a simpler way ?

I know this is a bit old but if only for reference.
The recommended way with API-Platform's graphql is to use stage. See this documentation.
In the case of the sending an e-mail you can use the serialize stage. See example below for sending email to notify user he has received a message.
<?php
namespace App\Stage;
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
use App\Email\Mailer;
final class SerializeStage implements SerializeStageInterface
{
/**
* #var Mailer
*/
private $mailer;
private $serializeStage;
public function __construct(
SerializeStageInterface $serializeStage,
Mailer $mailer)
{
$this->serializeStage = $serializeStage;
$this->mailer = $mailer;
}
/**
* #param object|iterable|null $itemOrCollection
*/
public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array
{
// Call the decorated serialized stage (this syntax calls the __invoke method).
$serializedObject = ($this->serializeStage)($itemOrCollection, $resourceClass, $operationName, $context);
// send notification e-mail if creating message
if ($resourceClass === 'App\Entity\Message' && $operationName === 'create') {
$this->mailer->sendMessageNotificationEmail($itemOrCollection->getReceiver());
}
return $serializedObject;
}
}
Then we need to decorate the native stage:
/config/services.yaml
App\Stage\SerializeStage:
decorates: api_platform.graphql.resolver.stage.serialize

Related

Using adapter pattern when consuming 3rd party APIs and creating a domain in Laravel

In my Laravel REST API project I mainly consume 3rd party APIs. For that, I have a 'Services' folder grouped by APIs (Accommodation, Planning, Discount etc) to send correct parameters to APIs and get the raw data.
I feel that I need to use adapter pattern here, because this 3rd party outputs need to be formatted.
Let me try to give an example.
I have an EventDao interface (Data access object) and one or multiple concrete EventDao classes like EventbriteDao.
I also have an adapter for each concrete DAO. e.g: EvenbriteAdapter
I will have some business logic somewhere so I need to have an Event entity class. To pass the adapter's data to the entity, I need an EventDTO class (data transfer object)
Finally I can call the eventDao (via interface thanks to Laravel's service providers)
I pass its raw output to the adapter, then adapter's output to the entity. Then I can call one of methods of the entity class. (After that I need to convert it to a proper json data, but it is the easy part.)
I don't get where to put other CRUD logic like updateEvent, deleteEvent, getAll etc. Should I call them directly in controller or create repositories (the repository pattern)? I am confused about it.
Would it be a good approach or over-engineering? Because I have 5-6 classes/interfaces other than controller.
Another problem is calling EventbriteAdapter directly in the controller. Should I have an interface for it? In this case I will need to bind both service and adapter interfaces to its implementations in AppServiceProvider.
My another concern is having collections for entity chunks. If I call getAll() method I can loop through the data and create an array of objects, but I am not comfortable with it. Laravel's collections would be useful, but I want to separate application layer from domain. What would be a good solution for it?
Here example codes for some of the files I mentioned are:
interface EventAdapter
{
public function getId();
public function getName();
public function getStartDate();
public function getLocationName();
}
class EventbriteAdapter implements EventInterface
{
private $raw;
public function __construct(array $raw)
{
$this->raw = $raw;
}
public function getName()
{
return $this->raw['name'];
}
public function getStartDate()
{
return $this->raw['start'];
}
public function getLocation()
{
return $this->raw['venue']['name'].' '.$this->raw['venue']['address'];
}
}
// Fetch event from Eventbrite (or other third-party)
$result = fetchEventFromEventbrite();
// Wrap event in adapter
$adapter = new EventbriteAdapter($result);
I don't get where to put other CRUD logic like updateEvent,
deleteEvent, getAll etc. Should I call them directly in controller or
create repositories (the repository pattern)? I am confused about it.
You can use the Gateway pattern to encapsulate all logic of interaction with 3rd party API. The gateway can contain methods to get data and save data. For example:
interface EventGatewayInterface
{
/**
* Get an event by id
*
* #param mixed $id unique identifier of an event
* #return Event the event
*/
public function getById($id);
/**
* Get all events
*
* #return Event[] list of events
*/
public function getAll();
/**
* Add an event
*
* #param Event $event the event which will be added
*/
public function add(Event $event);
/**
* Update an event
*
* #param Event $event the event which will be updated
*/
public function update(Event $event);
/**
* Delete an event
*
* #param Event $event the event which will be removed
*/
public function delete();
}
Would it be a good approach or over-engineering? Because I have 5-6
classes/interfaces other than controller.
In fact, you use the Adapter pattern to translate one representation (raw representation) to another (object representation). But the Adapter pattern is used to translate one program interface into a compatible program interface. To convert representations used the Mapper pattern. You can implement simple mapper for example:
class RawEventMapper
{
/**
* Map raw data to an event object
*
* #param array $data raw data
* #return Event the event object
*/
public function map(array $data)
{
$event = new Event();
$event->name = $data['name'];
$event->startDate = $data['start'];
$event->location = $data['venue']['name'].' '.$data['venue']['address'];
return $event;
}
}
and use it in Gateway to convert raw data to an object.
class ConcreteEventGateway implement EventGatewayInterface
{
/** #var RawEventMapper data mapper */
protected $mapper;
/**
* Constructor
*
* #param RawEventMapper $mapper data mapper
*/
public function __construct(RawEventMapper $mapper)
{
$this->mapper = $mapper;
}
/** #inheritdoc */
public function getById($id)
{
// get raw data from 3rd party API
return $this->mapper->map($raw);
}
}
Laravel's collections would be useful, but I want to separate
application layer from domain. What would be a good solution for it?
If you aim to create a domain layer independent on the framework you shouldn't use Laravel collection.

How can I change SMTP details globally at runtime?

I'm using Laravel 5.5. The nature of the website is a 'multisite' architecture where multiple websites/domains are run from the same codebase.
I've come across an issue when sending email. I need to change the from name and address as well as the transport (SMTP, etc) options depending on which website is being viewed. I have these details stored in a config file.
The easiest way is to just pull those details in the Controller before I call Mail::send/Mail::queue and to update them. However, this brings back 2 issues:
There is a heavy reliance on remembering to actually do that every time I send any email in the code. In short, it's not abiding by DRY.
I'd be forced to use Mail::send instead of Mail::queue, because the queue wouldn't have any idea of the config update from the time it was queued only from when it is processed .
How can I achieve what I am looking to do here in a clean way?
I thought about extending all of my 'Mailable' classes with a custom class that updates the SMTP details, but it doesn't look like you can update the SMTP/Transport information after the class is initiated; you can only update the from name and address.
I managed to find a way to do this.
I had my mailable class (ContactFormMailable) extend a custom class, as follows:
<?php
namespace CustomGlobal\Mail;
use CustomGlobal\Mail\CustomMailable;
use CustomGlobal\ContactForm;
class ContactFormMailable extends CustomMailable
{
public $contact_form;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(ContactForm $contact_form)
{
$this->contact_form = $contact_form;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->get_custom_mail_view('contact_form', $this->contact_form);
return $this->subject('Contact Form Enquiry')
->view($view);
}
}
You'll notice I'm calling get_custom_mail_view. This is in my extended class and used to calculate the view and template I need to use for my mail, depending on the website being viewed. In here I also set the location of my config folder.
<?php
namespace CustomGlobal\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Swift_Mailer;
use Swift_SmtpTransport;
use CustomGlobal\Website;
use CustomGlobal\Territory;
class CustomMailable extends Mailable
{
use Queueable, SerializesModels;
public $layout_view_to_serve;
public $host_folder;
/**
* Override Mailable functionality to support per-user mail settings
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(Mailer $mailer)
{
app()->call([$this, 'build']);
$config = config($this->host_folder .'.mail');
// Set SMTP details for this host
$host = $config['host'];
$port = $config['port'];
$encryption = $config['encryption'];
$transport = new Swift_SmtpTransport( $host, $port, $encryption );
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) use($config) {
$message->from([$config['from']['address'] => $config['from']['name']]);
$this->buildFrom($message)
->buildRecipients($message)
->buildSubject($message)
->buildAttachments($message)
->runCallbacks($message);
});
}
/**
* Calculate the template we need to serve.
* $entity can be any object but it must contain a
* $website_id and $territory_id, as that is used
* to calculate the path.
*/
public function get_custom_mail_view($view_filename, $entity)
{
if(empty($view_filename)) {
throw new Exception('The get_custom_mail_view method requires a view to be passed as parameter 1.');
}
if(empty($entity->website_id) || empty($entity->territory_id)) {
throw new Exception('The get_custom_mail_view method must be passed an object containing a website_id and territory_id value.');
}
// Get the website and territory
$website = Website::findOrFail($entity->website_id);
$territory = Territory::findOrFail($entity->territory_id);
$view_to_serve = false;
$layout_view_to_serve = false;
// Be sure to replace . with _, as Laravel doesn't play nice with dots in folder names
$host_folder = str_replace('.', '_', $website->website_domain);
$this->host_folder = $host_folder; // Used for mail config later
/***
Truncated for readability. What's in this area isn't really important to this answer.
***/
$this->layout_view_to_serve = $layout_view_to_serve;
return $view_to_serve;
}
}
It's important to remember that mail can be queued. If you do this is another way, such as setting a config at runtime, then you'll find that the process that runs the queue has no visibility/scope of your runtime config changes, and you'll end up firing out email from your default values.
I found a few answers similar to this one, which helped me out, but none of them worked completely, and some are out-dated (Swift_SmtpTransport is changed considerably since those answers).
Hopefully this helps someone else out.

How to add an Action to Account Controller in Shopware

How to add a custom action to an existing Controller in Shopware?
Examples (url structure):
/account/bonus
/account/custom
/account/...
Usually it's easier and cleaner to create a new controller for that purpose, but in some cases it's necessary.
You should not replace the "account" controller.
You can define you own action for existing controller with following:
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_Frontend_Account_MyBonus' => 'onAccountMyBonus',
];
}
and then
public function onAccountMyBonus(\Enlight_Event_EventArgs $args)
{
$args->setProcessed(true);
.....
your code here
}
Spoiler: Replace the controller
There is no cleaner way than to replace the whole controller and extend it's functionality, so it's nearly as clean as Shopware's hooks.
Guide
Add a new Subscriber to your Plugin
class AccountSubscriber implements SubscriberInterface
{
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
'Enlight_Controller_Dispatcher_ControllerPath_Frontend_Account' => 'getAccountController'
);
}
/**
* #return string
*/
public function getAccountController()
{
return $this->getPath() . '/Controllers/Frontend/AccountExtended.php';
}
/**
* #return string
*/
public function getPath()
{
$plugin = Shopware()->Container()->get('kernel')->getPlugins()['AcmeYourPlugin'];
return $plugin->getPath();
}
}
Downsides
Unfortunately some controller have private methods which impact the logic. Like the Account Controller. So it's not always possible to simply extend the controller.
In the end, try to add a new controller with a new route.
It's easier, and cleaner.
There is a cleaner way than replacing the whole Controller.
It is also not recommended to replace a whole controller due to the lack of update compatibility.
In the worst case something like that could kill the whole website.
A while ago I created a thread in the shopware forum (german) discussing the exact same issue. I wanted to extend an existing finishAction() in the checkout Controller.
public function onPostDispatchCheckout(\Enlight_Controller_ActionEventArgs $args)
{
/** #var \Enlight_Controller_Action $controller */
$controller = $args->getSubject();
/** #var \Enlight_Controller_Request_Request $request */
$request = $controller->Request();
if ($request->getActionName() !== 'finish') {
return;
}
// do your stuff here
}
So even though it is not the exact same issue you have, the procedure is quite the same.
First off you subscribe to the controller (in my case the PostDispatchCheckout Controller) afterwards you edit the controller in your Bootstrap.php
To make sure, that it just alters a specific action you have to use the if-construction so your code gets just triggered on the wished action [in my case the finishAction()].
I hope this helps. What wonders me though is why you have to add a new action to an already existing controller. I can think of no situation where something like that is more practicable than creating a complete new custom controller.
Kind regards,
Max

Set Mailable header via a trait?

I'm creating a Laravel package that would benefit from the use of emails. When a user would use my package they would want to email a file created by the package, but also set some custom headers for the email.
In an ideal solution, I would like to have a trait that the developer could simply use on their mailable class and it would automatically set the header for that email without any additional code. Is this something that is even possible via the use of a trait?
Some solutions have suggested adding headers to mailables by putting this in the build method:
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
But is there some way to have my own custom trait piggy-back on the build method of the Mailable that is using it WITHOUT having to write it in the Mailable class itself?
SOLUTION USING TRAITS
The only way to be able to do that with a trait would be defining the build method in your trait and make your user define another function instead of build such that you have the direct manipulation of the function actually used by Mailable class.
So your trait would be:
trait IsMailable {
public function build()
{
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
if(!method_exists($this, 'buildMail')) throw \Exception('buildMail is not defined!');
return $this->buildMail();
}
}
So your user will have to define the method buildMail instead of build.
OPTIMAL SOLUTION
The optimal solution, IMHO, would be extending the class Illuminate\Mail\Mailable redefining the method send and making the end user implement this newly defined class instead of Illuminate\Mail\Mailable.
So your class would be:
class Mailable extends \Illuminate\Mail\Mailable {
/**
* Send the message using the given mailer.
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(MailerContract $mailer)
{
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
parent::send($mailer);
}
}
Doing that your user can use the build method as it would do using the standard Illuminate\Mail\Mailable class but the end result would be that your class is piggybacking the additional information that you actually need.

Best way of passing response information from Model to Controller using Laravel

The Model View Controller architecture tells me that all my business logic should be inside the Model, while the data flow should be handled by the Controller.
Knowing this, while I'm dealing with my logic inside the Model, I need to let the Controller know if he's supposed to redirect to another url, redirect back, what kind of message or variable to pass during the redirection, etc.
What is the best way of doing this?
I can think of some ways, like throwing exceptions on the Modeland catching them on the Controller or returning an array from the Model and treating it on the Controller, but none of them seem very nice. The easiest way would be calling the Redirect->to() (or back()) inside the Model and just returning the Model's return on the Controller, but it seem to break the architecture's separation of rules.
Is there a "right" way of doing this? What would be the pros and cons of each way?
EDIT:
The answer below is old. Laravel now includes a bunch of different ways of handling common problems.
For example, use Laravel's FormRequest's as a way of validating data easily on controller methods, and Jobs to handle business logic for creating / updating models.
OLD POST:
This is a common question, and while the 'MVC' pattern is nice for a basic starting point for a web app, I feel like the majority of developers always need another intermediate service for validation, data handling, and other problems that come up during development.
To answer your question without bias: There is no right way.
To answer your question with my own personal bias, I feel the majority of developers will use the Repositories or Services pattern to handle intermediate data handling between the controller and the model, and also have separate classes for validation as well.
In my opinion, Repositories are better for a framework and data agnostic design (due their interface driven implementation), and Services are better for handling the business logic / rules. Controllers are better used for handling responses and for passing the input data to the repository or the service.
The paths for each of these patterns are the same though:
Request -> Controller (Validation) -> Service -> Model -> Database
Request -> Controller (Validation) -> RepositoryInterface -> Model -> Database
Validation is in brackets since input isn't passed from the validator to the service / repository, the input sent to the validator, gives the 'OK', and let's the controller know it's ok to send the data to the Service / Repository to be processed.
I only use Services when I'm absolutely positive I won't be changing frameworks or data sources. Otherwise I'll use Repositories. Repositories are just a little more work to setup, since you'll need to make Laravel resolve the interface to your repository class through its IoC.
Services Example:
The Service:
namespace App\Services;
use App\Models\Post;
class PostService
{
/**
* #var Post
*/
protected $model;
/**
* Constructor.
*
* #param Post $post
*/
public function __construct(Post $post)
{
$this->model = $post;
}
/**
* Creates a new post.
*
* #param array $input
*/
public function create(array $input)
{
// Perform business rules on data
$post = $this->model->create($input);
if($post) return $post;
return false;
}
}
The Controller:
namespace App\Http\Controllers;
use App\Services\PostService;
use App\Validators\PostValidaor;
class PostController extends Controller
{
/**
* #var PostService
*/
protected $postService;
/**
* #var PostValidator
*/
protected $postValidator;
/**
* Constructor.
*
* #param PostService $postService
* #param PostValidator $postValidator
*/
public function __construct(PostService $postService, PostValidator $post Validator)
{
$this->postService = $postService;
$this->postValidator = $postValidator;
}
/**
* Processes creating a new post.
*/
public function store()
{
$input = Input::all();
if($this->postValidator->passes($input)) {
// Validation passed, lets send off the data to the service
$post = $this->postService->create($input);
if($post) {
return 'A post was successfully created!';
} else {
return 'Uh oh, looks like there was an issue creating a post.';
}
} else {
// Validation failed, return the errors
return $this->postValidator->errors();
}
}
}
Now with this pattern, you have a nice separation of all your processes, and a clear indication of what each of them do.
For a repository example, Google 'Laravel Repository Pattern'. There are tons of articles about this.
Actually - in Laravel 5 that is not the best way to do it. Business logic should not be in models. The only thing that models should do is retrieve and store data from your database.
You are better off using the CommandBus or ServiceProviders to handle application logic and business rules. There are many articles on the web about these, but personally I prefer laracasts.com as the best learning resource.

Resources