Magento 2, call external web service when order is placed - web-services

I've been digging around and I can't seem to find the answer to what I'm looking for at all.
I need to call an external web service when an order is placed in magento 2. I also need to use the order information in the call. I simply dont know what the right path is. Has anyone done this before?

I know the answer is late, but here is the solution that works for me, I have written observer for event checkout_submit_all_after.
Create file at path [CompanyName]/[ModuleName]/etc/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_submit_all_after">
<observer name="push_orders_to_purchaser" instance="RLTSquare\Quote\Observer\PushOrdersToPurchaserApi" />
</event>
</config>
Now in observer ([CompanyName]/[ModuleName]/observer/PushOrdersToPurchaserApi.php)
<?php
namespace RLTSquare\Quote\Observer;
use \Magento\Framework\Event\Observer;
use \Magento\Framework\Event\ObserverInterface;
use Magento\Framework\HTTP\Adapter\Curl;
class PushOrdersToPurchaserApi implements ObserverInterface
{
const MOCK_API = 'https://yourwebsite.com/insertorders';
protected $_request;
protected $_webApiRequest;
protected $_serviceOutputProcessor;
protected $_zendClientFactory;
public function __construct
(
\Magento\Framework\App\RequestInterface $request,
\Magento\Framework\Webapi\Rest\Request $webApiRequest,
\Magento\Framework\Webapi\ServiceOutputProcessor $serviceOutputProcessor,
\Magento\Framework\HTTP\ZendClientFactory $zendClientFactory
)
{
$this->_request = $request;
$this->_webApiRequest = $webApiRequest;
$this->_serviceOutputProcessor = $serviceOutputProcessor;
$this->_zendClientFactory = $zendClientFactory;
}
public function execute(Observer $observer)
{
/**
* #var \Magento\Sales\Model\Order[] $orders
*/
$orders = $observer->getOrders();
$encodedData = null;
if ($orders) {
$data = $this->_serviceOutputProcessor->convertValue($orders, '\Magento\Sales\Api\Data\OrderInterface[]');
$payload = \Zend_Json::encode($data, true);
$client = $this->_zendClientFactory->create();
$client->setUri(self::MOCK_API);
$client->setConfig(['maxredirects' => 0, 'timeout' => 30]);
$client->setRawData($payload);
$response = $client->request(\Zend_Http_Client::POST)->getBody();
//play with response here...
}
}
}

Related

Symfony 4 Mock service in functional test

