Having a NuSOAP web service defined in an inline route closure function works great, but having it in a route closure controller does not.
Example: working
routes.php:
Route::any('api', function() {
require_once ('nusoap.php');
$server = new \nusoap_server();
$server->configureWSDL('TestService', false, url('api'));
$server->register('test',
array('input' => 'xsd:string'),
array('output' => 'xsd:string'),
);
function test($input){
return $input;
}
$rawPostData = file_get_contents("php://input");
return \Response::make($server->service($rawPostData), 200, array('Content-Type' => 'text/xml; charset=ISO-8859-1'));
});
SOAP Test Client
require_once('nusoap.php');
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
$result = $client->call("test", "HelloWorld");
print_r($result); exit();
response
HelloWorld
This works as expected.
Example: not working
Moving the code to a dedicated controller breaks it:
routes.php:
Route::any('api', 'SoapController#server');
SoapController.php:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SoapController extends Controller {
public function server() {
require_once ('nusoap.php');
$server = new \nusoap_server();
$server->configureWSDL('TestService', false, url('api'));
$server->register('test',
array('input' => 'xsd:string'),
array('output' => 'xsd:string'),
);
function test($input){
return $input;
}
$rawPostData = file_get_contents("php://input");
return \Response::make($server->service($rawPostData), 200, array('Content-Type' => 'text/xml; charset=ISO-8859-1'));
}
}
SOAP Test Client
require_once('nusoap.php');
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
$result = $client->call("test", "HelloWorld");
print_r($result); exit();
response
"method 'test' ('test') not defined in service('' '')
Steps To Reproduce:
Install a fresh copy of Laravel v5.2.45
Download the last NuSOAP version (v0.9.5)
Define the route for the NuSOAP Web Service - copy and paste the code from above
Create the SoapController - copy and paste the code from above
Create a new temp route to act as the SOAP client, and copy and paste the code beneath SOAP Test Client above into this route's closure function
Load the temp route page in the browser to execute a SOAP call to the Web Service
Conclusion:
This indicates, that for some reason, the output is different when using an inline route function versus using a dedicated route controller.
How could this be?
Your insight greatly appreciated:
If you have:
any experience with this and you have a solution
or, you have an idea on why this could occur
or, you have any thoughts on why having the NuSOAP code in the controller generates a different response than having it in an inline route function
...please chime in.
Thank you for your thoughts!
I have a simple solution for this.
Just install nusoap via composer.
composer require econea/nusoap
Call nusoap as needed:
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
I hope that helps.
CSRF validation should be passive for current laravel versions.
app\Http\Middleware\VerifyCsrfToken.php
protected $except = [
'your way'
];
Must be.
Related
In a Laravel project (Laravel 8 on PHP 8.0) I have a feature test in which I test an internal endpoint. The endpoint has a Controller calls a method on a Service. The Service then tries to call a third-party endpoint. It is this third-party endpoint that I would like to mock. The situation currently looks like this:
Internal Endpoint Feature Test
public function testStoreInternalEndpointSuccessful(): void
{
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
Internal Endpoint Controller
class InternalEndpointController extends Controller
{
public function __construct(protected InternalService $internalService)
{
}
public function store(Request $request): InternalResource
{
$data = $this.internalService->fetchExternalData();
return new InternalResource($data); // etc.
}
}
Internal Service
use GuzzleHttp\ClientInterface;
class InternalService
{
public function __construct(protected ClientInterface $client)
{
}
public function fetchExternalData()
{
$response = $this->httpClient->request('GET', 'v1/external-data');
$body = json_decode($response->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR);
return $body;
}
}
I have looked at Guzzle's documentation, but it seems like the MockHandler strategy requires you to execute the http request inside of the test, which is not wat I want in my test. I want Guzzle's http client to be mocked and to return a custom http response that I can specify in my test. I have tried to mock Guzzle's http client like this:
public function testStoreInternalEndpointSuccessful(): void
{
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
$mock = new MockHandler([
new GuzzleResponse(200, [], $contactResponse),
]);
$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);
$mock = Mockery::mock(Client::class);
$mock
->shouldReceive('create')
->andReturn($client);
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
But the InternalService does not seem to hit this mock in the test.
I have also considered and tried to use Http Fake, but it didn't work and I assume Guzzle's http client does not extend Laravel's http client.
What would be the best way to approach this problem and mock the third-party endpoint?
Edit
Inspired by this StackOverflow question, I have managed to solve this problem by injecting a Guzzle client with mocked responses into my service. The difference to the aforementioned StackOverflow question is that I had to use $this->app->singleton instead of $this->app->bind because my DI was configured differently:
AppServiceProvider.php
namespace App\Providers;
use App\Service\InternalService;
use GuzzleHttp\Client;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// my app uses ->singleton instead of ->bind
$this->app->singleton(InternalService::class, function () {
return new InternalService(new Client([
'base_uri' => config('app.internal.base_url'),
]));
});
}
}
Depending on your depending injection, you want to bind or singleton-ify your InternalService with a custom Guzzle http client that returns mocked responses, e.g. like this:
public function testStoreInternalEndpointSuccessful(): void
{
// depending on your DI configuration,
// this could be ->bind or ->singleton
$this->app->singleton(InternalService::class, function($app) {
$mockResponse = json_encode([
'data' => [
'id' => 0,
'name' => 'Jane Doe',
'type' => 'External',
'description' => 'Etc. you know the drill',
]
]);
$mock = new GuzzleHttp\Handler\MockHandler([
new GuzzleHttp\Psr7\Response(200, [], $mockResponse),
]);
$handlerStack = GuzzleHttp\HandlerStack::create($mock);
$client = new GuzzleHttp\Client(['handler' => $handlerStack]);
return new InternalService($client);
});
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
See also: Unit Testing Guzzle inside of Laravel Controller with PHPUnit
I tried implementing the below code but it is showing the EachPromise Error class not found and Promise Error class not found. Guzzle library is installed. Then also this error was there.
<?php
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Psr7\Response;
$users = ['one', 'two', 'three'];
$promises = (function () use ($users) {
foreach ($users as $user) {
// Using generator
yield $this->getAsync(
'https://api.demo.com/v1/users?username='
. $user);
}
})();
$eachPromise = new EachPromise($promises, [
// Number of concurrency
'concurrency' => 4,
'fulfilled' => function (Response $response) {
if ($response->getStatusCode() == 200) {
$user = json_decode(
$response->getBody(), true);
// processing response of the user
}
},
'rejected' => function ($reason) {
// handle promise rejected
}
]);
$eachPromise->promise()->wait();
?>
Seem there is no autoloader that locates and loads the class files mentioned in your code.
If your Guzzle package is installed with composer, you may try require_once() the /vendor/autoloader.php file in your source root.
Otherwise, try loading those class files individually - which is not recommended in modern PHP world(use autoloader whenever possible).
require_once("path-to/EachPromise.php");
require_once("path-to/Response.php");
EDIT
In the second sight, your code uses $this that requires an object instance context with $this->getAsync() being available, but apparently not so. The code seems like a partially copy-pasted code fragment from a Class object that won't run in your given set up.
You may need to set up the code context of your sample code.
I am using the default PHPUnit that comes with Lumen. While I am able to create a mock post call to my link, I am unable to find a way to feed raw data to it.
Currently, to mock up JSON input, from official document, I can:
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
Or if I want simple form input, I can:
$this->post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
Is there a way I can insert raw body content to the post request? (Or at least a request with XML input? This is a server to receive callback from WeChat, where we have no choice but forced to use XML as WeChat wanted to use.)
As stated in the documentation if you want to create a custom HTTP request you can use the call method:
If you would like to make a custom HTTP request into your application
and get the full Illuminate\Http\Response object, you may use the call
method:
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
Here is the call method:
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
So in your case it would be something like this:
$this->call('POST', '/user', [], [], [], ['Content-Type' => 'text/xml; charset=UTF8'], $xml);
To access the data in your controller you can use the following:
use Illuminate\Http\Request;
public function store(Request $request)
{
$xml = $request->getContent();
// Or you can use the global request helper
$xml = request()->getContent();
}
In my controller function I am using a require statement to include a file:
require app_path().'/plivo/plivo.php';
After this statement, I try to redirect from this controller using the following statement:
return Redirect::back()->with('success', 'Note added successfully');
However, this gives me the following error:
Call to undefined method Redirect::back()
How can I redirect from this function?
This is my full code:
public function sendSMS(){
require app_path().'/plivo/plivo.php';
$auth_id = "XXXXXXXXXXXX";
$auth_token = "XXXXXXXXXXXXXXXXXXXXX";
$p = new \RestAPI($auth_id, $auth_token);
$params = array(
'src' => '1XX7XX0',
'dst' => '91XXXXXXXXX7',
'text' => 'Test SMS',
'method' => 'POST'
);
$response = $p->send_message($params);
return Redirect::back()->with('success', 'Note added successfully');
}
This answer assumes that plivo.php is from this git repo.
The issue is that the plivo.php library defines a Redirect class in the global namespace. Because of this, Laravel does not register the global Redirect alias to point to the Illuminate\Support\Facades\Redirect facade.
So, in your final line return Redirect::back()->with(...);, the Redirect class being used is the class defined in the plivo.php library, not Laravel's Illuminate\Support\Facades\Redirect class.
The quickest fix would be to change your line to:
return Illuminate\Support\Facades\Redirect::back()->with('success', 'Note added successfully');
Another option would be to inject Laravel's redirector into your controller, and use that instead of using the facade:
class MyController extends BaseController {
public function __construct(\Illuminate\Routing\Redirector $redirector) {
$this->redirector = $redirector;
}
public function sendSMS() {
require app_path().'/plivo/plivo.php';
//
return $this->redirector->back()->with('success', 'Note added successfully');
}
}
A third option would be to update your code to use the plivo composer package, which has a namespace. The updates have been done in the dev branch of the repo, which you can find here. If you did this, you would get rid of your require statement and use the namespaced plivo classes.
I have a symfony website, and Im trying to do some unit testing. I have this kind of test where I try to submit something:
<?php
namespace Acme\AcmeBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomeControllerTest extends WebTestCase {
public function testrandomeThings() {
$client = static::createClient();
$crawler = $client->request(
'POST',
'/',
array(
"shopNumber" => 0099,
"cardNumber" => 231,
"cardPIN" => "adasd"),
array(),
array());
}
but I dont think that the data Im sending is being received in the controler:
class HomeController extends Controller
{
public function indexAction()
{
var_dump($_POST);
die;
return $this->render('AcmeBundle:Home:index.html.twig');
}
}
the var_dump is actually returning me an empty array.
What am I missing to send information through my POST request?
$_POST is a variable filled by PHP and the symfony request is only created from this globals if called directly over http. The symfony crawler doesn't make a real request, it creates a request from the parameters supplied in your $client->request and executes it. You need to access this stuff via the Request object. Never use $_POST, $_GET, etc. directly.
use Symfony\Component\HttpFoundation\Request;
class HomeController extends CoralBaseController
{
public function indexAction(Request $request)
{
var_dump($request->request->all());
die;
return $this->render('CoralWalletBundle:Home:index.html.twig');
}
}
use $request->request->all() to get all POST parameters in an array. To get only a specific parameter you can use $request->request->get('my_param'). If you ever need to acces GET parameters you can use $request->query->get('my_param'), but better set query parameters already in the routing pattern.
I think you're trying to do this:
$client = static::createClient();
$client->request($method, $url, [], [], [], json_encode($content));
$this->assertEquals(
200,
$client->getResponse()
->getStatusCode()
);
You're putting your data (content) in as the params array but you want to put it in as the raw body content which is a JSON encoded string.