Zend Soap server example - php

I'm trying to setup soap server by following https://framework.zend.com/blog/2017-01-24-zend-soap-server.html tutorial.
My corresponding changes
<?php
namespace Soap\Controller;
use Soap\Model;
use Zend\Soap\AutoDiscover as WsdlAutoDiscover;
use Zend\Soap\Server as SoapServer;
use Zend\Mvc\Controller\AbstractActionController;
class SoapController extends AbstractActionController{
private $env;
public function __construct(Model\Env $env){
$this->env = $env;
}
public function wsdlAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isGet()) {
return $this->prepareClientErrorResponse('GET');
}
$wsdl = new WsdlAutoDiscover();
$wsdl = new WsdlAutoDiscover();
$this->populateServer($wsdl);
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->getHeaders()->addHeaderLine('Content-Type', 'application/wsdl+xml');
$response->setContent($wsdl->toXml());
return $response;
}
private function prepareClientErrorResponse($allowed){
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->setStatusCode(405);
$response->getHeaders()->addHeaderLine('Allow', $allowed);
return $response;
}
private function populateServer($server){
// Expose a class and its methods:
$server->setClass(Model\Products::class);
// Expose an object instance and its methods:
$server->setObject($this->env);
// Expose a function:
$server->addFunction('Soap\Model\Env\ping');
$server->addFunction('Soap\Model\Env\pong');
}
public function serverAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isPost()) {
return $this->prepareClientErrorResponse('POST');
}
// Create the server
$server = new SoapServer(
$this->url()
->fromRoute('soap/wsdl', [], ['force_canonical' => true]),
[
'actor' => $this->url()
->fromRoute('soap/server', [], ['force_canonical' => true]),
]
);
$server->setReturnResponse(true);
$this->populateServer($server);
$soapResponse = $server->handle($request->getContent());
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
// Set the headers and content
$response->getHeaders()->addHeaderLine('Content-Type', 'application/soap+xml');
$response->setContent($soapResponse);
return $response;
}
}
After running the server I'm getting error
Call to undefined method Zend\Soap\AutoDiscover::setObject()
When I checked in source of AutoDiscover there is no setObject, What changes do I do to fix it.

This is the populateServer method from the link you posted:
use Acme\Model;
function populateServer($server, array $env)
{
// Expose a class and its methods:
$server->setClass(Model\Calculator::class);
// Or expose an object instance and its methods.
// However, this only works for Zend\Soap\Server, not AutoDiscover, so
// should not be used here.
// $server->setObject(new Model\Env($env));
// Expose a function:
$server->addFunction('Acme\Model\ping');
}
And the note just below that:
[...] if you want to create logic that can be re-used between the Server and AutoDiscover instances, you must confine your usage to setClass(). If that class requires constructor arguments or other ways of setting instance state, you should vary the logic for creation of the WSDL via AutoDiscover and creation of the server via Server.

Related

Symfony Deprecation on SessionTokenStorage when generating a csrf token in phpunit functionnal tests