I am testing a service which essentially is mostly serializing an object and sending it via a service to an external system.
If I create the typical unittest I would mock the response of the serializer and of the service, which contacts the external system. In fact there would be not much left to test except calling a bunch of setter Methods in my object.
The alternative would be using a KernelTestCase and creating a functional test, which would be fine except I don't want to contact the external system, but to use a mock only for this "external" service.
Is there any possibility to achieve this in Symfony 4?
Or is there another approach to this?
What I am doing now is the following:
<?php
namespace App\Tests\Service;
use App\Service\MyClassService;
use App\Service\ExternalClient\ExternalClient;
use JMS\Serializer\Serializer;
use JMS\Serializer\SerializerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
class MyClassServiceTest extends KernelTestCase
{
/** #var LoggerInterface */
private $logger;
/** #var Serializer */
private $serializer;
/** #var ExternalClient */
private $externalClient;
/** #var RequestInterface */
private $request;
/** #var MyClassService */
private $myClassService;
public function setUp()
{
$kernel = self::bootKernel();
$this->logger = $kernel->getContainer()->get(LoggerInterface::class);
$this->serializer = $kernel->getContainer()->get(SerializerInterface::class);
$this->externalClient = $this->createMock(ExternalClient::class);
}
public function testPassRegistrationData()
{
$getParams = [
'amount' => '21.56',
'product_id' => 867,
'order_id' => '47t34g',
'order_item_id' => 2,
'email' => 'kiki%40bubu.com',
];
$this->generateMyClassService($getParams);
$userInformation = $this->myClassService->passRegistrationData();
var_dump($userInformation);
}
/**
* generateMyClassService
*
* #param $getParams
*
* #return MyClass
*/
private function generateMyClassService($getParams)
{
$this->request = new Request($getParams, [], [], [], [], [], null);
$this->myClassService = new MyClassService(
$this->logger,
$this->serializer,
$this->externalClient,
$this->request
);
}
}
Give back this error:
Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service "App\Service\MyClassConfirmationService": argument "$request" of method "__construct()" references class "Symfony\Component\HttpFoundation\Request" but no such service exists.
You shouldn't inject Request into your services. You should use Symfony\Component\HttpFoundation\RequestStack instead of Request. Also, you should check if $requestStack->getCurrentRequest() doesn't return null. I suppose you get such error in process of container's initialization but you execute just a script (test) and of course, you don't have Request on it.
Since the whole thing needed some more tweak here I post my solution to which Nikita's answer lead me:
As he suggested I replaced "request in my service with RequestStack which worked out fine:
/**
* #param LoggerInterface $logger
* #param SerializerInterface $serializer
* #param ExternalClient $externalClient
* #param RequestStack $requestStack
*/
public function __construct(
LoggerInterface $logger,
SerializerInterface $serializer,
ExternalClient $externalClient,
RequestStack $requestStack
) {
$this->logger = $logger;
$this->serializer = $serializer;
$this->externalClient = $externalClient;
$this->request = $requestStack->getCurrentRequest();
$this->params = $this->request->query;
}
In my test I faked the request like this:
$this->request = new Request($getParams, [], [], [], [], [], null);
$this->requestStack = new RequestStack();
$this->requestStack->push($this->request);
However with that having fixed my next problems arised, since my class also asks for the logger and the serializer.
For the Logger I used a general Loggerclass I created especially for this test situations. But that leaves me to get the serializer and I wanted the real one or else I could have stuck to a mostly useless UnitTest.
That is what I did:
public function setUp()
{
$kernel = self::bootKernel();
$container = self::$container;
$this->serializer = $container->get('jms_serializer.serializer');
}
This then gave me the real serializer from the container.
Now I can let my mocked external client give me mocked answers and I can test my service for reaction without bothering an external service.

Laravel 5.5 Service Provider

I am trying to have a custom service provider with common methods that I am using throughout my application. However I am getting an error target not instantiable. I have the same working fine in Laravel 5.3 but now its not working in Laravel 5.5. Here is my code:
In the app\Helpers folder, I have created a folder Contracts with an interface FrontendContracts.
namespace App\Helpers\Contracts;
Interface FrontendContracts{
public function randomString($len);
}
In app\Helpers I have a class FrontendMethods which implements the interface
namespace App\Helpers;
use App\Helpers\Contracts\FrontendContracts;
class FrontendMethods implements FrontendContracts{
public function randomString($len){
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmonpqrstuvwxyz";
$result = "";
$charArray = str_split($chars);
for($i = 0; $i < $len; $i++){
$randItem = array_rand($charArray);
$result .= "".$charArray[$randItem];
}
$result .= time();
return $result;
}
}
In the app\Providers I have FrontendServiceProvider class with:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Helpers\FrontendMethods;
class FrontendServiceProvider extends ServiceProvider{
protected $defer = true;
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot(){
//
}
/**
* Register the application services.
*
* #return void
*/
public function register(){
$this->app->bind('App\Helpers\Contracts\FrontendContracts', function(){
return new FrontendMethods();
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides(){
return ['App\Helpers\Contracts\FrontendContracts'];
}
}
I have registered the provider in the providers array as:
App\Providers\FrontendServiceProvider::class,
I am getting the error message Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}
and Target [App\Helpers\Contracts\FrontendContracts] is not instantiable.
Can someone kindly point at to me what I am doing wrong?
I don't think you need the provides() method in your FrontendServiceProvider class. Try removing this.

What is the best practice for repository?

In my repositories, I have methods with too many arguments (for use in where) :
Example :
class ProchaineOperationRepository extends EntityRepository
{
public function getProchaineOperation(
$id = null, // Search by ID
\DateTime $dateMax = null, // Search by DateMax
\DateTime $dateMin = null, // Search by DateMin
$title = null // Search by title
)
In my controllers, I have differents action ... for get with ID, for get with ID and DateMin, for get ID and Title, ...
My method is too illegible because too many arguments ... and it would be difficult to create many methods because they are almost identical ...
What is the best practice ?
You have two main concerns in your question
You have too many arguments in your repository method which will be used in 'where' condition of the eventual query. You want to organize them in a better way
The repository method should be callable from the controller in a meaningful way because of possible complexity of arguments passed
I suggest you to write a Repository method like:
namespace AcmeBundle\Repository;
/**
* ProchaineOperationRepository
*
*/
class ProchaineOperationRepository extends \Doctrine\ORM\EntityRepository
{
public function search($filters, $sortBy = "id", $orderBy = "DESC")
{
$qb = $this->createQueryBuilder("po");
foreach ($filters as $key => $value){
$qb->andWhere("po.$key='$value'");
}
$qb->addOrderBy("po.$sortBy", $orderBy);
return $qb->getQuery()->getArrayResult();
}
}
The $filters variable here is an array which is supposed to hold the filters you are going to use in 'where' condition. $sortBy and $orderBy should also be useful to get the result in properly sequenced way
Now, you can call the repository method from your controller like:
class ProchaineOperationController extends Controller
{
/**
* #Route("/getById/{id}")
*/
public function getByIdAction($id)
{
$filters = ['id' => $id];
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters);
//process $result
}
/**
* #Route("/getByTitle/{title}")
*/
public function getByTitleAction($title)
{
$filters = ['title' => $title];
$sortBy = 'title';
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy);
//process $result
}
/**
* #Route("/getByIdAndDateMin/{id}/{dateMin}")
*/
public function getByIdAndDateMinAction($id, $dateMin)
{
$filters = ['id' => $id, 'dateMin' => $dateMin];
$sortBy = "dateMin";
$orderBy = "ASC";
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy, $orderBy);
//process $result
}
}
Note that you are calling the same repository method for all controller actions with minor changes according to your parameters. Also note that $sortBy and $orderBy are optionally passed.
Hope it helps!
If your objective is only to query with an AND operator between each properties, the best way could be to use the method proposed by doctrine for that : findBy() cf : this part of the doc
for instance :
$results = $this
->getDoctrine()
->getRepository('AppBundle:ProchaineOperation')
->findBy(array('dateMax' => $myDate, 'title' => 'Hello world');
EDIT : after comment
Then use the same way as Doctrine do : Pass only an array with id, dateMax... as keys if these are set. This should be solve the method signature problem which gives you so much trouble. :)

PHPunit: testing a Repository

I am testing my repository in Laravel and I came across a few issues, most probably with regards to the structure of my methods.
So, my repository looks like:
<?php
namespace Repositories\User;
use App\Test\Models\Entities\User;
use Illuminate\Database\Eloquent\Model;
class UserRepository implements UserInterface
{
/**
* #var Model $userModel
*/
protected $userModel;
/**
* Setting our class $userModel to the injected model
*
* #param Model $userModel
* #return UserRepository
*/
public function __construct(Model $userModel)
{
$this->userModel = $userModel;
}
/**
* Returns the User object associated with the userEmail
*
* #param string $userEmail
* #return User | null
*/
public function getUserByEmail($userEmail)
{
// Search by email
$user = $this->userModel
->where('email', '=', strtolower($userEmail))
->first();
if ($user) {
return $user->first();
}
return null;
}
}
/**
* #param $id
* #param $email
* #param $source
*
* #dataProvider usersDataProvider
*/
public function testGetUserByEmail($id, $email, $source)
{
$user = new User();
$user->id = $id;
$user->email = $email;
$user->user_source_id = $source;
$this->user->shouldReceive('getUserByEmail')->once()
->andReturn($user);
}
I am quite new working with Mockery and am just wondering whether I am following the correct approach in order to test my getUserByEmail($email) method. Please bare in mind that (as expected) getUserByEmail($email) makes a call to the Database.
Also, this is the message that I receive:
PHP Fatal error: Call to a member function connection() on null in /private/var/www/ff-php-prelaunch/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php, which probably implies that there is no initialized connection to the DB.
UPDATE
Btw, my setUp() is as follows:
public function setUp()
{
$this->user = Mockery::mock('App\Test\Models\Entities\User');
parent::setUp();
}
You probably don't extend the default TestCase class provided by Laravel in the tests directory. Your environment is not correct and you have no connection to the database.
If you don't want to actually query the database you should use Mockery to create a mock of your dependencies (here $userModel). You basically create a Mock the following way, I didn't test this code but the general idea is here.
protected function setUp() {
parent::setUp();
$userModelMockedMethods = [
'where' => 'some return'
];
// This is our dependency mock
$userModelMock = Mockery::mock(Model::class, $userModelMockedMethods);
// this mock now will return 'some return' if you call the `where` method
// on it. If you wish the where method to return something callable, you
// should return another mock instead of a string
// This replaces the mock in the dependency manager.
$this->app->instance(Model::class, $UserModelMock);
}