I'm on symfony 5.4
I didn't understand what symfony really need in order to correct this deprecation:
Since symfony/security-csrf 5.3: Using the "Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage" without a session has no effect and is deprecated. It will throw a "Symfony\Component\HttpFoundation\Exception\SessionNotFoundException" in Symfony 6.0
1x in MeansCablesControllerTest::TestDatagridAdd from App\Tests\Controller
My function in tests/Controller/MeansBenchesControllerTest.php (WebTestCase) :
function datagridAddUpdate($controllerName, $dataArray)
{
$client = static::createClient();
$usersRepository = static::getContainer()->get(UsersRepository::class);
$testUserAdmin = $usersRepository->find(1);
$client->loginUser($testUserAdmin);
$csrfToken = $client->getContainer()->get('security.csrf.token_manager')->getToken($controllerName.'Token_item');
$dataArray['_token'] = $csrfToken;
$crawler = $client->request('POST', '/datagridAddUpdate/'.$controllerName,$dataArray, [], ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest']);
$this->assertResponseIsSuccessful('Status code 2xx pour datagridAdd : '.$controllerName);
}
Running this before building the form will make sure there is a session available for it:
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
self::getContainer()->get(RequestStack::class)->push($request);
Unfortunately, #bart's answer didn't work for me. It effectively suppressed the deprecation warning, but it creates a separate session from the loginUser() session. My goal was to log in, and then be able to set session values on the common logged in session.
I figured out a workaround for my use case, documented here: https://github.com/symfony/symfony/discussions/46961
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\UserInterface;
abstract class AbstractWebTestCase extends WebTestCase
{
private KernelBrowser $client;
private SessionInterface $session;
public function setUp(): void
{
parent::setUp();
$this->client = static::createClient();
...
}
...
/**
* This replicates static::createClient()->loginUser()
* Inspect that method, as there are additional checks there that may be necessary for your use case.
* The magic here is tracking an internal $session object that can be updated as needed.
*/
protected function loginUser(UserInterface $user): void
{
$token = new TestBrowserToken($user->getRoles(), $user, $firewallContext);
$container = static::getContainer();
$container->get('security.untracked_token_storage')->setToken($token);
$this->session = $container->get('session.factory')->createSession();
$this->setLoginSessionValue('_security_'.$firewallContext, serialize($token));
$domains = array_unique(array_map(function (Cookie $cookie) {
return $cookie->getName() === $this->session->getName() ? $cookie->getDomain() : '';
}, $this->client->getCookieJar()->all())) ?: [''];
foreach ($domains as $domain) {
$cookie = new Cookie($this->session->getName(), $this->session->getId(), null, null, $domain);
$this->client->getCookieJar()->set($cookie);
}
return $this;
}
/** #param mixed $value */
protected function setLoginSessionValue(string $name, $value): self
{
if (isset($this->session)) {
$this->session->set($name, $value);
$this->session->save();
return $this;
}
throw new \LogicException("loginUser() must be called to initialize session");
}
...
}
And now we can update the session:
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
class MyWebTest extends AbstractWebTestCase
{
public function testSomething(): void
{
$user = ...;
$this->loginUser($user);
// Technically, you don't need to generate a real token here, and instead could use any test string
$tokenId = ...;
$csrfToken = static::getContainer()->get('security.csrf.token_generator')->generateToken();
$this->setLoginSessionValue(SessionTokenStorage::SESSION_NAMESPACE . "/$tokenId", $csrfToken);
// Now you can make raw POST requests without crawling to the form page first!
}
}

How to mock request method in phpunit mockery?

I started using mockery so I have a problem in doing my unit test . I want to test authenticate middleware , I passed one condition for expectsJson so I need one more pattern to return true from expectesJson like below but not success
Authenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
AuthenticatTest.php
class AuthenticateTest extends TestCase
{
/**
* A basic unit test example.
*
* #return void
*/
public function testMiddleware()
{
$request = Request::create(config('app.url') . '500', 'GET',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:8000']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$expectedStatusCode = 401;
$this->assertContains("http://",$method->invokeArgs($middleware,[$request]));
}
public function testMiddlewareElse()
{
$this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$request]));
}
}
testMiddlewareElse is failed , How to return true for $request->expectsJson
Here's how you could test a request for the authentication middleware. Assume that you have a route that requires authentication that is managed by UserController#dashboard (or similar):
public function testMiddleware() {
// You could disable the other middleware of the route if you don't want them to run e.g.
// $this->withoutMiddleware([ list of middleware to disable ]);
$mockController = $this->prophecy(UserController::class);
//This is if the middleware passes and the controller method is called, use shouldNotBeCalled if you expect it to fail
$mockController->dashboard(Prophecy::any())->shouldBeCalled();
$this->app->instance(
UserController::class,
$mockController->reveal()
);
$this->json("GET", url()->action("UserController#dashboard"));
}
I found the solution ! I need to pass mock class in invoke params ...;)
public function testMiddlewareElse()
{
$mock = $this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$mock]));
}

Unit testing guzzle http promises callbacks

I have the following code in a class:
private function makeRequest(DetailedPayload $detailedPayload, $request): void
{
$this->httpClient
->sendAsync($request)
->then(
function (ResponseInterface $response) use ($detailedPayload) {
$this->handleServerResponse($detailedPayload, $response);
},
function (RequestException $exception) use ($detailedPayload) {
$this->logRequestException($detailedPayload, $exception);
}
);
}
An now my handler functions looks like so:
private function handleServerResponse(DetailedPayload $detailedPayload, ResponseInterface $response): void
{
if (200 === $response->getStatusCode()) {
try {
$this->queueDataPublisher->publish($detailedPayload);
} catch (FailedToPublishQueueDataException $exception) {
$this->logPublisherException($detailedPayload, $response);
}
} else {
$this->logNonOkResponse($detailedPayload, $response);
}
}
Now I want to test my class which has the signature:
public function __construct(Client $httpClient, LoggerInterface $logger, QueueDataPublisher $queueDataPublisher)
I can mock all the logger and the publisher class and also can follow the instruction to mock the http request as mentioned on the guzzle documentation found here: http://docs.guzzlephp.org/en/stable/testing.html
My test looks as below:
/**
* #test
*
* #throws \Exception
*/
public function willMakeHttpRequestToServer()
{
$client = new Client(
[
'handler' => HandlerStack::create(
new MockHandler(
[
new Response(200)
]
)
)
]
);
$logger = $this->prophesize(LoggerInterface::class);
$queueDataPublisher = $this->prophesize(QueueDataPublisher::class);
$transportClient = new TransportClient($client, $logger->reveal(), $queueDataPublisher->reveal());
$detailedPayload = (new DetailedPayload())
->setStepId('test_step_id')
->setStageId('test_stage_id')
->setProtocolId('test_protocol_id')
->setPayload('test_payload');
$queueDataPublisher
->publish($detailedPayload)
->shouldBeCalled();
$transportClient->sendPayload($detailedPayload);
}
But I can never get this test to green. Has andbody tried something like this to test the async request.
Any idea on how I can approach to test this implementation.
The test requrns the response telling the expectation to call of a function on publisher failed as so:
No calls have been made that match:

Symfony - get access to the method from another file in controller

I am using symfony 3 and trying to get access to the class I declared in
src/AppBundle/Service/ApiEngine.php
namespace AppBundle\Service;
use DateTime;
class ApiEngine {
private $api_handler;
public function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public function getProfileData() {
return [ /*some data*/ ];
}
}
I declared this file in
config/service.yml
service:
*
*
*
api:
class: AppBundle\Service\ApiEngine
arguments: ["#username", "#repos"]
In controller I am trying to use some of the ApiEngine methods like this:
src/AppBundle/Controller/GitApiController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class GitApiController extends Controller {
/**
* #Route("/{username}", name ="gitapi", defaults={"username": "symfony"})
*/
public function gitApiAction($username) {
$api = $this->get('api')->__construct($username, false)->getProfileData();
return $this->render('gitapi/index.html.twig', $api);
} }
But it gives me this error:
(1/1) ServiceNotFoundException The service "api" has a dependency on a
non-existent service "username".
I advise you to change your class into this for example:
private function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public static function createApiEngine($username, bool $repos)
{
return new self($username, $bool);
}
After inside your controller you can do this:
$api = ApiEngine::createApiEngine($username, false);
$api->getProfileData();
Into your controller you need to insert a use for ApiEngine, in this use case you don't need dependency injection so inside your services.yml remove arguments please

Laravel dependency injection in domain namespace