phpunit mock web service(not WSDL)

I have a small problem which I think is quite simple to solve for experienced PHPUnit users.
I'm working with ZF2.
I'm working with a web service that returns plain text(CSV). I'd like to unit test the service that I've created.
I currently have a working configuration which is not the right way to do it I think.. I'm using mocks now when I'm unit testing my models and I have seen that PHPUnit has a special mock for web services, but that only supports WSDL.
Beneath you'll find my code and I hope someone can help me out with some explanation about the best practice for this situation.
The docs and the topics out here did not help me out (yet).
Thanks in advance!
The test itself:
public function testCanSearchSteeringWheels()
{
// Create the entry and fill it with the data that should be retrieved from the web service
$steeringWheelEntity = new SteeringWheelEntity();
$steeringWheelEntity->setId('170633')
->setName('Nice steering wheel one')
->setGrossPrice(100)
->setNetPrice(75);
// Setup the http client which whill make the final call to the web service
$httpClient = new Client();
$httpClient->setOptions(array(
'maxredirects' => 5,
'timeout' => 60,
))
->setAuth($this->config['supplier_name']['api']['username'], $this->config['supplier_name']['api']['password'])
;
$steeringWheelService = new SteeringWheelService($httpClient, new Request(), $this->config['supplier_name']);
// Search for a steering wheel by id code
$searchResult = $steeringWheelService->search('ID=5221552658987');
$this->assertEquals($steeringWheelEntity, $searchResult[0]);
}
The SteeringWheelEntity
namespace SupplierName\Entity;
class SteeringWheelEntity
{
// vars
// exchange array method
// getters methods
// setters methods
}
The SteeringWheelService
namespace SupplierName\Service;
use SupplierName\Entity\SteeringWheelEntity;
class SteeringWheelService extends AbstractWebService
{
/**
* search()
*
* #param string $param
* #return array
*/
public function search($param)
{
$this->appendUrl('ww0800?3,' . $param);
$response = $this->dispatch();
$parsedBody = $this->parse($response->getBody());
$entities = array();
foreach ($parsedBody as $data)
{
$steeringWheel = new SteeringWheelEntity();
// Fill SteeringWheelEntity with data
$entities[] = $steeringWheel;
}
return $entities;
}
}
The AbstractWebService
use \Zend\Http\Client;
use \Zend\Http\Request;
class AbstractWebService
{
private $httpClient;
private $request;
private $response;
protected $config;
private $url;
public function __construct(Client $httpClient, Request $request, Array $config)
{
$this->url = $config['api']['url'];
$this->httpClient = $httpClient;
$this->request = $request;
$this->config = $config;
}
protected function setUrl($url)
{
$this->url = $url;
return $this->url;
}
protected function appendUrl($string)
{
$this->url .= $string;
}
protected function getUrl()
{
return $this->url;
}
public function dispatch()
{
$this->request->setUri($this->getUrl());
$this->response = $this->httpClient->dispatch($this->request);
if (!$this->response->isSuccess()) {
throw new \Exception('HTTP error #' . $this->response->getStatusCode() . ' when connecting to ' . $this->getUrl() . '.');
}
return $this->response;
}
public function parse()
{
// Parse the content
}
}
Rather than using a mock for a web service. Could you just mock the \Zend\Http\Request and \Zend\Http\Client objects as they are doing the work for you? This way you have control over what the Zend objects return to you versus having to try to mock the web service.
That would be how I would go about testing the services.