I am having issues getting dependency injection to work with dependency inversion. I can call App::make in the constructor, and the dependency inversion works just fine ... it is only when I try to inject it into the constructor that I have issues.
ReflectionException Class StationsInterface does not exist
A uri that would hit this would be .../stats/station/mogreet/6
The File Stucture:
-app
-controllers
*StatsController
-Dashboard
-Datasync
-Interfaces
*DatasyncInterface
*MogreetDatasyncInterface
-Services
*MogreetDatasync
*BackendServiceProvider
*DatasyncBase
-Repositories
-Interfaces
*CredentialsInterface
*StationsInterface
-Stations
*DbCredentials
*DbStations
*FileCredentials
-Stats
*BackendServiceProvider
*DbRepositoryBase
The relevant code blocks are as follows:
Service Provider:
<?php namespace Dashboard\Repositories;
use Illuminate\Support\ServiceProvider;
class BackendServiceProvider extends ServiceProvider {
public function register() {
// Service Providers located in Stats directory
$this->app->bind('StatsDailyInterface', 'Dashboard\Repositories\Stats\DbStatsDaily');
//$this->app->bind('StatsMonthlyRepository', 'Dashboard\Repositories\Stats\DbStatsMonthly');
//$this->app->bind('StatsYearlyRepository', 'Dashboard\Repositories\Stats\DbStatsYearly');
// Service Providers located in Stations directory
$this->app->bind('CredentialsInterface', 'Dashboard\Repositories\Stations\FileCredentials');
$this->app->bind('StationsInterface', 'Dashboard\Repositories\Stations\DbStations');
}
}
Controller: Note that in the constructor of this I am using App::make instead of Injecting the Dependency. If I inject the dependency I get a class resolution error exactly like I do in the DatasyncBase class.
<?php
use Dashboard\ConrollerFacades\Facades\Services;
class StatsController extends BaseController {
/*
|--------------------------------------------------------------------------
| Stats Controller
|--------------------------------------------------------------------------
|
| Pull and display stats for station, market, or corporate views
|
|
|
*/
private $StationModel;
public function __construct() {
$this->StationModel = App::make('StationsInterface');
}
/**
* Pulls stats for an individual station
*
* #param string $service of station
* #param integer $id of station
*
* #return void
*/
public function station( $service, $stationId ) {
$this->Service = $this->serviceSelector($service);
if(!$this->Service) throw new Exception('Unknown Service Selected', 1);
$this->Service->init($stationId);
exit();
}
/**
* Pulls stats for a Market
*
* #param integer $id of market
*
* #return void
*/
public function market( $service, $marketId ) {
$this->Service = $this->serviceSelector($service);
if(!$this->Service) throw new Exception('Unknown Service Selected', 1);
foreach($StationModel->getStationIdsByMarket($marketId) as $station) {
$this->Service->init($station);
}
exit();
}
/**
* Pulls stats for Corporate (all stations)
*
* #return void
*/
public function corporate( $service ) {
$this->Service = $this->serviceSelector($service);
if(!$this->Service) throw new Exception('Unknown Service Selected', 1);
foreach($StationModel->getAllStationIds() as $station) {
$this->Service->init($station);
}
exit();
}
private function serviceSelector($service) {
switch(strtolower($service)) {
case 'brightcove': return App::make('BrightCoveDatasyncInterface'); break;
case 'facebook': return App::make('FacebookDatasyncInterface'); break;
case 'googleanalytics': return App::make('GoogleAnalyticsDatasyncInterface'); break;
case 'liquidcompass': return App::make('LiquidCompassDatasyncInterface'); break;
case 'mogreet': return App::make('MogreetDatasyncInterface'); break;
case 'twitter': return App::make('TwitterDatasyncInterface'); break;
default: return false; break;
}
}
}
The constructor of this class is where the dependency injection issue is occurring.
DatasyncBase: This class is never directly instantiated, it is inherited by a service class like MogreetDatasync. Moving the constructor to the MogreetDatasync class for testing does not resolve the issue.
<?php namespace Dashboard\Datasync;
use Dashboard\Repositories\Interfaces\StationsInterface;
use Dashboard\Repositories\Interfaces\CredentialsInterface;
class DatasyncBase {
protected $Station;
protected $Credentials;
protected $results;
protected $stats;
public function __construct(StationsInterface $Station , CredentialsInterface $Credentials) {
$this->Station = $Station;
$this->Credentials = $Credentials;
$this->stats = array();
}
public function __destruct() {
unset($this->results);
unset($this->stats);
}
public function init() {}
protected function fetch($uri = null, $post_fields = null) {
$cURL = curl_init();
curl_setopt($cURL, CURLOPT_URL, $uri);
curl_setopt($cURL, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($cURL, CURLOPT_POSTFIELDS, $post_fields);
$this->results = curl_exec($cURL);
curl_close($cURL);
}
}
A Datasync Service:
<?php namespace Dashboard\Datasync\Services;
use Dashboard\Datasync\DatasyncBase;
use Dashboard\Datasync\Interfaces\MogreetDatasyncInterface;
class MogreetDatasync extends DatasyncBase implements MogreetDatasyncInterface {
public function init($stationId) {}
protected function uri() {}
protected function parse() {}
protected function write() {}
}
The answer to this question is closures for the ServiceDatasyncInterfaces. Previously I was defining the bindings like this:
$this->app->bind('MogreetDatasyncInterface', 'Dashboard\Datasync\Services\MogreetDatasync');
This however does NOT allow the IoC to "recursively" inject the Inversion Dependencies, you must use App::make('InversionInterface') for IoC to actually resolve this correctly.
<?php namespace Dashboard\Datasync;
use Illuminate\Support\ServiceProvider;
use Dashboard\Datasync\Services\BrightCoveDatasync;
use Dashboard\Datasync\Services\FacebookDatasync;
use Dashboard\Datasync\Services\GoogleAnalyticsDatasync;
use Dashboard\Datasync\Services\LiquidCompassDatasync;
use Dashboard\Datasync\Services\MogreetDatasync;
use Dashboard\Datasync\Services\TwitterDatasync;
class BackendServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind('BrightCoveDatasyncInterface', function() { return new BrightCoveDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
$this->app->bind('FacebookDatasyncInterface', function() { return new FacebookDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
$this->app->bind('GoogleAnalyticsDatasyncInterface', function() { return new GoogleAnalyticsDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
$this->app->bind('LiquidCompassDatasyncInterface', function() { return new LiquidCompassDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
$this->app->bind('MogreetDatasyncInterface', function() { return new MogreetDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
$this->app->bind('TwitterDatasyncInterface', function() { return new TwitterDatasync( $this->app->make('StationsInterface'), $this->app->make('CredentialsInterface') ); });
}
}
This is a rather minor issue, but you will need to use the correct interface in the file containing the class that is being injected. My DatasyncBase file now looks like this:
<?php namespace Dashboard\Datasync;
use Dashboard\Repositories\Interfaces\StationsInterface;
use Dashboard\Repositories\Interfaces\CredentialsInterface;
class DatasyncBase {
protected $Station;
protected $Credentials;
protected $results;
protected $stats;
public function __construct(StationsInterface $Station, CredentialsInterface $Credentials) {
$this->Station = $Station;
$this->Credentials = $Credentials;
$this->stats = array();
}
public function __destruct() {
unset($this->results);
unset($this->stats);
}
public function init() {}
protected function fetch($uri, $post_fields = '') {
$cURL = curl_init();
curl_setopt($cURL, CURLOPT_URL, $uri);
curl_setopt($cURL, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($cURL, CURLOPT_POSTFIELDS, $post_fields);
$this->results = curl_exec($cURL);
curl_close($cURL);
}
}
You can find more on ServiceProvider's here:
https://laracasts.com/lessons/service-providers-decoded

Categories